use std::collections::HashSet; use sequoia_openpgp as openpgp; use openpgp::Cert; use openpgp::Fingerprint; use openpgp::KeyHandle; use openpgp::Result; use openpgp::cert::amalgamation::ValidAmalgamation; use openpgp::parse::Parse; use openpgp::types::RevocationStatus; use super::common::Sq; use super::common::STANDARD_POLICY; #[test] fn sq_key_delete() -> Result<()> { let sq = Sq::new(); let (cert, cert_file, _rev_file) = sq.key_generate(&[], &["alice"]); assert!(cert.is_tsk()); // Delete all the secret key material from a certificate stored in // a file. Make sure the result contains no secret key material. let updated = sq.key_delete(&cert_file, std::path::PathBuf::from("-").as_path()); assert!(! updated.is_tsk()); // Do the same for a certificate whose secret key material is // managed by the keystore. sq.key_import(cert_file); let cert = sq.key_export(cert.key_handle()); assert!(cert.is_tsk()); let updated = sq.key_delete(cert.key_handle(), None); assert!(! updated.is_tsk()); // If we really stripped the secret key material, then `sq key // export` will fail. assert!(sq.key_export_maybe(cert.key_handle()).is_err()); Ok(()) } #[test] fn unbound_subkey() { // Make sure we don't delete secret key material if there is 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. let bound: HashSet = HashSet::from_iter(vc.keys().map(|ka| ka.fingerprint())); let all: HashSet = HashSet::from_iter(cert.keys().map(|ka| ka.fingerprint())); assert!(bound.len() < all.len()); let result = sq.key_delete(&cert_path, None); for ka in result.keys() { if bound.contains(&ka.fingerprint()) { assert!(! ka.has_secret()); } else { assert!(ka.has_secret()); } } } #[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()); } } if revoked.is_none() { panic!("Expected a revoked subkey, but didn't fine one"); } let updated = sq.key_delete(cert_path, None); assert!(! updated.is_tsk()); } #[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()); } } if revoked.is_none() { panic!("Expected a revoked subkey, but didn't fine one"); } let updated = sq.key_delete(cert_path, None); assert!(! updated.is_tsk()); } #[test] fn sha1_subkey() { // Make sure we can't delete secret key material if there is 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); assert!(sq.try_key_delete(cert_path, None).is_err()); } #[test] fn sha1_subkey_without_secret_key_material() { // Make sure we can delete secret key material in the presence of // a subkey that is bound using SHA-1, but for which there is no // secret key material. 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. eprintln!("Valid keys:"); let valid_keys: Vec<_> = vc.keys() .map(|ka| { let fpr = ka.fingerprint(); eprintln!(" - {}", fpr); fpr }) .collect(); eprintln!("All keys:"); let all_keys: Vec<_> = cert.keys() .map(|ka| { let fpr = ka.fingerprint(); eprintln!(" - {}", fpr); fpr }) .collect(); assert!(valid_keys.len() < all_keys.len()); let mut update = cert_path; for fpr in all_keys.iter() { if ! valid_keys.contains(fpr) { let update2 = sq.scratch_file( Some(&format!("delete-{}", fpr)[..])); sq.key_subkey_delete( update, &[KeyHandle::from(fpr)], update2.as_path()); update = update2; } } let cert = Cert::from_file(&update).expect("can read"); for ka in cert.keys() { if valid_keys.contains(&ka.fingerprint()) { assert!(ka.has_secret()); } else { assert!(! ka.has_secret(), "{} still has secret key material", ka.fingerprint()); } } let result = sq.key_delete(update, None); assert!(! result.is_tsk()); } #[test] fn ambiguous() { // If a key is associated with multiple certificates, then sq key // delete should refuse to delete the secret key material. let sq = Sq::new(); let (alice1, alice1_path, _alice1_rev) = sq.key_generate(&[], &["alice1"]); sq.key_import(&alice1_path); let common_subkey = alice1.keys().subkeys().take(1) .map(|ka| ka.key_handle()) .collect::>(); let (alice2, alice2_path, _alice2_rev) = sq.key_generate(&[], &["alice2"]); sq.key_import(&alice2_path); let alice2_update = sq.scratch_file("alice2-updated"); sq.key_subkey_bind(&[], vec![ &alice2_path ], alice2.fingerprint(), common_subkey.clone(), &alice2_update); sq.key_import(alice2_update); assert!(sq.try_key_delete(alice2.fingerprint(), None).is_err()); // We should be able to delete it using sq key subkey delete. sq.key_subkey_delete(alice1.fingerprint(), &common_subkey, None); sq.key_subkey_delete(alice2.fingerprint(), &common_subkey, None); // And now we should be able to delete the secret key material // associated with the certificate. sq.key_delete(alice2.fingerprint(), None); }