use sequoia_openpgp as openpgp; use openpgp::Cert; use openpgp::KeyHandle; use openpgp::KeyID; use openpgp::Result; use openpgp::cert::amalgamation::ValidAmalgamation; use openpgp::packet::Key; use openpgp::parse::Parse; use openpgp::types::RevocationStatus; use super::common::FileOrKeyHandle; use super::common::power_set; use super::common::Sq; use super::common::STANDARD_POLICY; fn check<'a, H>( sq: &Sq, cert_handle: H, to_delete: &[KeyHandle], success: bool) where H: Into, { let cert_handle = cert_handle.into(); eprintln!("Deleting keys from {:?}:", cert_handle); for k in to_delete.iter() { eprintln!(" - {}", k); } // Delete the selection. let result = sq.try_key_subkey_delete(&cert_handle, to_delete, None); let got = match (success, result) { (true, Ok(cert)) => cert, (true, Err(err)) => { panic!("Failed, but should have succeeded: {}", err) } (false, Ok(_)) => { panic!("Succeded, but should have failed") } (false, Err(_)) => return, }; // Make sure we got exactly what we asked for; no // more, no less. eprintln!("Result:"); let mut deletions = 0; for got in got.keys() { eprintln!(" {} {} secret key material", got.fingerprint(), if got.has_secret() { "has" } else { "doesn't have" }); let should_have_deleted = to_delete.iter().find(|kh| kh.aliases(got.key_handle())).is_some(); if should_have_deleted { assert!( ! got.has_secret(), "got secret key material \ for a key we should have deleted ({})", got.fingerprint()); deletions += 1; } else { assert!( got.has_secret(), "didn't get secret key material \ for a key we didn't delete ({})", got.fingerprint()); } } assert_eq!(deletions, to_delete.len()); } #[test] fn sq_key_subkey_delete() -> Result<()> { let sq = Sq::new(); // Generate a key in a file. let (cert, cert_file, _rev_file) = sq.key_generate(&[], &["alice"]); assert!(cert.is_tsk()); // Delete each non-empty subset of keys. eprintln!("Certificate:"); for k in cert.keys() { eprintln!(" {}", k.fingerprint()); } let keys: Vec> = cert.keys() .map(|k| { k.key().clone() }) .collect(); let key_ids = keys.iter().map(|k| k.fingerprint()).collect::>(); for (((i, to_delete), keystore), by_fpr) in power_set(&key_ids).into_iter() .enumerate() .flat_map(|x| [(x.clone(), false), (x.clone(), true)]) .flat_map(|x| [(x.clone(), false), (x.clone(), true)]) { eprintln!("Test #{}, by {}, from {}:", i + 1, if by_fpr { "fingerprint" } else { "key ID" }, if keystore { "the key store".to_string() } else { cert_file.display().to_string() }); let to_delete: Vec = if by_fpr { to_delete.iter() .map(|fpr| KeyHandle::from(fpr)) .collect() } else { to_delete.iter() .map(|fpr| KeyHandle::from(KeyID::from(fpr))) .collect() }; if keystore { // Import it into the key store. sq.key_import(&cert_file); check(&sq, cert.key_handle(), &to_delete, true); } else { check(&sq, &cert_file, &to_delete, true); } } Ok(()) } #[test] fn unbound_subkey() { // Make sure we can't delete an unbound subkey. let sq = Sq::new(); let cert_path = sq.test_data() .join("keys") .join("unbound-subkey.pgp"); let cert = Cert::from_file(&cert_path).expect("can read"); let vc = cert.with_policy(STANDARD_POLICY, sq.now()) .expect("valid cert"); // One subkey should be considered invalid. assert!(vc.keys().count() < cert.keys().count()); let unbound = "E992BF8BA7A27BB4FBB71D973857E47B14874045" .parse::().expect("valid"); check(&sq, &cert_path, &[ unbound ], false); } #[test] fn soft_revoked_subkey() { // Make sure we can delete a soft revoked subkey. let sq = Sq::new(); let cert_path = sq.test_data() .join("keys") .join("soft-revoked-subkey.pgp"); let cert = Cert::from_file(&cert_path).expect("can read"); let vc = cert.with_policy(STANDARD_POLICY, sq.now()) .expect("valid cert"); // Make sure the revoked key is there and is really revoked. let mut revoked = None; for k in vc.keys().subkeys() { if let RevocationStatus::Revoked(_) = k.revocation_status() { assert!(revoked.is_none(), "Only expected a single revoked subkey"); revoked = Some(k.key_handle()); } } let revoked = if let Some(revoked) = revoked { revoked } else { panic!("Expected a revoked subkey, but didn't fine one"); }; check(&sq, &cert_path, &[ revoked ], true); } #[test] fn hard_revoked_subkey() { // Make sure we can delete a hard revoked subkey. let sq = Sq::new(); let cert_path = sq.test_data() .join("keys") .join("hard-revoked-subkey.pgp"); let cert = Cert::from_file(&cert_path).expect("can read"); let vc = cert.with_policy(STANDARD_POLICY, sq.now()) .expect("valid cert"); // Make sure the revoked key is there and is really revoked. let mut revoked = None; for k in vc.keys().subkeys() { if let RevocationStatus::Revoked(_) = k.revocation_status() { assert!(revoked.is_none(), "Only expected a single revoked subkey"); revoked = Some(k.key_handle()); } } let revoked = if let Some(revoked) = revoked { revoked } else { panic!("Expected a revoked subkey, but didn't fine one"); }; check(&sq, &cert_path, &[ revoked ], true); } #[test] fn sha1_subkey() { // Make sure we can delete a subkey that is bound using SHA-1. let sq = Sq::new(); let cert_path = sq.test_data() .join("keys") .join("sha1-subkey-priv.pgp"); let cert = Cert::from_file(&cert_path).expect("can read"); let vc = cert.with_policy(STANDARD_POLICY, sq.now()) .expect("valid cert"); // Make sure the subkey key is there and really uses SHA-1. let valid_subkeys: Vec<_> = vc.keys().subkeys() .map(|ka| ka.fingerprint()) .collect(); let all_subkeys: Vec<_> = cert.keys().subkeys() .map(|ka| ka.fingerprint()) .collect(); assert_eq!(valid_subkeys.len(), 0); assert_eq!(all_subkeys.len(), 1); let subkey = all_subkeys[0].clone(); check(&sq, &cert_path, &[ KeyHandle::from(subkey) ], true); }