]> git.saurik.com Git - apt.git/blame - methods/gpgv.cc
properly check for "all good sigs are weak"
[apt.git] / methods / gpgv.cc
CommitLineData
ea542140
DK
1#include <config.h>
2
b3d44315 3#include <apt-pkg/acquire-method.h>
472ff00e 4#include <apt-pkg/configuration.h>
453b82a3 5#include <apt-pkg/error.h>
2f5b6151 6#include <apt-pkg/gpgv.h>
453b82a3 7#include <apt-pkg/strutl.h>
3927c6da 8#include <apt-pkg/fileutl.h>
23e64f6d 9#include "aptmethod.h"
b3d44315 10
453b82a3 11#include <ctype.h>
b3d44315 12#include <errno.h>
453b82a3
DK
13#include <stddef.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
b3d44315 17#include <sys/wait.h>
453b82a3 18#include <unistd.h>
4e03c47d 19
d9105124 20#include <array>
4e03c47d 21#include <algorithm>
d9105124
JAK
22#include <sstream>
23#include <iterator>
b3d44315 24#include <iostream>
453b82a3 25#include <string>
f5a3d009
DK
26#include <vector>
27
ea542140
DK
28#include <apti18n.h>
29
8f3ba4e8
DK
30using std::string;
31using std::vector;
32
b3d44315
MV
33#define GNUPGPREFIX "[GNUPG:]"
34#define GNUPGBADSIG "[GNUPG:] BADSIG"
35#define GNUPGNOPUBKEY "[GNUPG:] NO_PUBKEY"
36#define GNUPGVALIDSIG "[GNUPG:] VALIDSIG"
c5d8878d
MV
37#define GNUPGGOODSIG "[GNUPG:] GOODSIG"
38#define GNUPGKEYEXPIRED "[GNUPG:] KEYEXPIRED"
39#define GNUPGREVKEYSIG "[GNUPG:] REVKEYSIG"
2abb68b7 40#define GNUPGNODATA "[GNUPG:] NODATA"
b3d44315 41
349c5c89
JAK
42struct Digest {
43 enum class State {
44 Untrusted,
45 Weak,
46 Trusted,
47 } state;
48 char name[32];
d9105124
JAK
49};
50
349c5c89
JAK
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;
07ea3af0
JAK
79};
80
23e64f6d 81class GPGVMethod : public aptMethod
b3d44315
MV
82{
83 private:
da9ed163 84 string VerifyGetSigners(const char *file, const char *outfile,
b0d40854
DK
85 std::string const &key,
86 vector<string> &GoodSigners,
c5d8878d
MV
87 vector<string> &BadSigners,
88 vector<string> &WorthlessSigners,
349c5c89 89 vector<Signer> &SoonWorthlessSigners,
b3d44315 90 vector<string> &NoPubKeySigners);
b3d44315 91 protected:
3b302846 92 virtual bool URIAcquire(std::string const &Message, FetchItem *Itm) APT_OVERRIDE;
b3d44315
MV
93 public:
94
23e64f6d 95 GPGVMethod() : aptMethod("gpgv","1.0",SingleInstance | SendConfig) {};
b3d44315
MV
96};
97
da9ed163 98string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
b0d40854 99 std::string const &key,
b3d44315
MV
100 vector<string> &GoodSigners,
101 vector<string> &BadSigners,
c5d8878d 102 vector<string> &WorthlessSigners,
349c5c89 103 vector<Signer> &SoonWorthlessSigners,
b3d44315
MV
104 vector<string> &NoPubKeySigners)
105{
46e39c8e 106 bool const Debug = _config->FindB("Debug::Acquire::gpgv", false);
da9ed163 107
46e39c8e
MV
108 if (Debug == true)
109 std::clog << "inside VerifyGetSigners" << std::endl;
110
b3d44315 111 int fd[2];
4e03c47d 112 bool const keyIsID = (key.empty() == false && key[0] != '/');
46e39c8e 113
b3d44315 114 if (pipe(fd) < 0)
b3d44315 115 return "Couldn't create pipe";
b3d44315 116
cf440fac 117 pid_t pid = fork();
b3d44315 118 if (pid < 0)
da9ed163 119 return string("Couldn't spawn new process") + strerror(errno);
b3d44315 120 else if (pid == 0)
4e03c47d 121 ExecGPGV(outfile, file, 3, fd, (keyIsID ? "" : key));
b3d44315
MV
122 close(fd[1]);
123
cf440fac
DK
124 FILE *pipein = fdopen(fd[0], "r");
125
b39bb552 126 // Loop over the output of apt-key (which really is gnupg), and check the signatures.
4e03c47d 127 std::vector<std::string> ValidSigners;
bf6ac7ca
DK
128 size_t buffersize = 0;
129 char *buffer = NULL;
b3d44315
MV
130 while (1)
131 {
bf6ac7ca
DK
132 if (getline(&buffer, &buffersize, pipein) == -1)
133 break;
46e39c8e
MV
134 if (Debug == true)
135 std::clog << "Read: " << buffer << std::endl;
b3d44315
MV
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 {
46e39c8e
MV
143 if (Debug == true)
144 std::clog << "Got BADSIG! " << std::endl;
b3d44315
MV
145 BadSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
146 }
4e03c47d 147 else if (strncmp(buffer, GNUPGNOPUBKEY, sizeof(GNUPGNOPUBKEY)-1) == 0)
b3d44315 148 {
46e39c8e
MV
149 if (Debug == true)
150 std::clog << "Got NO_PUBKEY " << std::endl;
b3d44315
MV
151 NoPubKeySigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
152 }
4e03c47d 153 else if (strncmp(buffer, GNUPGNODATA, sizeof(GNUPGBADSIG)-1) == 0)
2abb68b7 154 {
46e39c8e
MV
155 if (Debug == true)
156 std::clog << "Got NODATA! " << std::endl;
2abb68b7
MV
157 BadSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
158 }
4e03c47d 159 else if (strncmp(buffer, GNUPGKEYEXPIRED, sizeof(GNUPGKEYEXPIRED)-1) == 0)
c5d8878d 160 {
46e39c8e
MV
161 if (Debug == true)
162 std::clog << "Got KEYEXPIRED! " << std::endl;
c5d8878d
MV
163 WorthlessSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
164 }
4e03c47d 165 else if (strncmp(buffer, GNUPGREVKEYSIG, sizeof(GNUPGREVKEYSIG)-1) == 0)
c5d8878d 166 {
46e39c8e
MV
167 if (Debug == true)
168 std::clog << "Got REVKEYSIG! " << std::endl;
c5d8878d
MV
169 WorthlessSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
170 }
4e03c47d 171 else if (strncmp(buffer, GNUPGGOODSIG, sizeof(GNUPGGOODSIG)-1) == 0)
b3d44315
MV
172 {
173 char *sig = buffer + sizeof(GNUPGPREFIX);
c5d8878d 174 char *p = sig + sizeof("GOODSIG");
b3d44315
MV
175 while (*p && isxdigit(*p))
176 p++;
177 *p = 0;
46e39c8e
MV
178 if (Debug == true)
179 std::clog << "Got GOODSIG, key ID:" << sig << std::endl;
b3d44315
MV
180 GoodSigners.push_back(string(sig));
181 }
4e03c47d
DK
182 else if (strncmp(buffer, GNUPGVALIDSIG, sizeof(GNUPGVALIDSIG)-1) == 0)
183 {
184 char *sig = buffer + sizeof(GNUPGVALIDSIG);
d9105124
JAK
185 std::istringstream iss((string(sig)));
186 vector<string> tokens{std::istream_iterator<string>{iss},
187 std::istream_iterator<string>{}};
4e03c47d
DK
188 char *p = sig;
189 while (*p && isxdigit(*p))
190 p++;
191 *p = 0;
d9105124 192 // Reject weak digest algorithms
349c5c89
JAK
193 Digest digest = FindDigest(tokens[7]);
194 switch (digest.state) {
195 case Digest::State::Weak:
07ea3af0
JAK
196 // Treat them like an expired key: For that a message about expiry
197 // is emitted, a VALIDSIG, but no GOODSIG.
349c5c89 198 SoonWorthlessSigners.push_back({string(sig), digest.name});
8fa99570
DK
199 if (Debug == true)
200 std::clog << "Got weak VALIDSIG, key ID: " << sig << std::endl;
349c5c89
JAK
201 break;
202 case Digest::State::Untrusted:
08fd77e8
JAK
203 // Treat them like an expired key: For that a message about expiry
204 // is emitted, a VALIDSIG, but no GOODSIG.
349c5c89 205 WorthlessSigners.push_back(string(sig));
08fd77e8 206 GoodSigners.erase(std::remove(GoodSigners.begin(), GoodSigners.end(), string(sig)));
8fa99570
DK
207 if (Debug == true)
208 std::clog << "Got untrusted VALIDSIG, key ID: " << sig << std::endl;
349c5c89
JAK
209 break;
210 case Digest::State::Trusted:
8fa99570
DK
211 if (Debug == true)
212 std::clog << "Got trusted VALIDSIG, key ID: " << sig << std::endl;
349c5c89 213 break;
08fd77e8 214 }
d9105124 215
4e03c47d
DK
216 ValidSigners.push_back(string(sig));
217 }
b3d44315
MV
218 }
219 fclose(pipein);
1b7bf822 220 free(buffer);
b3d44315 221
4e03c47d
DK
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
cf440fac 249 int status;
b3d44315 250 waitpid(pid, &status, 0);
46e39c8e 251 if (Debug == true)
b3d44315 252 {
2737f28a 253 ioprintf(std::clog, "gpgv exited with status %i\n", WEXITSTATUS(status));
b3d44315
MV
254 }
255
256 if (WEXITSTATUS(status) == 0)
257 {
4e03c47d
DK
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 }
da9ed163 270 return "";
b3d44315
MV
271 }
272 else if (WEXITSTATUS(status) == 1)
339690e4 273 return _("At least one invalid signature was encountered.");
b3d44315 274 else if (WEXITSTATUS(status) == 111)
b39bb552 275 return _("Could not execute 'apt-key' to verify signature (is gnupg installed?)");
ae99ce2e 276 else if (WEXITSTATUS(status) == 112)
b3d44315 277 {
ae99ce2e
DK
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;
b3d44315
MV
283 }
284 else
b39bb552 285 return _("Unknown error executing apt-key");
b3d44315
MV
286}
287
b0d40854 288bool GPGVMethod::URIAcquire(std::string const &Message, FetchItem *Itm)
b3d44315 289{
b0d40854
DK
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");
b3d44315
MV
293 vector<string> GoodSigners;
294 vector<string> BadSigners;
c5d8878d
MV
295 // a worthless signature is a expired or revoked one
296 vector<string> WorthlessSigners;
349c5c89 297 vector<Signer> SoonWorthlessSigners;
b3d44315
MV
298 vector<string> NoPubKeySigners;
299
300 FetchResult Res;
301 Res.Filename = Itm->DestFile;
302 URIStart(Res);
303
b39bb552 304 // Run apt-key on file, extract contents and get the key ID of the signer
b0d40854 305 string msg = VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(), key,
c5d8878d 306 GoodSigners, BadSigners, WorthlessSigners,
07ea3af0
JAK
307 SoonWorthlessSigners, NoPubKeySigners);
308
8fa99570
DK
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 {
07ea3af0
JAK
317 for (auto const & Signer : SoonWorthlessSigners)
318 // TRANSLATORS: The second %s is the reason and is untranslated for repository owners.
5f060c27 319 Warning(_("Signature by key %s uses weak digest algorithm (%s)"), Signer.key.c_str(), Signer.note.c_str());
07ea3af0
JAK
320 }
321
b3d44315
MV
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.
c5d8878d 327 if (BadSigners.empty() && WorthlessSigners.empty() && NoPubKeySigners.empty())
b3d44315
MV
328 errmsg = msg;
329 else
330 {
331 if (!BadSigners.empty())
332 {
339690e4 333 errmsg += _("The following signatures were invalid:\n");
b3d44315 334 for (vector<string>::iterator I = BadSigners.begin();
f7f0d6c7 335 I != BadSigners.end(); ++I)
b3d44315
MV
336 errmsg += (*I + "\n");
337 }
c5d8878d
MV
338 if (!WorthlessSigners.empty())
339 {
340 errmsg += _("The following signatures were invalid:\n");
341 for (vector<string>::iterator I = WorthlessSigners.begin();
f7f0d6c7 342 I != WorthlessSigners.end(); ++I)
c5d8878d
MV
343 errmsg += (*I + "\n");
344 }
b3d44315
MV
345 if (!NoPubKeySigners.empty())
346 {
339690e4 347 errmsg += _("The following signatures couldn't be verified because the public key is not available:\n");
b3d44315 348 for (vector<string>::iterator I = NoPubKeySigners.begin();
f7f0d6c7 349 I != NoPubKeySigners.end(); ++I)
b3d44315
MV
350 errmsg += (*I + "\n");
351 }
352 }
ce424cd4
MV
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())
f23153d0 357 return _error->Error("%s", errmsg.c_str());
b3d44315
MV
358 }
359
b3d44315
MV
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 {
b39bb552 370 std::clog << "apt-key succeeded\n";
b3d44315
MV
371 }
372
373 return true;
374}
375
376
377int main()
378{
339690e4 379 setlocale(LC_ALL, "");
3927c6da 380
b3d44315
MV
381 GPGVMethod Mth;
382
383 return Mth.Run();
384}