]> git.saurik.com Git - apt.git/blame_incremental - methods/gpgv.cc
properly check for "all good sigs are weak"
[apt.git] / methods / gpgv.cc
... / ...
CommitLineData
1#include <config.h>
2
3#include <apt-pkg/acquire-method.h>
4#include <apt-pkg/configuration.h>
5#include <apt-pkg/error.h>
6#include <apt-pkg/gpgv.h>
7#include <apt-pkg/strutl.h>
8#include <apt-pkg/fileutl.h>
9#include "aptmethod.h"
10
11#include <ctype.h>
12#include <errno.h>
13#include <stddef.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <sys/wait.h>
18#include <unistd.h>
19
20#include <array>
21#include <algorithm>
22#include <sstream>
23#include <iterator>
24#include <iostream>
25#include <string>
26#include <vector>
27
28#include <apti18n.h>
29
30using std::string;
31using std::vector;
32
33#define GNUPGPREFIX "[GNUPG:]"
34#define GNUPGBADSIG "[GNUPG:] BADSIG"
35#define GNUPGNOPUBKEY "[GNUPG:] NO_PUBKEY"
36#define GNUPGVALIDSIG "[GNUPG:] VALIDSIG"
37#define GNUPGGOODSIG "[GNUPG:] GOODSIG"
38#define GNUPGKEYEXPIRED "[GNUPG:] KEYEXPIRED"
39#define GNUPGREVKEYSIG "[GNUPG:] REVKEYSIG"
40#define GNUPGNODATA "[GNUPG:] NODATA"
41
42struct Digest {
43 enum class State {
44 Untrusted,
45 Weak,
46 Trusted,
47 } state;
48 char name[32];
49};
50
51static constexpr Digest Digests[] = {
52 {Digest::State::Untrusted, "Invalid digest"},
53 {Digest::State::Untrusted, "MD5"},
54 {Digest::State::Weak, "SHA1"},
55 {Digest::State::Weak, "RIPE-MD/160"},
56 {Digest::State::Trusted, "Reserved digest"},
57 {Digest::State::Trusted, "Reserved digest"},
58 {Digest::State::Trusted, "Reserved digest"},
59 {Digest::State::Trusted, "Reserved digest"},
60 {Digest::State::Trusted, "SHA256"},
61 {Digest::State::Trusted, "SHA384"},
62 {Digest::State::Trusted, "SHA512"},
63 {Digest::State::Trusted, "SHA224"},
64};
65
66static Digest FindDigest(std::string const & Digest)
67{
68 int id = atoi(Digest.c_str());
69 if (id >= 0 && static_cast<unsigned>(id) < _count(Digests)) {
70 return Digests[id];
71 } else {
72 return Digests[0];
73 }
74}
75
76struct Signer {
77 std::string key;
78 std::string note;
79};
80
81class GPGVMethod : public aptMethod
82{
83 private:
84 string VerifyGetSigners(const char *file, const char *outfile,
85 std::string const &key,
86 vector<string> &GoodSigners,
87 vector<string> &BadSigners,
88 vector<string> &WorthlessSigners,
89 vector<Signer> &SoonWorthlessSigners,
90 vector<string> &NoPubKeySigners);
91 protected:
92 virtual bool URIAcquire(std::string const &Message, FetchItem *Itm) APT_OVERRIDE;
93 public:
94
95 GPGVMethod() : aptMethod("gpgv","1.0",SingleInstance | SendConfig) {};
96};
97
98string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
99 std::string const &key,
100 vector<string> &GoodSigners,
101 vector<string> &BadSigners,
102 vector<string> &WorthlessSigners,
103 vector<Signer> &SoonWorthlessSigners,
104 vector<string> &NoPubKeySigners)
105{
106 bool const Debug = _config->FindB("Debug::Acquire::gpgv", false);
107
108 if (Debug == true)
109 std::clog << "inside VerifyGetSigners" << std::endl;
110
111 int fd[2];
112 bool const keyIsID = (key.empty() == false && key[0] != '/');
113
114 if (pipe(fd) < 0)
115 return "Couldn't create pipe";
116
117 pid_t pid = fork();
118 if (pid < 0)
119 return string("Couldn't spawn new process") + strerror(errno);
120 else if (pid == 0)
121 ExecGPGV(outfile, file, 3, fd, (keyIsID ? "" : key));
122 close(fd[1]);
123
124 FILE *pipein = fdopen(fd[0], "r");
125
126 // Loop over the output of apt-key (which really is gnupg), and check the signatures.
127 std::vector<std::string> ValidSigners;
128 size_t buffersize = 0;
129 char *buffer = NULL;
130 while (1)
131 {
132 if (getline(&buffer, &buffersize, pipein) == -1)
133 break;
134 if (Debug == true)
135 std::clog << "Read: " << buffer << std::endl;
136
137 // Push the data into three separate vectors, which
138 // we later concatenate. They're kept separate so
139 // if we improve the apt method communication stuff later
140 // it will be better.
141 if (strncmp(buffer, GNUPGBADSIG, sizeof(GNUPGBADSIG)-1) == 0)
142 {
143 if (Debug == true)
144 std::clog << "Got BADSIG! " << std::endl;
145 BadSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
146 }
147 else if (strncmp(buffer, GNUPGNOPUBKEY, sizeof(GNUPGNOPUBKEY)-1) == 0)
148 {
149 if (Debug == true)
150 std::clog << "Got NO_PUBKEY " << std::endl;
151 NoPubKeySigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
152 }
153 else if (strncmp(buffer, GNUPGNODATA, sizeof(GNUPGBADSIG)-1) == 0)
154 {
155 if (Debug == true)
156 std::clog << "Got NODATA! " << std::endl;
157 BadSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
158 }
159 else if (strncmp(buffer, GNUPGKEYEXPIRED, sizeof(GNUPGKEYEXPIRED)-1) == 0)
160 {
161 if (Debug == true)
162 std::clog << "Got KEYEXPIRED! " << std::endl;
163 WorthlessSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
164 }
165 else if (strncmp(buffer, GNUPGREVKEYSIG, sizeof(GNUPGREVKEYSIG)-1) == 0)
166 {
167 if (Debug == true)
168 std::clog << "Got REVKEYSIG! " << std::endl;
169 WorthlessSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
170 }
171 else if (strncmp(buffer, GNUPGGOODSIG, sizeof(GNUPGGOODSIG)-1) == 0)
172 {
173 char *sig = buffer + sizeof(GNUPGPREFIX);
174 char *p = sig + sizeof("GOODSIG");
175 while (*p && isxdigit(*p))
176 p++;
177 *p = 0;
178 if (Debug == true)
179 std::clog << "Got GOODSIG, key ID:" << sig << std::endl;
180 GoodSigners.push_back(string(sig));
181 }
182 else if (strncmp(buffer, GNUPGVALIDSIG, sizeof(GNUPGVALIDSIG)-1) == 0)
183 {
184 char *sig = buffer + sizeof(GNUPGVALIDSIG);
185 std::istringstream iss((string(sig)));
186 vector<string> tokens{std::istream_iterator<string>{iss},
187 std::istream_iterator<string>{}};
188 char *p = sig;
189 while (*p && isxdigit(*p))
190 p++;
191 *p = 0;
192 // Reject weak digest algorithms
193 Digest digest = FindDigest(tokens[7]);
194 switch (digest.state) {
195 case Digest::State::Weak:
196 // Treat them like an expired key: For that a message about expiry
197 // is emitted, a VALIDSIG, but no GOODSIG.
198 SoonWorthlessSigners.push_back({string(sig), digest.name});
199 if (Debug == true)
200 std::clog << "Got weak VALIDSIG, key ID: " << sig << std::endl;
201 break;
202 case Digest::State::Untrusted:
203 // Treat them like an expired key: For that a message about expiry
204 // is emitted, a VALIDSIG, but no GOODSIG.
205 WorthlessSigners.push_back(string(sig));
206 GoodSigners.erase(std::remove(GoodSigners.begin(), GoodSigners.end(), string(sig)));
207 if (Debug == true)
208 std::clog << "Got untrusted VALIDSIG, key ID: " << sig << std::endl;
209 break;
210 case Digest::State::Trusted:
211 if (Debug == true)
212 std::clog << "Got trusted VALIDSIG, key ID: " << sig << std::endl;
213 break;
214 }
215
216 ValidSigners.push_back(string(sig));
217 }
218 }
219 fclose(pipein);
220 free(buffer);
221
222 // apt-key has a --keyid parameter, but this requires gpg, so we call it without it
223 // and instead check after the fact which keyids where used for verification
224 if (keyIsID == true)
225 {
226 if (Debug == true)
227 std::clog << "GoodSigs needs to be limited to keyid " << key << std::endl;
228 std::vector<std::string>::iterator const foundItr = std::find(ValidSigners.begin(), ValidSigners.end(), key);
229 bool const found = (foundItr != ValidSigners.end());
230 std::copy(GoodSigners.begin(), GoodSigners.end(), std::back_insert_iterator<std::vector<std::string> >(NoPubKeySigners));
231 if (found)
232 {
233 // we look for GOODSIG here as well as an expired sig is a valid sig as well (but not a good one)
234 std::string const goodlongkeyid = "GOODSIG " + key.substr(24, 16);
235 bool const foundGood = std::find(GoodSigners.begin(), GoodSigners.end(), goodlongkeyid) != GoodSigners.end();
236 if (Debug == true)
237 std::clog << "Key " << key << " is valid sig, is " << goodlongkeyid << " also a good one? " << (foundGood ? "yes" : "no") << std::endl;
238 GoodSigners.clear();
239 if (foundGood)
240 {
241 GoodSigners.push_back(goodlongkeyid);
242 NoPubKeySigners.erase(std::remove(NoPubKeySigners.begin(), NoPubKeySigners.end(), goodlongkeyid), NoPubKeySigners.end());
243 }
244 }
245 else
246 GoodSigners.clear();
247 }
248
249 int status;
250 waitpid(pid, &status, 0);
251 if (Debug == true)
252 {
253 ioprintf(std::clog, "gpgv exited with status %i\n", WEXITSTATUS(status));
254 }
255
256 if (WEXITSTATUS(status) == 0)
257 {
258 if (keyIsID)
259 {
260 // gpgv will report success, but we want to enforce a certain keyring
261 // so if we haven't found the key the valid we found is in fact invalid
262 if (GoodSigners.empty())
263 return _("At least one invalid signature was encountered.");
264 }
265 else
266 {
267 if (GoodSigners.empty())
268 return _("Internal error: Good signature, but could not determine key fingerprint?!");
269 }
270 return "";
271 }
272 else if (WEXITSTATUS(status) == 1)
273 return _("At least one invalid signature was encountered.");
274 else if (WEXITSTATUS(status) == 111)
275 return _("Could not execute 'apt-key' to verify signature (is gnupg installed?)");
276 else if (WEXITSTATUS(status) == 112)
277 {
278 // acquire system checks for "NODATA" to generate GPG errors (the others are only warnings)
279 std::string errmsg;
280 //TRANSLATORS: %s is a single techy word like 'NODATA'
281 strprintf(errmsg, _("Clearsigned file isn't valid, got '%s' (does the network require authentication?)"), "NODATA");
282 return errmsg;
283 }
284 else
285 return _("Unknown error executing apt-key");
286}
287
288bool GPGVMethod::URIAcquire(std::string const &Message, FetchItem *Itm)
289{
290 URI const Get = Itm->Uri;
291 string const Path = Get.Host + Get.Path; // To account for relative paths
292 std::string const key = LookupTag(Message, "Signed-By");
293 vector<string> GoodSigners;
294 vector<string> BadSigners;
295 // a worthless signature is a expired or revoked one
296 vector<string> WorthlessSigners;
297 vector<Signer> SoonWorthlessSigners;
298 vector<string> NoPubKeySigners;
299
300 FetchResult Res;
301 Res.Filename = Itm->DestFile;
302 URIStart(Res);
303
304 // Run apt-key on file, extract contents and get the key ID of the signer
305 string msg = VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), key,
306 GoodSigners, BadSigners, WorthlessSigners,
307 SoonWorthlessSigners, NoPubKeySigners);
308
309 // Check if all good signers are soon worthless and warn in that case
310 if (std::all_of(GoodSigners.begin(), GoodSigners.end(), [&](std::string const &good) {
311 return std::any_of(SoonWorthlessSigners.begin(), SoonWorthlessSigners.end(), [&](Signer const &weak) {
312 // VALIDSIG reports a keyid (40 = 24 + 16), GOODSIG is a longid (16) only
313 return weak.key.compare(24, 16, good, strlen("GOODSIG "), 16) == 0;
314 });
315 }))
316 {
317 for (auto const & Signer : SoonWorthlessSigners)
318 // TRANSLATORS: The second %s is the reason and is untranslated for repository owners.
319 Warning(_("Signature by key %s uses weak digest algorithm (%s)"), Signer.key.c_str(), Signer.note.c_str());
320 }
321
322 if (GoodSigners.empty() || !BadSigners.empty() || !NoPubKeySigners.empty())
323 {
324 string errmsg;
325 // In this case, something bad probably happened, so we just go
326 // with what the other method gave us for an error message.
327 if (BadSigners.empty() && WorthlessSigners.empty() && NoPubKeySigners.empty())
328 errmsg = msg;
329 else
330 {
331 if (!BadSigners.empty())
332 {
333 errmsg += _("The following signatures were invalid:\n");
334 for (vector<string>::iterator I = BadSigners.begin();
335 I != BadSigners.end(); ++I)
336 errmsg += (*I + "\n");
337 }
338 if (!WorthlessSigners.empty())
339 {
340 errmsg += _("The following signatures were invalid:\n");
341 for (vector<string>::iterator I = WorthlessSigners.begin();
342 I != WorthlessSigners.end(); ++I)
343 errmsg += (*I + "\n");
344 }
345 if (!NoPubKeySigners.empty())
346 {
347 errmsg += _("The following signatures couldn't be verified because the public key is not available:\n");
348 for (vector<string>::iterator I = NoPubKeySigners.begin();
349 I != NoPubKeySigners.end(); ++I)
350 errmsg += (*I + "\n");
351 }
352 }
353 // this is only fatal if we have no good sigs or if we have at
354 // least one bad signature. good signatures and NoPubKey signatures
355 // happen easily when a file is signed with multiple signatures
356 if(GoodSigners.empty() or !BadSigners.empty())
357 return _error->Error("%s", errmsg.c_str());
358 }
359
360 // Just pass the raw output up, because passing it as a real data
361 // structure is too difficult with the method stuff. We keep it
362 // as three separate vectors for future extensibility.
363 Res.GPGVOutput = GoodSigners;
364 Res.GPGVOutput.insert(Res.GPGVOutput.end(),BadSigners.begin(),BadSigners.end());
365 Res.GPGVOutput.insert(Res.GPGVOutput.end(),NoPubKeySigners.begin(),NoPubKeySigners.end());
366 URIDone(Res);
367
368 if (_config->FindB("Debug::Acquire::gpgv", false))
369 {
370 std::clog << "apt-key succeeded\n";
371 }
372
373 return true;
374}
375
376
377int main()
378{
379 setlocale(LC_ALL, "");
380
381 GPGVMethod Mth;
382
383 return Mth.Run();
384}