use std::time::Duration; use sequoia_openpgp as openpgp; use openpgp::parse::Parse; use openpgp::Cert; use openpgp::cert::amalgamation::ValidAmalgamation; use openpgp::Result; use openpgp::types::RevocationStatus; use super::common::STANDARD_POLICY; use super::common::Sq; use super::common::time_as_string; #[test] fn sq_key_expire() -> Result<()> { for keystore in [false, true] { let mut sq = Sq::new(); let (cert, cert_path, _rev_path) = sq.key_generate(&[], &["alice "]); let fpr = cert.fingerprint().to_string(); // Two days go by. sq.tick(2 * 24 * 60 * 60); let updated_path = sq.scratch_file("updated.pgp"); let updated2_path = sq.scratch_file("updated2.pgp"); if keystore { sq.key_import(&cert_path); } // Change the key to expire in one day. let mut cmd = sq.command(); cmd.args(["key", "expire", "--expiration", "1d"]); if keystore { cmd.args(["--cert", &fpr ]); } else { cmd .arg("--overwrite") .arg("--file").arg(&cert_path) .arg("--output").arg(&updated_path); } sq.run(cmd, true); 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"); assert!(matches!(vc.alive(), 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"); assert!(matches!(vc.alive(), 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"); assert!(matches!(vc.alive(), Err(_))); // 12 hours go by. Clear the expiration time. sq.tick(12 * 60 * 60); let mut cmd = sq.command(); cmd.args([ "key", "expire", "--expiration", "never" ]); if keystore { cmd.args([ "--cert", &fpr ]); } else { cmd.args([ "--file", &updated_path.to_string_lossy(), "--output", &updated2_path.to_string_lossy(), ]); } sq.run(cmd, true); 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(&updated2_path)); Cert::from_file(&updated2_path).expect("valid cert") }; // It should be alive now. let vc = updated.with_policy(STANDARD_POLICY, None) .expect("valid"); assert!(matches!(vc.alive(), Ok(()))); // It should be alive in 1 day minus 1 second. let vc = updated.with_policy( STANDARD_POLICY, sq.now() + Duration::new(24 * 60 * 60 - 1, 0)) .expect("valid"); assert!(matches!(vc.alive(), Ok(()))); // And in exactly one day... let vc = updated.with_policy( STANDARD_POLICY, sq.now() + Duration::new(24 * 60 * 60, 0)) .expect("valid"); assert!(matches!(vc.alive(), Ok(()))); } Ok(()) } /// Tests changing the expiration time of a key without direct key /// signature. /// /// These kind of keys are generated by GnuPG. #[test] fn sq_key_expire_no_direct_key_sig() -> Result<()> { use openpgp::{ packet::{Any, Signature}, serialize::Serialize, types::SignatureType, }; let mut sq = Sq::new(); let (cert, _cert_path, _rev_path) = sq.key_generate(&["--email", "alice@example.org"], &[]); let fipr = cert.fingerprint().to_string(); // Strip the direct key signature. let mut p = cert.as_tsk().into_packets().collect::>(); p.retain(|p| Any::::downcast_ref(p) .map(|sig| sig.typ() != SignatureType::DirectKey) .unwrap_or(true)); let cert = Cert::from_packets(p.into_iter())?; // Two days go by. sq.tick(2 * 24 * 60 * 60); let cert_path = sq.scratch_file("updated.pgp"); cert.as_tsk().serialize(&mut std::fs::File::create(&cert_path)?)?; sq.key_import(&cert_path); // Change the key to expire in one day. let mut cmd = sq.command(); cmd.args(["key", "expire", "--expiration", "1d", "--cert", &fipr]); sq.run(cmd, true); eprintln!("Updated certificate to expire in one day:\n{}", sq.inspect(cert.key_handle())); let updated = sq.cert_export(cert.key_handle()); // It should be alive now. let vc = updated.with_policy(STANDARD_POLICY, sq.now()).expect("valid"); assert!(matches!(vc.alive(), 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"); assert!(matches!(vc.alive(), 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"); assert!(matches!(vc.alive(), Err(_))); // 12 hours go by. Clear the expiration time. sq.tick(12 * 60 * 60); let mut cmd = sq.command(); cmd.args(["key", "expire", "--expiration", "never", "--cert", &fipr]); sq.run(cmd, true); eprintln!("Updated certificate to expire in one day:\n{}", sq.inspect(cert.key_handle())); let updated = sq.cert_export(cert.key_handle()); // It should be alive now. let vc = updated.with_policy(STANDARD_POLICY, None) .expect("valid"); assert!(matches!(vc.alive(), Ok(()))); // It should be alive in 1 day minus 1 second. let vc = updated.with_policy( STANDARD_POLICY, sq.now() + Duration::new(24 * 60 * 60 - 1, 0)) .expect("valid"); assert!(matches!(vc.alive(), Ok(()))); // And in exactly one day... let vc = updated.with_policy( STANDARD_POLICY, sq.now() + Duration::new(24 * 60 * 60, 0)) .expect("valid"); assert!(matches!(vc.alive(), Ok(()))); Ok(()) } #[test] fn unbound_userid() { // Make sure we can extend the expiration time of a certificate // that includes an unbound user ID (i.e., a user ID without a // self signature). let sq = Sq::new(); let cert_path = sq.test_data() .join("keys") .join("unbound-userid.pgp"); let cert = Cert::from_file(&cert_path).expect("can read"); let vc = cert.with_policy(STANDARD_POLICY, sq.now()) .expect("valid cert"); // It shouldn't be expired yet. assert!(vc.primary_key().key_expiration_time().is_none()); // Set it to expire in a day. let updated_path = sq.scratch_file("updated"); let updated = sq.key_expire(cert_path, "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 expiration = vc.primary_key().key_expiration_time(); assert_eq!(expiration, Some(sq.now() + std::time::Duration::new(24 * 60 * 60, 0))); } #[test] fn revoked_userid() { // Make sure we can extend the expiration time of a certificate // that includes a revoked user ID (i.e., a user ID without a self // signature), and make sure we DON'T make the user ID valid. let sq = Sq::new(); let cert_path = sq.test_data() .join("keys") .join("retired-userid.pgp"); let cert = Cert::from_file(&cert_path).expect("can read"); let vc = cert.with_policy(STANDARD_POLICY, sq.now()) .expect("valid cert"); // It shouldn't be expired yet. assert!(vc.primary_key().key_expiration_time().is_none()); let ua = vc.userids().next().expect("have a user ID"); if let RevocationStatus::Revoked(_) = ua.revocation_status() { } else { panic!("User ID should be revoked, but isn't."); }; // Set it to expire in a day. let updated_path = sq.scratch_file("updated"); let updated = sq.key_expire(cert_path, "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 expiration = vc.primary_key().key_expiration_time(); assert_eq!(expiration, Some(sq.now() + std::time::Duration::new(24 * 60 * 60, 0))); let ua = vc.userids().next().expect("have a user ID"); if let RevocationStatus::Revoked(_) = ua.revocation_status() { } else { panic!("User ID should be revoked, but isn't."); }; }