File: tests.rs

package info (click to toggle)
rust-sequoia-keyring-linter 1.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 536 kB
  • sloc: makefile: 4
file content (398 lines) | stat: -rw-r--r-- 13,698 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
#[cfg(test)]
mod integration {
    use std::path;

    use assert_cmd::Command;
    use predicates::prelude::*;

    use sequoia_openpgp as openpgp;

    use openpgp::Cert;
    use openpgp::Packet;
    use openpgp::parse::Parse;

    fn dir() -> path::PathBuf {
        path::Path::new("tests").join("data")
    }

    // passwords: one '-p' option per element.
    // required_fixes: the number of fixes (= new top-level signatures) needed.
    // expected_fixes: the number of them that we can create.
    fn t(base: &str, prv: Option<&str>, passwords: &[&str],
         required_fixes: usize, expected_fixes: usize)
    {
        assert!(required_fixes >= expected_fixes);

        let dir = dir();
        let mut suffixes = vec![ "pub" ];
        if let Some(prv) = prv {
            suffixes.push(prv);
        }

        for suffix in suffixes.iter() {
            // Lint it.
            let filename = &format!("{}-{}.pgp", base, suffix);
            eprintln!("Linting {}", filename);
            Command::cargo_bin("sq-keyring-linter").unwrap()
                .current_dir(&dir)
                .arg(filename)
                .assert()
                .code(if required_fixes > 0 { 2 } else { 0 });


            // Fix it.
            let filename = &format!("{}-{}.pgp", base, suffix);
            eprint!("Fixing {}", filename);
            if passwords.len() > 0 {
                eprint!(" (passwords: ");
                for (i, p) in passwords.iter().enumerate() {
                    if i > 0 {
                        eprint!(", ");
                    }
                    eprint!("{:?}", p)
                }
                eprint!(")");
            }
            eprintln!(".");

            let expected_fixes = if suffix == &"pub" {
                // We only have public key material: we won't be able
                // to fix anything.
                0
            } else {
                expected_fixes
            };

            let mut cmd = Command::cargo_bin("sq-keyring-linter").unwrap();
            let mut cmd = cmd.current_dir(&dir)
                .args(&[ "--fix", &format!("{}-{}.pgp", base, suffix)]);
            for p in passwords.iter() {
                cmd = cmd.arg("-p").arg(p)
            }
            cmd.assert()
                 // If not everything can be fixed, then --fix's exit code is 3.
                .code(if expected_fixes == required_fixes { 0 } else { 3 })
                .stdout(predicate::function(|output: &[u8]| -> bool {
                    if expected_fixes == 0 {
                        // If there are no fixes, nothing is printed.
                        output == b""
                    } else {
                        // We got a certificate on stdout.  Pass it
                        // through the linter.
                        Command::cargo_bin("sq-keyring-linter").unwrap()
                            .current_dir(&dir)
                            .arg("-")
                            .write_stdin(output)
                            .assert()
                            .code(
                                if expected_fixes == required_fixes {
                                    // Everything should have been fixed.
                                    0
                                } else {
                                    // There are still issues.
                                    2
                                });

                        // Check that the number of new signatures equals
                        // the number of expected new signatures.
                        let orig_sigs: isize =
                            Cert::from_file(dir.clone().join(filename)).unwrap()
                            .into_packets()
                            .map(|p| {
                                if let Packet::Signature(_) = p {
                                    1
                                } else {
                                    0
                                }
                            })
                            .sum();

                        let fixed_sigs: isize = Cert::from_bytes(output)
                            .map(|cert| {
                                cert.into_packets()
                                    .map(|p| {
                                        match p {
                                            Packet::Signature(_) => 1,
                                            Packet::SecretKey(_)
                                                | Packet::SecretSubkey(_) =>
                                                panic!("Secret key material \
                                                        should not be exported!"),
                                            _ => 0,
                                        }
                                    })
                                    .sum()
                            })
                            .map_err(|err| {
                                eprintln!("Parsing fixed certificate: {}", err);
                                0
                            })
                            .unwrap();

                        let fixes = fixed_sigs - orig_sigs;
                        if expected_fixes as isize != fixes {
                            eprintln!("Expected {} fixes, \
                                       found {} additional signatures",
                                      expected_fixes, fixes);
                            false
                        } else {
                            true
                        }
                    }
                }));
        }
    }

