]> git.saurik.com Git - apt.git/blob - methods/gpgv.cc
Make the weak signature message less ambigious
[apt.git] / methods / gpgv.cc
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
30 using std::string;
31 using 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
42 struct Digest {
43 enum class State {
44 Untrusted,
45 Weak,
46 Trusted,
47 } state;
48 char name[32];
49 };
50
51 static 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
66 static 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
76 struct Signer {
77 std::string key;
78 std::string note;
79 };
80
81 class 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
98 string 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 if (Debug == true)
193 std::clog << "Got VALIDSIG, key ID: " << sig << std::endl;
194 // Reject weak digest algorithms
195 Digest digest = FindDigest(tokens[7]);
196 switch (digest.state) {
197 case Digest::State::Weak:
198 // Treat them like an expired key: For that a message about expiry
199 // is emitted, a VALIDSIG, but no GOODSIG.
200 SoonWorthlessSigners.push_back({string(sig), digest.name});
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 break;
208 case Digest::State::Trusted:
209 break;
210 }
211
212 ValidSigners.push_back(string(sig));
213 }
214 }
215 fclose(pipein);
216 free(buffer);
217
218 // apt-key has a --keyid parameter, but this requires gpg, so we call it without it
219 // and instead check after the fact which keyids where used for verification
220 if (keyIsID == true)
221 {
222 if (Debug == true)
223 std::clog << "GoodSigs needs to be limited to keyid " << key << std::endl;
224 std::vector<std::string>::iterator const foundItr = std::find(ValidSigners.begin(), ValidSigners.end(), key);
225 bool const found = (foundItr != ValidSigners.end());
226 std::copy(GoodSigners.begin(), GoodSigners.end(), std::back_insert_iterator<std::vector<std::string> >(NoPubKeySigners));
227 if (found)
228 {
229 // we look for GOODSIG here as well as an expired sig is a valid sig as well (but not a good one)
230 std::string const goodlongkeyid = "GOODSIG " + key.substr(24, 16);
231 bool const foundGood = std::find(GoodSigners.begin(), GoodSigners.end(), goodlongkeyid) != GoodSigners.end();
232 if (Debug == true)
233 std::clog << "Key " << key << " is valid sig, is " << goodlongkeyid << " also a good one? " << (foundGood ? "yes" : "no") << std::endl;
234 GoodSigners.clear();
235 if (foundGood)
236 {
237 GoodSigners.push_back(goodlongkeyid);
238 NoPubKeySigners.erase(std::remove(NoPubKeySigners.begin(), NoPubKeySigners.end(), goodlongkeyid), NoPubKeySigners.end());
239 }
240 }
241 else
242 GoodSigners.clear();
243 }
244
245 int status;
246 waitpid(pid, &status, 0);
247 if (Debug == true)
248 {
249 ioprintf(std::clog, "gpgv exited with status %i\n", WEXITSTATUS(status));
250 }
251
252 if (WEXITSTATUS(status) == 0)
253 {
254 if (keyIsID)
255 {
256 // gpgv will report success, but we want to enforce a certain keyring
257 // so if we haven't found the key the valid we found is in fact invalid
258 if (GoodSigners.empty())
259 return _("At least one invalid signature was encountered.");
260 }
261 else
262 {
263 if (GoodSigners.empty())
264 return _("Internal error: Good signature, but could not determine key fingerprint?!");
265 }
266 return "";
267 }
268 else if (WEXITSTATUS(status) == 1)
269 return _("At least one invalid signature was encountered.");
270 else if (WEXITSTATUS(status) == 111)
271 return _("Could not execute 'apt-key' to verify signature (is gnupg installed?)");
272 else if (WEXITSTATUS(status) == 112)
273 {
274 // acquire system checks for "NODATA" to generate GPG errors (the others are only warnings)
275 std::string errmsg;
276 //TRANSLATORS: %s is a single techy word like 'NODATA'
277 strprintf(errmsg, _("Clearsigned file isn't valid, got '%s' (does the network require authentication?)"), "NODATA");
278 return errmsg;
279 }
280 else
281 return _("Unknown error executing apt-key");
282 }
283
284 bool GPGVMethod::URIAcquire(std::string const &Message, FetchItem *Itm)
285 {
286 URI const Get = Itm->Uri;
287 string const Path = Get.Host + Get.Path; // To account for relative paths
288 std::string const key = LookupTag(Message, "Signed-By");
289 vector<string> GoodSigners;
290 vector<string> BadSigners;
291 // a worthless signature is a expired or revoked one
292 vector<string> WorthlessSigners;
293 vector<Signer> SoonWorthlessSigners;
294 vector<string> NoPubKeySigners;
295
296 FetchResult Res;
297 Res.Filename = Itm->DestFile;
298 URIStart(Res);
299
300 // Run apt-key on file, extract contents and get the key ID of the signer
301 string msg = VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), key,
302 GoodSigners, BadSigners, WorthlessSigners,
303 SoonWorthlessSigners, NoPubKeySigners);
304
305
306 // Check if there are any good signers that are not soon worthless
307 std::vector<std::string> NotWarnAboutSigners(GoodSigners);
308 for (auto const & Signer : SoonWorthlessSigners)
309 NotWarnAboutSigners.erase(std::remove(NotWarnAboutSigners.begin(), NotWarnAboutSigners.end(), "GOODSIG " + Signer.key));
310 // If all signers are soon worthless, report them.
311 if (NotWarnAboutSigners.empty()) {
312 for (auto const & Signer : SoonWorthlessSigners)
313 // TRANSLATORS: The second %s is the reason and is untranslated for repository owners.
314 Warning(_("Signature by key %s uses weak digest algorithm (%s)"), Signer.key.c_str(), Signer.note.c_str());
315 }
316
317 if (GoodSigners.empty() || !BadSigners.empty() || !NoPubKeySigners.empty())
318 {
319 string errmsg;
320 // In this case, something bad probably happened, so we just go
321 // with what the other method gave us for an error message.
322 if (BadSigners.empty() && WorthlessSigners.empty() && NoPubKeySigners.empty())
323 errmsg = msg;
324 else
325 {
326 if (!BadSigners.empty())
327 {
328 errmsg += _("The following signatures were invalid:\n");
329 for (vector<string>::iterator I = BadSigners.begin();
330 I != BadSigners.end(); ++I)
331 errmsg += (*I + "\n");
332 }
333 if (!WorthlessSigners.empty())
334 {
335 errmsg += _("The following signatures were invalid:\n");
336 for (vector<string>::iterator I = WorthlessSigners.begin();
337 I != WorthlessSigners.end(); ++I)
338 errmsg += (*I + "\n");
339 }
340 if (!NoPubKeySigners.empty())
341 {
342 errmsg += _("The following signatures couldn't be verified because the public key is not available:\n");
343 for (vector<string>::iterator I = NoPubKeySigners.begin();
344 I != NoPubKeySigners.end(); ++I)
345 errmsg += (*I + "\n");
346 }
347 }
348 // this is only fatal if we have no good sigs or if we have at
349 // least one bad signature. good signatures and NoPubKey signatures
350 // happen easily when a file is signed with multiple signatures
351 if(GoodSigners.empty() or !BadSigners.empty())
352 return _error->Error("%s", errmsg.c_str());
353 }
354
355 // Just pass the raw output up, because passing it as a real data
356 // structure is too difficult with the method stuff. We keep it
357 // as three separate vectors for future extensibility.
358 Res.GPGVOutput = GoodSigners;
359 Res.GPGVOutput.insert(Res.GPGVOutput.end(),BadSigners.begin(),BadSigners.end());
360 Res.GPGVOutput.insert(Res.GPGVOutput.end(),NoPubKeySigners.begin(),NoPubKeySigners.end());
361 URIDone(Res);
362
363 if (_config->FindB("Debug::Acquire::gpgv", false))
364 {
365 std::clog << "apt-key succeeded\n";
366 }
367
368 return true;
369 }
370
371
372 int main()
373 {
374 setlocale(LC_ALL, "");
375
376 GPGVMethod Mth;
377
378 return Mth.Run();
379 }