use anyhow::Result; use sequoia_openpgp as openpgp; use openpgp::{ Packet, cert::prelude::*, packet::signature::SignatureBuilder, parse::Parse, serialize::{ Serialize, SerializeInto, }, types::{ReasonForRevocation, SignatureType}, }; use super::super::*; #[test] #[ntest::timeout(600000)] fn general_purpose_cv25519() -> Result<()> { general_purpose(CipherSuite::Cv25519) } #[test] #[ntest::timeout(600000)] fn general_purpose_rsa2k() -> Result<()> { general_purpose(CipherSuite::RSA2k) } #[test] #[ntest::timeout(600000)] fn general_purpose_rsa3k() -> Result<()> { general_purpose(CipherSuite::RSA3k) } #[test] #[ntest::timeout(600000)] fn general_purpose_rsa4k() -> Result<()> { general_purpose(CipherSuite::RSA4k) } #[test] #[ntest::timeout(600000)] fn general_purpose_p256() -> Result<()> { general_purpose(CipherSuite::P256) } #[test] #[ntest::timeout(600000)] fn general_purpose_p384() -> Result<()> { general_purpose(CipherSuite::P384) } #[test] #[ntest::timeout(600000)] fn general_purpose_p521() -> Result<()> { general_purpose(CipherSuite::P521) } fn general_purpose(cs: CipherSuite) -> Result<()> { test_key(&format!("{:?}", cs), "convert-sk-to-pk", // This is a NOP option. || CertBuilder::general_purpose( cs, Some("Alice Lovelace ")) .set_creation_time(Experiment::now()) .generate() .map(|(cert, _rev)| cert))?; test_key(&format!("{:?}", cs), "import-show", || CertBuilder::general_purpose( cs, Some("Alice Lovelace ")) .set_creation_time(Experiment::now()) .generate() .map(|(cert, _rev)| cert))?; Ok(()) } fn test_key(slug: &str, options: &str, cert_factory: F) -> Result<()> where F: Fn() -> Result, { // We do the experiment twice, once to test the human readable // output, then to test the machine readable output. As importing // material is inherently stateful, we need to do it in two // different experiments. // Human-readable experiment. let mut experiment = make_experiment!(format!("{}-{}-human-readable", options, slug))?; let cert = experiment.artifact( "cert", &cert_factory, |a, f| a.as_tsk().serialize(f), |b| Cert::from_bytes(&b))?; experiment.section("Importing cert..."); let diff = experiment.invoke(&[ "--import", "--import-options", options, &experiment.store("cert", &cert.to_vec()?)?, ])?; diff.assert_success(); diff.assert_limits(0, 0, 0); experiment.section("Importing cert again, unchanged..."); let diff = experiment.invoke(&[ "--import", "--import-options", options, &experiment.store("cert", &cert.to_vec()?)?, ])?; diff.assert_success(); diff.assert_limits(0, 0, 0); experiment.section("Importing key..."); let diff = experiment.invoke(&[ "--import", "--import-options", options, &experiment.store("key", &cert.as_tsk().to_vec()?)?, ])?; diff.assert_success(); // STDOUT: agent gives spurious information to GnuPG because it // doesn't know the key yet. GnuPG marks that with an #, but this // happens only during initial import, so it is more a fluke, // really. Let's see if we can get away with not emulating that. diff.assert_limits(3, 0, 67); experiment.section("Importing key again, unchanged..."); let diff = experiment.invoke(&[ "--import", "--import-options", options, &experiment.store("key", &cert.as_tsk().to_vec()?)?, ])?; diff.assert_success(); diff.assert_limits(0, 0, 67); // Machine-readable experiment. let mut experiment = make_experiment!(format!("{}-{}-machine-readable", options, slug))?; let cert = experiment.artifact( "cert", &cert_factory, |a, f| a.as_tsk().serialize(f), |b| Cert::from_bytes(&b))?; experiment.section("Importing cert..."); let diff = experiment.invoke(&[ "--with-colons", "--import", "--import-options", options, &experiment.store("cert", &cert.to_vec()?)?, ])?; diff.assert_success(); // STDOUT: Curve25519 key length. diff.assert_limits(3, 0, 0); experiment.section("Importing cert again, unchanged..."); let diff = experiment.invoke(&[ "--with-colons", "--import", "--import-options", options, &experiment.store("cert", &cert.to_vec()?)?, ])?; diff.assert_success(); // STDOUT: Curve25519 key length. diff.assert_limits(3, 0, 0); experiment.section("Importing key..."); let diff = experiment.invoke(&[ "--with-colons", "--import", "--import-options", options, &experiment.store("key", &cert.as_tsk().to_vec()?)?, ])?; diff.assert_success(); // STDOUT: agent gives spurious information to GnuPG because it // doesn't know the key yet. GnuPG marks that with an #, but this // happens only during initial import, so it is more a fluke, // really. Let's see if we can get away with not emulating that. // STDOUT: Curve25519 key length. diff.assert_limits(3 + 3, 0, 67); experiment.section("Importing key again, unchanged..."); let diff = experiment.invoke(&[ "--with-colons", "--import", "--import-options", options, &experiment.store("key", &cert.as_tsk().to_vec()?)?, ])?; diff.assert_success(); // STDOUT: Bug in GnuPG: Field 15, serial number of token, should // indicate with a '+' that the secret is available, but GnuPG // doesn't do that. // STDOUT: Curve25519 key length. diff.assert_limits(3 + 3, 0, 67); Ok(()) } #[test] #[ntest::timeout(600000)] fn cert_revocation() -> Result<()> { let mut experiment = make_experiment!()?; let cert = experiment.artifact( "cert", || { let (cert, _) = CertBuilder::new() .set_creation_time(Experiment::now()) .add_userid("Alice Lovelace ") .add_signing_subkey() .generate()?; Ok(cert) }, |a, f| a.as_tsk().serialize(f), |b| Cert::from_bytes(&b))?; let rev = experiment.artifact( "rev", || { // Create and sign a revocation certificate. let mut signer = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; CertRevocationBuilder::new() .set_signature_creation_time(Experiment::now())? .set_reason_for_revocation(ReasonForRevocation::KeyCompromised, b"It was the maid :/")? .build(&mut signer, &cert, None) .map(Packet::from) }, |a, f| a.serialize(f), |b| Packet::from_bytes(&b))?; test_revocation(cert, rev, experiment) } fn test_revocation(cert: Cert, rev: Packet, mut experiment: Experiment) -> Result<()> { experiment.section("Importing revocation without knowing the cert..."); let diff = experiment.invoke(&[ "--import", &experiment.store("rev", &rev.to_vec()?)?, ])?; diff.assert_limits(0, 0, 0); experiment.section("Importing cert..."); let diff = experiment.invoke(&[ "--import", &experiment.store("cert", &cert.to_vec()?)?, ])?; diff.assert_success(); experiment.section("Importing revocation..."); let diff = experiment.invoke(&[ "--import", &experiment.store("rev", &rev.to_vec()?)?, ])?; diff.assert_success(); diff.assert_limits(0, 38 /* no ultimately trusted keys found */, 0); Ok(()) } #[test] #[ntest::timeout(600000)] fn local_sigs() -> Result<()> { let mut experiment = make_experiment!()?; let cert = experiment.artifact( "cert", || { let (cert, _rev) = CertBuilder::general_purpose( None, Some("Alice Lovelace ")) .set_creation_time(Experiment::now()) .generate()?; let (bob, _rev) = CertBuilder::new() .set_creation_time(Experiment::now()) .generate()?; let mut signer = bob.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let uid = cert.userids().next().unwrap().userid(); let sig = SignatureBuilder::new(SignatureType::GenericCertification) .set_signature_creation_time(Experiment::now())? .set_exportable_certification(false)? .sign_userid_binding(&mut signer, cert.primary_key().key(), &uid)?; cert.insert_packets(vec![sig]) }, |a, f| a.as_tsk().serialize(f), |b| Cert::from_bytes(&b))?; experiment.section("Importing cert..."); let diff = experiment.invoke(&[ "--import", &experiment.store("cert", &cert.to_vec()?)?, ])?; diff.assert_success(); experiment.section("Importing cert without local sigs..."); let diff = experiment.invoke(&[ "--import", "--import-options", "no-import-local-sigs", &experiment.store("cert", &cert.to_vec()?)?, ])?; diff.assert_success(); experiment.section("Importing cert with local sigs..."); let diff = experiment.invoke(&[ "--import", "--import-options", "import-local-sigs", &experiment.store("cert", &cert.to_vec()?)?, ])?; diff.assert_success(); Ok(()) }