    #[test]
    fn known_good() {
        t("gnupg-rsa-normal", Some("priv"), &[], 0, 0);
        t("gnupg-ecc-normal", Some("priv"), &[], 0, 0);
    }

    #[test]
    fn userid_certification() {
        // User ID: SHA256
        // User ID: SHA1
        // Enc Subkey: SHA256
        t("sha1-userid", Some("priv"), &[], 1, 1);
    }

    #[test]
    fn revoked_userid_certification() {
        // A revoked User ID shouldn't be updated.

        // User ID: SHA256
        // User ID: SHA1 (revoked)
        // Enc Subkey: SHA256
        t("sha1-userid-revoked", Some("priv"), &[], 0, 0);
    }

    #[test]
    fn signing_subkey_binding_signature() {
        // User ID: SHA256
        // Enc Subkey: SHA256
        // Sig Subkey: SHA1
        t("sha1-signing-subkey", Some("priv"), &[], 1, 1);
    }

    #[test]
    fn encryption_subkey_binding_signature() {
        // User ID: SHA256
        // Enc Subkey: SHA256
        // Enc Subkey: SHA1
        t("sha1-encryption-subkey", Some("priv"), &[], 1, 1);
    }

    #[test]
    fn subkey_backsig() {
        // User ID: SHA256
        // Enc Subkey: SHA256
        // Sig Subkey: SHA256, backsig: SHA1
        t("sha1-backsig-signing-subkey", Some("priv"), &[], 1, 1);
    }

    #[test]
    fn all_bad() {
        // User ID: SHA1
        // Enc Subkey: SHA1
        t("only-sha1", Some("priv"), &[], 2, 2);

        // We don't fix MD5 signatures.
        //
        // User ID: MD5
        // Enc Subkey: MD5
        t("only-md5", Some("priv"), &[], 2, 0);
    }

    #[test]
    fn passwords() {
        // User ID: SHA1
        // Enc Subkey: SHA1

        // Wrong password.
        t("all-sha1-password-Foobar", Some("priv"), &["foobar"], 2, 0);
        // Right password.
        t("all-sha1-password-Foobar", Some("priv"), &["Foobar"], 2, 2);

        // Try multiple passwords.
        t("all-sha1-password-Foobar", Some("priv"), &["Foobar", "bar"], 2, 2);
        t("all-sha1-password-Foobar", Some("priv"), &["bar", "Foobar"], 2, 2);
    }

    #[test]
    fn multiple_passwords() {
        // The primary is encrypted with foo and the signing subkey
        // with bar.  We need to provide both, because the signing
        // subkey needs its backsig updated.

        // User ID: SHA256
        // Enc Subkey: SHA256
        // Enc Subkey: SHA1
        // Sig Subkey: SHA1

        // We only have the password for the signing subkey: we can't
        // update anything.
        t("multiple-passwords", Some("priv"), &["bar", "Foobar"], 2, 0);
        // We only have the password for the primary key: we can't
        // update the backsig.
        t("multiple-passwords", Some("priv"), &["foo", "Foobar"], 2, 1);
        // We have all passwords: we can fix everything.
        t("multiple-passwords", Some("priv"), &["bar", "Foobar", "foo"], 2, 2);
    }

    #[test]
    fn offline_subkeys() {
        // The User ID, the encryption subkey, and the signing subkey
        // all need new signatures.  With just the primary key, we are
        // able to create two of the three required signatures.

        // User ID: SHA1
        // Enc Subkey: SHA1
        // Sig Subkey: SHA1

        // We can't update the backsig.
        t("sha1-offline-subkeys", Some("offline"), &[], 3, 2);
        // We can fix everything.
        t("sha1-offline-subkeys", Some("priv"), &[], 3, 3);
    }

    #[test]
    fn sha1_authentication_subkey() {
        // User ID: SHA1
        // Enc Subkey: SHA1
        // Auth Subkey: SHA1
        t("sha1-authentication-subkey", Some("priv"), &[], 3, 3);
    }

    #[test]
    fn authentication_subkey() {
        // An authentication subkey doesn't require a backsig.  Make
        // sure we don't flag a missing backsig as an error.

        // User ID: SHA512
        // Enc Subkey: SHA512
        // Auth Subkey: SHA512
        t("authentication-subkey", Some("priv"), &[], 0, 0);
    }

