use std::time::Duration; use sequoia_openpgp as openpgp; use openpgp::Cert; use openpgp::cert::amalgamation::ValidAmalgamation; use openpgp::KeyHandle; use openpgp::Result; use openpgp::parse::Parse; use openpgp::types::RevocationStatus; use super::common::STANDARD_POLICY; use super::common::Sq; use super::common::power_set; use super::common::time_as_string; #[test] fn sq_key_subkey_expire() -> Result<()> { let mut sq = Sq::new(); let (cert, cert_path, _rev_path) = sq.key_generate(&[], &["alice "]); let fpr = cert.fingerprint().to_string(); let updated_path = sq.scratch_file("updated.pgp"); let updated2_path = sq.scratch_file("updated2.pgp"); let keys = cert.keys().map(|k| k.fingerprint()).collect::>(); // Two days go by. sq.tick(2 * 24 * 60 * 60); for expiring in power_set(&keys) { for keystore in [false, true] { let cert_expiring = expiring.contains(&cert.fingerprint()); for (i, fpr) in keys.iter().enumerate() { eprintln!(" {}. {}: {}expiring", i, fpr, if expiring.contains(&fpr) { "" } else { "NOT " }); } if keystore { sq.key_import(&cert_path); } // Change the key to expire in one day. let mut cmd = sq.command(); cmd.args([ "key", "subkey", "expire", "--expiration", "1d", ]); if keystore { cmd.args(["--cert", &fpr ]); } else { cmd .arg("--overwrite") .arg("--file").arg(&cert_path) .arg("--output").arg(&updated_path); } for k in expiring.iter() { cmd.args(["--key", &k.to_string()]); } sq.run(cmd, true); eprintln!("Updated keys at {} to expire in one day:\n{}", sq.now_as_string(), sq.inspect(&updated_path)); let updated = if keystore { eprintln!("Updated certificate to expire in one day:\n{}", sq.inspect(cert.key_handle())); sq.cert_export(cert.key_handle()) } else { eprintln!("Updated certificate to expire in one day:\n{}", sq.inspect(&updated_path)); Cert::from_file(&updated_path).expect("valid cert") }; // It should be alive now. let vc = updated.with_policy(STANDARD_POLICY, sq.now()).expect("valid"); for k in vc.keys() { assert!(k.alive().is_ok()); } // It should be alive in 1 day minus 1 second. let t = sq.now() + Duration::new(24 * 60 * 60 - 1, 0); eprintln!("Checking expiration status at {}", time_as_string(t.into())); let vc = updated.with_policy(STANDARD_POLICY, t).expect("valid"); for k in vc.keys() { assert!(k.alive().is_ok()); } // But in exactly one day, it should be expired. let t = sq.now() + Duration::new(24 * 60 * 60, 0); eprintln!("Checking expiration status at {}", time_as_string(t.into())); let vc = updated.with_policy(STANDARD_POLICY, t).expect("valid"); for k in vc.keys() { assert_eq!( cert_expiring || expiring.contains(&k.fingerprint()), k.alive().is_err(), "{} is {}alive", k.fingerprint(), if k.alive().is_ok() { "" } else { "NOT "}); } // 12 hours go by. Clear the expiration time. sq.tick(12 * 60 * 60); let mut cmd = sq.command(); cmd.args([ "key", "subkey", "expire", "--expiration", "never" ]); if keystore { cmd.args([ "--cert", &fpr ]); } else { cmd .arg("--overwrite") .arg("--file").arg(&updated_path) .arg("--output").arg(&updated2_path); } for k in expiring.iter() { cmd.args(["--key", &k.to_string()]); } sq.run(cmd, true); let updated = if keystore { eprintln!("Updated certificate at {} to never expire:\n{}", sq.now_as_string(), sq.inspect(cert.key_handle())); sq.cert_export(cert.key_handle()) } else { eprintln!("Updated certificate at {} to never expire:\n{}", sq.now_as_string(), sq.inspect(&updated2_path)); Cert::from_file(&updated2_path).expect("valid cert") }; // It should be alive now. let vc = updated.with_policy(STANDARD_POLICY, sq.now()) .expect("valid"); for k in vc.keys() { assert!(k.alive().is_ok()); } // It should be alive in 1 day minus 1 second. let t = sq.now() + Duration::new(24 * 60 * 60 - 1, 0); eprintln!("Checking expiration status at {}", time_as_string(t.into())); let vc = updated.with_policy(STANDARD_POLICY, t).expect("valid"); for k in vc.keys() { eprintln!(" {} expires at {}", k.fingerprint(), if let Some(t) = k.key_expiration_time() { time_as_string(t.into()) } else { "never".to_string() }); if let Err(err) = k.alive() { panic!("{} should be alive, but it's not: {}", k.fingerprint(), err); } } // And in exactly one day... let t = sq.now() + Duration::new(24 * 60 * 60, 0); eprintln!("Checking expiration status at {}", time_as_string(t.into())); let vc = updated.with_policy(STANDARD_POLICY, t).expect("valid"); for k in vc.keys() { eprintln!(" {} expires at {}", k.fingerprint(), if let Some(t) = k.key_expiration_time() { time_as_string(t.into()) } else { "never".to_string() }); if let Err(err) = k.alive() { panic!("{} should be alive, but it's not: {}", k.fingerprint(), err); } } } } Ok(()) } #[test] fn unbound_subkey() { // Make sure we can't extend the expiration time of 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"); // Set it to expire in a day. let updated_path = sq.scratch_file("updated"); let updated = sq.key_subkey_expire(cert_path, &[ unbound ], "1d", None, updated_path.as_path(), false); assert!(updated.is_err()); } #[test] fn soft_revoked_subkey() { // Make sure we can't extend the expiration time of 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.fingerprint()); } } let revoked = if let Some(revoked) = revoked { revoked } else { panic!("Expected a revoked subkey, but didn't fine one"); }; // Set it to expire in a day. let updated_path = sq.scratch_file("updated"); let updated = sq.key_subkey_expire(cert_path, &[ revoked.clone().into() ], "1d", None, updated_path.as_path(), true) .expect("sq key expire should succeed"); let vc = updated.with_policy(STANDARD_POLICY, sq.now()) .expect("valid cert"); let mut good = false; for k in vc.keys() { if k.fingerprint() == revoked { if let RevocationStatus::Revoked(_) = k.revocation_status() { panic!("{} shouldn't be revoked, but is.", revoked); } let expiration = k.key_expiration_time(); assert_eq!(expiration, Some(sq.now() + std::time::Duration::new(24 * 60 * 60, 0))); good = true; break; } } assert!(good); } #[test] fn hard_revoked_subkey() { // Make sure we can't extend the expiration time of 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.fingerprint()); } } let revoked = if let Some(revoked) = revoked { revoked } else { panic!("Expected a revoked subkey, but didn't fine one"); }; // Set it to expire in a day. let updated_path = sq.scratch_file("updated"); let result = sq.key_subkey_expire(cert_path, &[ revoked.clone().into() ], "1d", None, updated_path.as_path(), false); if result.is_ok() { panic!("Updated expiration of hard revoked subkey, but shouldn't have."); } }