summaryrefslogtreecommitdiffstats
path: root/openpgp/src/cert/builder.rs
diff options
context:
space:
mode:
Diffstat (limited to 'openpgp/src/cert/builder.rs')
-rw-r--r--openpgp/src/cert/builder.rs665
1 files changed, 665 insertions, 0 deletions
diff --git a/openpgp/src/cert/builder.rs b/openpgp/src/cert/builder.rs
new file mode 100644
index 00000000..fe8b6ccf
--- /dev/null
+++ b/openpgp/src/cert/builder.rs
@@ -0,0 +1,665 @@
+use std::time;
+
+use crate::packet;
+use crate::packet::{
+ key,
+ Key,
+ key::Key4,
+};
+use crate::Result;
+use crate::packet::Signature;
+use crate::packet::signature;
+use crate::Cert;
+use crate::cert::CertRevocationBuilder;
+use crate::Error;
+use crate::conversions::Time;
+use crate::crypto::Password;
+use crate::autocrypt::Autocrypt;
+use crate::types::{
+ Features,
+ HashAlgorithm,
+ KeyFlags,
+ SignatureType,
+ SymmetricAlgorithm,
+};
+
+/// Groups symmetric and asymmetric algorithms
+#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug)]
+pub enum CipherSuite {
+ /// EdDSA and ECDH over Curve25519 with SHA512 and AES256
+ Cv25519,
+ /// 3072 bit RSA with SHA512 and AES256
+ RSA3k,
+ /// EdDSA and ECDH over NIST P-256 with SHA256 and AES256
+ P256,
+ /// EdDSA and ECDH over NIST P-384 with SHA384 and AES256
+ P384,
+ /// EdDSA and ECDH over NIST P-521 with SHA512 and AES256
+ P521,
+ /// 2048 bit RSA with SHA512 and AES256
+ RSA2k,
+ /// 4096 bit RSA with SHA512 and AES256
+ RSA4k,
+}
+
+impl Default for CipherSuite {
+ fn default() -> Self {
+ CipherSuite::Cv25519
+ }
+}
+
+impl CipherSuite {
+ fn generate_key<R>(self, flags: &KeyFlags)
+ -> Result<Key<key::SecretParts, R>>
+ where R: key::KeyRole
+ {
+ use crate::types::Curve;
+
+ match self {
+ CipherSuite::RSA2k =>
+ Key4::generate_rsa(2048),
+ CipherSuite::RSA3k =>
+ Key4::generate_rsa(3072),
+ CipherSuite::RSA4k =>
+ Key4::generate_rsa(4096),
+ CipherSuite::Cv25519 | CipherSuite::P256 |
+ CipherSuite::P384 | CipherSuite::P521 => {
+ let sign = flags.can_certify() || flags.can_sign()
+ || flags.can_authenticate();
+ let encrypt = flags.can_encrypt_for_transport()
+ || flags.can_encrypt_at_rest();
+ let curve = match self {
+ CipherSuite::Cv25519 if sign => Curve::Ed25519,
+ CipherSuite::Cv25519 if encrypt => Curve::Cv25519,
+ CipherSuite::Cv25519 => {
+ return Err(Error::InvalidOperation(
+ "No key flags set".into())
+ .into());
+ }
+ CipherSuite::P256 => Curve::NistP256,
+ CipherSuite::P384 => Curve::NistP384,
+ CipherSuite::P521 => Curve::NistP521,
+ _ => unreachable!(),
+ };
+
+ match (sign, encrypt) {
+ (true, false) => Key4::generate_ecc(true, curve),
+ (false, true) => Key4::generate_ecc(false, curve),
+ (true, true) =>
+ Err(Error::InvalidOperation(
+ "Can't use key for encryption and signing".into())
+ .into()),
+ (false, false) =>
+ Err(Error::InvalidOperation(
+ "No key flags set".into())
+ .into()),
+ }
+ },
+ }.map(|key| key.into())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct KeyBlueprint {
+ flags: KeyFlags,
+ expiration: Option<time::Duration>,
+}
+
+/// Simplifies generation of Keys.
+///
+/// Builder to generate complex Cert hierarchies with multiple user IDs.
+#[derive(Clone, Debug)]
+pub struct CertBuilder {
+ ciphersuite: CipherSuite,
+ primary: KeyBlueprint,
+ subkeys: Vec<KeyBlueprint>,
+ userids: Vec<packet::UserID>,
+ user_attributes: Vec<packet::UserAttribute>,
+ password: Option<Password>,
+}
+
+impl CertBuilder {
+ /// Returns a new CertBuilder.
+ ///
+ /// The returned CertBuilder is setup to only create a
+ /// certification-capable primary key using the default cipher
+ /// suite. You'll almost certainly want to add subkeys (using
+ /// `CertBuilder::add_signing_subkey`, or
+ /// `CertBuilder::add_encryption_subkey`, for instance), and user
+ /// ids (using `CertBuilder::add_userid`).
+ pub fn new() -> Self {
+ CertBuilder{
+ ciphersuite: CipherSuite::default(),
+ primary: KeyBlueprint{
+ flags: KeyFlags::default().set_certify(true),
+ expiration: None,
+ },
+ subkeys: vec![],
+ userids: vec![],
+ user_attributes: vec![],
+ password: None,
+ }
+ }
+
+ /// Generates a general-purpose key.
+ ///
+ /// The key's primary key is certification- and signature-capable.
+ /// The key has one subkey, an encryption-capable subkey.
+ pub fn general_purpose<C, U>(ciphersuite: C, userids: Option<U>) -> Self
+ where C: Into<Option<CipherSuite>>,
+ U: Into<packet::UserID>
+ {
+ CertBuilder {
+ ciphersuite: ciphersuite.into().unwrap_or(Default::default()),
+ primary: KeyBlueprint {
+ flags: KeyFlags::default()
+ .set_certify(true)
+ .set_sign(true),
+ expiration: Some(
+ time::Duration::new(3 * 52 * 7 * 24 * 60 * 60, 0)),
+ },
+ subkeys: vec![
+ KeyBlueprint {
+ flags: KeyFlags::default()
+ .set_encrypt_for_transport(true)
+ .set_encrypt_at_rest(true),
+ expiration: None,
+ }
+ ],
+ userids: userids.into_iter().map(|x| x.into()).collect(),
+ user_attributes: vec![],
+ password: None,
+ }
+ }
+
+ /// Generates a key compliant to
+ /// [Autocrypt](https://autocrypt.org/).
+ ///
+ /// If no version is given the latest one is used.
+ ///
+ /// The autocrypt specification requires a UserID. However,
+ /// because it can be useful to add the UserID later, it is
+ /// permitted to be none.
+ pub fn autocrypt<'a, V, U>(version: V, userid: Option<U>)
+ -> Self
+ where V: Into<Option<Autocrypt>>,
+ U: Into<packet::UserID>
+ {
+ let builder = CertBuilder{
+ ciphersuite: match version.into().unwrap_or(Default::default()) {
+ Autocrypt::V1 => CipherSuite::RSA3k,
+ Autocrypt::V1_1 => CipherSuite::Cv25519,
+ },
+ primary: KeyBlueprint {
+ flags: KeyFlags::default()
+ .set_certify(true)
+ .set_sign(true),
+ expiration: Some(
+ time::Duration::new(3 * 52 * 7 * 24 * 60 * 60, 0)),
+ },
+ subkeys: vec![
+ KeyBlueprint {
+ flags: KeyFlags::default()
+ .set_encrypt_for_transport(true)
+ .set_encrypt_at_rest(true),
+ expiration: None,
+ }
+ ],
+ userids: vec![],
+ user_attributes: vec![],
+ password: None,
+ };
+
+ if let Some(userid) = userid {
+ builder.add_userid(userid.into())
+ } else {
+ builder
+ }
+ }
+
+ /// Sets the encryption and signature algorithms for primary and all subkeys.
+ pub fn set_cipher_suite(mut self, cs: CipherSuite) -> Self {
+ self.ciphersuite = cs;
+ self
+ }
+
+ /// Adds a new user ID. The first user ID added will be the primary user ID.
+ pub fn add_userid<'a, U>(mut self, uid: U) -> Self
+ where U: Into<packet::UserID>
+ {
+ self.userids.push(uid.into());
+ self
+ }
+
+ /// Adds a new user attribute.
+ pub fn add_user_attribute<'a, U>(mut self, ua: U) -> Self
+ where U: Into<packet::UserAttribute>
+ {
+ self.user_attributes.push(ua.into());
+ self
+ }
+
+ /// Adds a signing capable subkey.
+ pub fn add_signing_subkey(self) -> Self {
+ self.add_subkey(KeyFlags::default().set_sign(true), None)
+ }
+
+ /// Adds an encryption capable subkey.
+ pub fn add_encryption_subkey(self) -> Self {
+ self.add_subkey(KeyFlags::default()
+ .set_encrypt_for_transport(true)
+ .set_encrypt_at_rest(true), None)
+ }
+
+ /// Adds an certification capable subkey.
+ pub fn add_certification_subkey(self) -> Self {
+ self.add_subkey(KeyFlags::default().set_certify(true), None)
+ }
+
+ /// Adds an authentication capable subkey.
+ pub fn add_authentication_subkey(self) -> Self {
+ self.add_subkey(KeyFlags::default().set_authenticate(true), None)
+ }
+
+ /// Adds a custom subkey.
+ ///
+ /// If `expiration` is `None`, the subkey uses the same expiration
+ /// time as the primary key.
+ pub fn add_subkey<T>(mut self, flags: KeyFlags, expiration: T) -> Self
+ where T: Into<Option<time::Duration>>
+ {
+ self.subkeys.push(KeyBlueprint {
+ flags: flags,
+ expiration: expiration.into(),
+ });
+ self
+ }
+
+ /// Sets the capabilities of the primary key. The function automatically
+ /// makes the primary key certification capable if subkeys are added.
+ pub fn primary_keyflags(mut self, flags: KeyFlags) -> Self {
+ self.primary.flags = flags;
+ self
+ }
+
+ /// Sets a password to encrypt the secret keys with.
+ pub fn set_password(mut self, password: Option<Password>) -> Self {
+ self.password = password;
+ self
+ }
+
+ /// Sets the expiration time.
+ ///
+ /// A value of None means never.
+ pub fn set_expiration<T>(mut self, expiration: T) -> Self
+ where T: Into<Option<time::Duration>>
+ {
+ self.primary.expiration = expiration.into();
+ self
+ }
+
+ /// Generates the actual Cert.
+ pub fn generate(mut self) -> Result<(Cert, Signature)> {
+ use crate::{PacketPile, Packet};
+ use crate::types::ReasonForRevocation;
+
+ let mut packets = Vec::<Packet>::with_capacity(
+ 1 + 1 + self.subkeys.len() + self.userids.len()
+ + self.user_attributes.len());
+
+ // make sure the primary key can sign subkeys
+ if !self.subkeys.is_empty() {
+ self.primary.flags = self.primary.flags.set_certify(true);
+ }
+
+ // Generate & and self-sign primary key.
+ let (primary, sig) = self.primary_key()?;
+ let mut signer = primary.clone().mark_parts_secret().unwrap()
+ .into_keypair().unwrap();
+
+ packets.push(Packet::PublicKey({
+ let mut primary = primary.clone();
+ if let Some(ref password) = self.password {
+ primary.secret_mut().unwrap().encrypt_in_place(password)?;
+ }
+ primary
+ }));
+ packets.push(sig.clone().into());
+
+ let mut cert =
+ Cert::from_packet_pile(PacketPile::from(packets))?;
+
+ // Sign UserIDs.
+ for uid in self.userids.into_iter() {
+ let builder = signature::Builder::from(sig.clone())
+ .set_type(SignatureType::PositiveCertificate)
+ // GnuPG wants at least a 512-bit hash for P521 keys.
+ .set_hash_algo(HashAlgorithm::SHA512);
+ let signature = uid.bind(&mut signer, &cert, builder, None)?;
+ cert = cert.merge_packets(vec![uid.into(), signature.into()])?;
+ }
+
+ // Sign UserAttributes.
+ for ua in self.user_attributes.into_iter() {
+ let builder = signature::Builder::from(sig.clone())
+ .set_type(SignatureType::PositiveCertificate)
+ // GnuPG wants at least a 512-bit hash for P521 keys.
+ .set_hash_algo(HashAlgorithm::SHA512);
+ let signature = ua.bind(&mut signer, &cert, builder, None)?;
+ cert = cert.merge_packets(vec![ua.into(), signature.into()])?;
+ }
+
+ // sign subkeys
+ for blueprint in self.subkeys {
+ let flags = &blueprint.flags;
+ let mut subkey = self.ciphersuite.generate_key(flags)?;
+
+ let mut builder =
+ signature::Builder::new(SignatureType::SubkeyBinding)
+ // GnuPG wants at least a 512-bit hash for P521 keys.
+ .set_hash_algo(HashAlgorithm::SHA512)
+ .set_features(&Features::sequoia())?
+ .set_key_flags(flags)?
+ .set_key_expiration_time(
+ blueprint.expiration.or(self.primary.expiration))?;
+
+ if flags.can_encrypt_for_transport() || flags.can_encrypt_at_rest()
+ {
+ builder = builder.set_preferred_symmetric_algorithms(vec![
+ SymmetricAlgorithm::AES256,
+ ])?;
+ }
+
+ if flags.can_certify() || flags.can_sign() {
+ builder = builder.set_preferred_hash_algorithms(vec![
+ HashAlgorithm::SHA512,
+ ])?;
+
+ // We need to create a primary key binding signature.
+ let mut subkey_signer = subkey.clone().into_keypair().unwrap();
+ let backsig =
+ signature::Builder::new(SignatureType::PrimaryKeyBinding)
+ // GnuPG wants at least a 512-bit hash for P521 keys.
+ .set_hash_algo(HashAlgorithm::SHA512)
+ .set_signature_creation_time(
+ time::SystemTime::now().canonicalize())?
+ .set_issuer_fingerprint(subkey.fingerprint())?
+ .set_issuer(subkey.keyid())?
+ .sign_subkey_binding(&mut subkey_signer, &primary,
+ &subkey.mark_parts_public_ref())?;
+ builder = builder.set_embedded_signature(backsig)?;
+ }
+
+ let signature = subkey.mark_parts_public_ref()
+ .bind(&mut signer, &cert, builder, None)?;
+
+ if let Some(ref password) = self.password {
+ subkey.secret_mut().unwrap().encrypt_in_place(password)?;
+ }
+ cert = cert.merge_packets(vec![Packet::SecretSubkey(subkey),
+ signature.into()])?;
+ }
+
+ let revocation = CertRevocationBuilder::new()
+ .set_reason_for_revocation(
+ ReasonForRevocation::Unspecified, b"Unspecified")?
+ .build(&mut signer, &cert, None)?;
+
+ // keys generated by the builder are never invalid
+ assert!(cert.bad.is_empty());
+ assert!(cert.unknowns.is_empty());
+
+ Ok((cert, revocation))
+ }
+
+ fn primary_key(&self)
+ -> Result<(key::PublicKey, Signature)>
+ {
+ let key = self.ciphersuite.generate_key(
+ &KeyFlags::default().set_certify(true))?;
+ let sig = signature::Builder::new(SignatureType::DirectKey)
+ // GnuPG wants at least a 512-bit hash for P521 keys.
+ .set_hash_algo(HashAlgorithm::SHA512)
+ .set_features(&Features::sequoia())?
+ .set_key_flags(&self.primary.flags)?
+ .set_signature_creation_time(
+ time::SystemTime::now().canonicalize())?
+ .set_key_expiration_time(self.primary.expiration)?
+ .set_issuer_fingerprint(key.fingerprint())?
+ .set_issuer(key.keyid())?
+ .set_preferred_hash_algorithms(vec![HashAlgorithm::SHA512])?;
+
+ let mut signer = key.clone().into_keypair()
+ .expect("key generated above has a secret");
+ let sig = sig.sign_primary_key_binding(&mut signer)?;
+
+ Ok((key.mark_parts_public(), sig.into()))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::packet::signature::subpacket::{SubpacketTag, SubpacketValue};
+ use crate::types::PublicKeyAlgorithm;
+
+ #[test]
+ fn all_opts() {
+ let (cert, _) = CertBuilder::new()
+ .set_cipher_suite(CipherSuite::Cv25519)
+ .add_userid("test1@example.com")
+ .add_userid("test2@example.com")
+ .add_signing_subkey()
+ .add_encryption_subkey()
+ .add_certification_subkey()
+ .generate().unwrap();
+
+ let mut userids = cert.userids()
+ .map(|u| String::from_utf8_lossy(u.userid().value()).into_owned())
+ .collect::<Vec<String>>();
+ userids.sort();
+
+ assert_eq!(userids,
+ &[ "test1@example.com",
+ "test2@example.com",
+ ][..]);
+ assert_eq!(cert.subkeys().count(), 3);
+ }
+
+ #[test]
+ fn direct_key_sig() {
+ let (cert, _) = CertBuilder::new()
+ .set_cipher_suite(CipherSuite::Cv25519)
+ .add_signing_subkey()
+ .add_encryption_subkey()
+ .add_certification_subkey()
+ .generate().unwrap();
+
+ assert_eq!(cert.userids().count(), 0);
+ assert_eq!(cert.primary_key_signature(None).unwrap().typ(),
+ crate::types::SignatureType::DirectKey);
+ assert_eq!(cert.subkeys().count(), 3);
+ if let Some(sig) = cert.primary_key_signature(None) {
+ assert!(sig.features().supports_mdc());
+ } else {
+ panic!();
+ }
+ }
+
+ #[test]
+ fn setter() {
+ let (cert1, _) = CertBuilder::new()
+ .set_cipher_suite(CipherSuite::Cv25519)
+ .set_cipher_suite(CipherSuite::RSA3k)
+ .set_cipher_suite(CipherSuite::Cv25519)
+ .generate().unwrap();
+ assert_eq!(cert1.primary().pk_algo(), PublicKeyAlgorithm::EdDSA);
+
+ let (cert2, _) = CertBuilder::new()
+ .set_cipher_suite(CipherSuite::RSA3k)
+ .add_userid("test2@example.com")
+ .add_encryption_subkey()
+ .generate().unwrap();
+ assert_eq!(cert2.primary().pk_algo(),
+ PublicKeyAlgorithm::RSAEncryptSign);
+ assert_eq!(cert2.subkeys().next().unwrap().key().pk_algo(),
+ PublicKeyAlgorithm::RSAEncryptSign);
+ }
+
+ #[test]
+ fn defaults() {
+ let (cert1, _) = CertBuilder::new()
+ .add_userid("test2@example.com")
+ .generate().unwrap();
+ assert_eq!(cert1.primary().pk_algo(),
+ PublicKeyAlgorithm::EdDSA);
+ assert!(cert1.subkeys().next().is_none());
+ if let Some(sig) = cert1.primary_key_signature(None) {
+ assert!(sig.features().supports_mdc());
+ } else {
+ panic!();
+ }
+ }
+
+ #[test]
+ fn autocrypt_v1() {
+ let (cert1, _) = CertBuilder::autocrypt(Autocrypt::V1,
+ Some("Foo"))
+ .generate().unwrap();
+ assert_eq!(cert1.primary().pk_algo(),
+ PublicKeyAlgorithm::RSAEncryptSign);
+ assert_eq!(cert1.subkeys().next().unwrap().key().pk_algo(),
+ PublicKeyAlgorithm::RSAEncryptSign);
+ assert_eq!(cert1.userids().count(), 1);
+ }
+
+ #[test]
+ fn autocrypt_v1_1() {
+ let (cert1, _) = CertBuilder::autocrypt(Autocrypt::V1_1,
+ Some("Foo"))
+ .generate().unwrap();
+ assert_eq!(cert1.primary().pk_algo(),
+ PublicKeyAlgorithm::EdDSA);
+ assert_eq!(cert1.subkeys().next().unwrap().key().pk_algo(),
+ PublicKeyAlgorithm::ECDH);
+ assert_match!(
+ crate::crypto::mpis::PublicKey::ECDH {
+ curve: crate::types::Curve::Cv25519, ..
+ } = cert1.subkeys().next().unwrap().key().mpis());
+ assert_eq!(cert1.userids().count(), 1);
+ }
+
+ #[test]
+ fn always_certify() {
+ let (cert1, _) = CertBuilder::new()
+ .set_cipher_suite(CipherSuite::Cv25519)
+ .primary_keyflags(KeyFlags::default())
+ .add_encryption_subkey()
+ .generate().unwrap();
+ let sig_pkts = &cert1.primary_key_signature(None).unwrap().hashed_area();
+
+ match sig_pkts.lookup(SubpacketTag::KeyFlags).unwrap().value() {
+ SubpacketValue::KeyFlags(ref ks) => assert!(ks.can_certify()),
+ v => panic!("Unexpected subpacket: {:?}", v),
+ }
+
+ assert_eq!(cert1.subkeys().count(), 1);
+ }
+
+ #[test]
+ fn gen_wired_subkeys() {
+ let (cert1, _) = CertBuilder::new()
+ .set_cipher_suite(CipherSuite::Cv25519)
+ .primary_keyflags(KeyFlags::default())
+ .add_subkey(KeyFlags::default().set_certify(true), None)
+ .generate().unwrap();
+ let sig_pkts = cert1.subkeys().next().unwrap().self_signatures[0].hashed_area();
+
+ match sig_pkts.lookup(SubpacketTag::KeyFlags).unwrap().value() {
+ SubpacketValue::KeyFlags(ref ks) => assert!(ks.can_certify()),
+ v => panic!("Unexpected subpacket: {:?}", v),
+ }
+
+ assert_eq!(cert1.subkeys().count(), 1);
+ }
+
+ #[test]
+ fn generate_revocation_certificate() {
+ use crate::RevocationStatus;
+ let (cert, revocation) = CertBuilder::new()
+ .set_cipher_suite(CipherSuite::Cv25519)
+ .generate().unwrap();
+ assert_eq!(cert.revoked(None),
+ RevocationStatus::NotAsFarAsWeKnow);
+
+ let cert = cert.merge_packets(vec![revocation.clone().into()]).unwrap();
+ assert_eq!(cert.revoked(None),
+ RevocationStatus::Revoked(vec![ &revocation ]));
+ }
+
+ #[test]
+ fn builder_roundtrip() {
+ use crate::PacketPile;
+
+ let (cert,_) = CertBuilder::new()
+ .set_cipher_suite(CipherSuite::Cv25519)
+ .add_signing_subkey()
+ .generate().unwrap();
+ let pile = cert.clone().into_packet_pile().into_children().collect::<Vec<_>>();
+ let exp = Cert::from_packet_pile(PacketPile::from(pile))
+ .unwrap();
+
+ assert_eq!(cert, exp);
+ }
+
+ #[test]
+ fn encrypted_secrets() {
+ let (cert,_) = CertBuilder::new()
+ .set_cipher_suite(CipherSuite::Cv25519)
+ .set_password(Some(String::from("streng geheim").into()))
+ .generate().unwrap();
+ assert!(cert.primary().secret().unwrap().is_encrypted());
+ }
+
+ #[test]
+ fn all_ciphersuites() {
+ use self::CipherSuite::*;
+
+ for cs in vec![Cv25519, RSA3k, P256, P384, P521, RSA2k, RSA4k] {
+ assert!(CertBuilder::new()
+ .set_cipher_suite(cs)
+ .generate().is_ok());
+ }
+ }
+
+ #[test]
+ fn expiration_times() {
+ let s = std::time::Duration::new(1, 0);
+ let (cert,_) = CertBuilder::new()
+ .set_expiration(600 * s)
+ .add_subkey(KeyFlags::default().set_sign(true),
+ 300 * s)
+ .add_subkey(KeyFlags::default().set_authenticate(true),
+ None)
+ .generate().unwrap();
+
+ let now = cert.primary().creation_time();
+ let key = cert.primary();
+ let sig = cert.primary_key_signature(None).unwrap();
+ assert!(sig.key_alive(key, now));
+ assert!(sig.key_alive(key, now + 599 * s));
+ assert!(! sig.key_alive(key, now + 601 * s));
+
+ let (sig, key) = cert.keys_valid().signing_capable()
+ .nth(0).map(|(s, _, k)| (s.unwrap(), k)).unwrap();
+ assert!(sig.key_alive(key, now));
+ assert!(sig.key_alive(key, now + 299 * s));
+ assert!(! sig.key_alive(key, now + 301 * s));
+
+ let (sig, key) = cert.keys_valid().authentication_capable()
+ .nth(0).map(|(s, _, k)| (s.unwrap(), k)).unwrap();
+ assert!(sig.key_alive(key, now));
+ assert!(sig.key_alive(key, now + 599 * s));
+ assert!(! sig.key_alive(key, now + 601 * s));
+ }
+}