    #[test]
    fn sha1_userid_sha256_subkeys() {
        // The User ID is protected with a SHA-1 signature, but two
        // subkeys are protected with SHA256.  Make sure the subkeys
        // don't get new binding signatures.

        // User ID: SHA1
        // Enc Subkey: SHA1
        // Sig Subkey: SHA256
        // Enc Subkey: SHA256
        t("sha1-userid-sha256-subkeys", Some("priv"), &[], 2, 2);
    }

    #[test]
    fn no_backsig() {
        // If a key doesn't have a backsig and needs one, it won't be
        // detected as an issue, because it is not valid under
        // SHA1+SP.  That's okay.

        // User ID: SHA512
        // Sig Subkey: SHA512, no backsig.
        t("no-backsig", Some("priv"), &[], 0, 0);
    }

    #[test]
    fn sha512_self_sig_sha1_revocation() {
        // Under the standard policy, SHA1 revocations are considered
        // bad.  We assume that SP+SHA-1 is strictly more liberal than
        // SP (i.e., it accepts at least everything that SP accepts).

        // User ID: SHA512, SHA-1 revocation.
        t("sha512-self-sig-sha1-revocation", None, &[], 0, 0);
    }

    #[test]
    fn revoked_certificate() {
        // The certificate is only valid under SP+SHA1, and the
        // revocation certificate uses SHA1.  There is no need to
        // upgrade the certificate or the revocation certificate.

        // User ID: SHA1
        // Enc Subkey: SHA1
        // Revocation: SHA1
        t("sha1-cert-sha1-revocation", Some("priv"), &[], 0, 0);

        // The certificate is only valid under SP+SHA1, and the
        // revocation certificate uses SHA256.  There is no need to
        // upgrade the certificate or the revocation certificate.

        // User ID: SHA1
        // Enc Subkey: SHA1
        // Revocation: SHA256
        t("sha1-cert-sha256-revocation", Some("priv"), &[], 0, 0);

        // The certificate is valid under SP (the signatures use
        // SHA512), but there are two revocation certificates that use
        // SHA1.  Make sure we upgrade them.

        // User ID: SHA512
        // Enc Subkey: SHA512
        // Revocation: SHA1
        // Revocation: SHA1
        t("sha512-cert-sha1-revocation", Some("priv"), &[], 2, 2);

        // The certificate is valid under SP (the signatures use
        // SHA256), and it is revoked using a SHA256 revocation
        // certificate, which is also valid under SP.  It also has a
        // SHA-1 protected signing subkey.  Because the certificate is
        // revoked and the revocation certificate uses SHA256, we
        // don't need to fix the SHA-1 signature.  Make sure we don't.

        // User ID: SHA256
        // Enc Subkey: SHA256
        // Sig Subkey: SHA1
        // Revocation: SHA256
        t("sha256-cert-sha256-revocation", Some("priv"), &[], 0, 0);
    }

    #[test]
    fn expired_certificates() {
        // User ID: SHA256 (expired)
        // Enc Subkey: SHA256
        t("sha256-expired", Some("priv"), &[], 0, 0);

        // User ID: SHA1 (expired)
        // Enc Subkey: SHA1
        t("sha1-expired", Some("priv"), &[], 0, 0);

        // User ID: SHA256 (old, expired), SHA1 (new, live)
        // Enc Subkey: SHA256
        t("sha256-expired-sha1-live", Some("priv"), &[], 1, 1);
    }

    #[test]
    fn list_keys() {
        Command::cargo_bin("sq-keyring-linter").unwrap()
            .current_dir(&dir())
            .args(&[
                "--list-keys",
                // 94F19D3CB5656E0BC3977C09A8AC5ACC2FB87104
                "sha1-userid-pub.pgp",
                // 55EF7181C288067AE189FF12F5A5CD01D8070917
                "gnupg-rsa-normal-pub.pgp"
            ])
            .assert()
            // If there are issues, the exit code is 2.
            .code(2)
            .stdout(predicate::eq("94F19D3CB5656E0BC3977C09A8AC5ACC2FB87104\n"));
    }

    #[test]
    fn signature() {
        Command::cargo_bin("sq-keyring-linter").unwrap()
            .current_dir(&dir())
            .args(&[
                "msg.sig",
            ])
            .assert()
            // If there are issues, the exit code is 1.
            .code(1);
    }
}