From 36270e24d2761a40e92668ab0d083afae322da4c Mon Sep 17 00:00:00 2001 From: "Neal H. Walfield" Date: Wed, 9 Mar 2022 11:09:25 +0100 Subject: openpgp: Add a subkey builder. - Add `KeyBuilder` and `SubkeyBuilder` for creating a key, and attaching a subkey to a certificate. - See #483. --- openpgp/src/cert.rs | 2 +- openpgp/src/cert/builder.rs | 16 +- openpgp/src/cert/builder/key.rs | 1015 +++++++++++++++++++++++++++++++++++++++ openpgp/src/cert/prelude.rs | 2 + 4 files changed, 1030 insertions(+), 5 deletions(-) create mode 100644 openpgp/src/cert/builder/key.rs (limited to 'openpgp/src') diff --git a/openpgp/src/cert.rs b/openpgp/src/cert.rs index 562ca652..62ff3e20 100644 --- a/openpgp/src/cert.rs +++ b/openpgp/src/cert.rs @@ -185,7 +185,7 @@ pub mod bundle; mod parser; mod revoke; -pub use self::builder::{CertBuilder, CipherSuite}; +pub use self::builder::{CertBuilder, CipherSuite, KeyBuilder, SubkeyBuilder}; pub use parser::{ CertParser, diff --git a/openpgp/src/cert/builder.rs b/openpgp/src/cert/builder.rs index a160a074..3593fdc2 100644 --- a/openpgp/src/cert/builder.rs +++ b/openpgp/src/cert/builder.rs @@ -3,9 +3,11 @@ use std::marker::PhantomData; use crate::packet; use crate::packet::{ - key, Key, key::Key4, + key::KeyRole, + key::SecretKey as KeySecretKey, + key::SecretParts as KeySecretParts, }; use crate::Result; use crate::packet::Signature; @@ -26,6 +28,12 @@ use crate::types::{ RevocationKey, }; +mod key; +pub use key::{ + KeyBuilder, + SubkeyBuilder, +}; + /// Groups symmetric and asymmetric algorithms. /// /// This is used to select a suite of ciphers. @@ -133,8 +141,8 @@ impl CipherSuite { } fn generate_key(self, flags: K) - -> Result> - where R: key::KeyRole, + -> Result> + where R: KeyRole, K: AsRef, { use crate::types::Curve; @@ -1481,7 +1489,7 @@ impl CertBuilder<'_> { /// Creates the primary key and a direct key signature. fn primary_key(&self, creation_time: std::time::SystemTime) - -> Result<(key::SecretKey, Signature, Box)> + -> Result<(KeySecretKey, Signature, Box)> { let mut key = self.primary.ciphersuite .unwrap_or(self.ciphersuite) diff --git a/openpgp/src/cert/builder/key.rs b/openpgp/src/cert/builder/key.rs new file mode 100644 index 00000000..e23e463a --- /dev/null +++ b/openpgp/src/cert/builder/key.rs @@ -0,0 +1,1015 @@ +use std::time::{Duration, SystemTime}; + +use crate::packet::{ + Key, + key, +}; + +use crate::Result; +use crate::Packet; +use crate::packet::signature::{ + SignatureBuilder, + SIG_BACKDATE_BY, + subpacket::SubpacketTag, +}; +use crate::cert::prelude::*; +use crate::Error; +use crate::crypto::{Password, Signer}; +use crate::types::{ + HashAlgorithm, + KeyFlags, + SignatureType, +}; + +/// A Key builder. +/// +/// A `KeyBuilder` is used to create a key, which can then be attached +/// to an existing certificate as a subkey using +/// [`KeyBuilder::subkey`]. +/// +/// # Examples +/// +/// Generate a signing key and attach it to a certificate: +/// +/// ``` +/// use sequoia_openpgp as openpgp; +/// use openpgp::cert::prelude::*; +/// use openpgp::policy::StandardPolicy; +/// use openpgp::types::KeyFlags; +/// +/// # fn main() -> openpgp::Result<()> { +/// let p = &StandardPolicy::new(); +/// +/// # let (cert, _) = +/// # CertBuilder::general_purpose(None, Some("alice@example.org")) +/// # .generate()?; +/// # +/// let vc = cert.with_policy(p, None)?; +/// # let vc1 = vc.clone(); +/// let cert_new = KeyBuilder::new(KeyFlags::empty().set_signing()) +/// .subkey(vc)? +/// .attach_cert()?; +/// # let vc2 = cert_new.with_policy(p, None)?; +/// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count()); +/// # Ok(()) +/// # } +/// ``` +pub struct KeyBuilder { + flags: KeyFlags, + cipher_suite: CipherSuite, + password: Option, + creation_time: Option, +} +assert_send_and_sync!(KeyBuilder); + +impl KeyBuilder { + /// Returns a new `KeyBuilder`. + /// + /// Use [`KeyBuilder::subkey`] to generate a subkey and get a + /// [`SubkeyBuilder`], which can be used to add the subkey to a + /// certificate. + pub fn new(flags: KeyFlags) -> Self { + KeyBuilder { + flags, + cipher_suite: Default::default(), + creation_time: None, + password: None, + } + } + + /// Returns the selected cipher suite. + pub fn cipher_suite(&self) -> CipherSuite { + self.cipher_suite + } + + /// Sets the cipher suite. + pub fn set_cipher_suite(mut self, cipher_suite: CipherSuite) -> Self { + self.cipher_suite = cipher_suite; + self + } + + /// Returns the creation time. + /// + /// Returns `None` if the creation time hasn't been specified. In + /// that case, the creation time will be set to the current time + /// when the key material is generated by [`KeyBuilder::subkey`]. + pub fn creation_time(&self) -> Option { + self.creation_time + } + + /// Sets the creation time. + /// + /// If `None`, then the creation time will be set to the current + /// time when the key material is generated by + /// [`KeyBuilder::subkey`]. + pub fn set_creation_time(mut self, creation_time: T) -> Self + where T: Into> + { + self.creation_time = creation_time.into(); + self + } + + /// Returns the password, if any. + pub fn password(&self) -> Option<&Password> { + self.password.as_ref() + } + + /// Sets the password. + pub fn set_password(mut self, password: T) -> Self + where T: Into> + { + self.password = password.into(); + self + } + + /// Generates a key, and returns a `SubkeyBuilder`. + /// + /// The [`SubkeyBuilder`] will add the key to the specified + /// certificate. + /// + /// If the key creation time has not been explicitly set using + /// [`KeyBuilder::set_creation_time`], then the key's creation + /// time is set to the current time minus a few seconds. + /// + /// Setting the creation time to a short time in the past solves + /// two problems. First, when a new binding signature is created, + /// it must have a newer time than the previous binding signature. + /// This policy ensures that if a second binding signature is + /// immediately created after the key is created it does not need + /// to be postdated and thus can be used immediately. Second, if + /// the key is immediately transferred to another computer and its + /// clock is not quite synchronized, the key may appear to have + /// been created in the future and will thus be ignored. Although + /// NTP is widely used, emperically it seems that some virtual + /// machines have laggy clocks. + pub fn subkey(self, vc: ValidCert) -> Result> { + let mut key: Key + = self.cipher_suite.generate_key(&self.flags)?; + let ct = self.creation_time.unwrap_or_else(|| { + crate::now() - Duration::new(SIG_BACKDATE_BY, 0) + }); + key.set_creation_time(ct)?; + + let signer = key.clone().into_keypair().unwrap(); + + if let Some(ref password) = self.password { + key.secret_mut().encrypt_in_place(password)?; + } + + let mut builder = SubkeyBuilder::new( + vc, key.parts_into_unspecified(), self.flags)?; + builder = builder.set_signature_creation_time(ct)?; + Ok(builder.set_subkey_signer(signer)) + } +} + +/// A Subkey builder. +/// +/// This builder simplifies attaching a subkey to a certificate, or +/// updating an existing subkey's binding signature. It is a more +/// high-level variant of [`SignatureBuilder`], which should be used +/// if more control is needed than this builder provides. +/// +/// # Security Considerations: Key Expiration +/// +/// **It is essential that keys have reasonable expiration times.** If +/// a binding signature is accidentally published without an +/// expiration time, it is effectively impossible to retract this by +/// publishing a new binding signature that has an expiration. This +/// is because an attacker may be able to withhold the newer binding +/// signature thereby causing a victim to use a key that is actually +/// expired. +/// +/// The heuristic described below takes this security consideration +/// into account. However, because the heuristic never extends a +/// key's expiration on its own, there are still cases where it is +/// necessary to set the expiration manually. +/// +/// # Binding Signature +/// +/// To attach a subkey to a certificate, the primary key needs to +/// issue a [subkey binding signature]. This binding signature +/// provides information about the key including its validity period +/// (i.e., when it expires), and may contain auxiliary information +/// like notations. A subkey binding signature usually contains the +/// following information: +/// +/// [subkey binding signature]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.1 +/// +/// - [Signature creation time](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.4) +/// +/// - [Key flags](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.21) +/// +/// - [Issuer](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.5) and Issuer Fingerprint. +/// +/// - [Primary key binding signature](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.26) (if the key is signing capable) +/// +/// The following information is also meaningful in the context of +/// a subkey binding signature: +/// +/// - [Key expiration time](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.6) +/// (relative to the key's creation time, not the signature's +/// creation time!) +/// +/// - [Signature exiration time](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.10) +/// +/// - [Exportable certification](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.11) +/// +/// - [Notations](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.16) +/// +/// Because a `SubkeyBuilder` is just a wrapper around a +/// [`SignatureBuilder`], refer [`SignatureBuilder`]'s documentation +/// about to understand how some of these subpackets are automatically +/// set. +/// +/// It is possible to change the signature's creation time and key +/// expiration time using the +/// [`SubkeyBuilder::set_signature_creation_time`] and +/// [`SubkeyBuilder::set_key_expiration_time`] methods. Other +/// subpackets can be modified using +/// [`SubkeyBuilder::with_signature_template`]. +/// +/// ## Heuristic +/// +/// This builder uses a heuristic to select a binding signature to use +/// as a template and to select a key expiration. It is possible to +/// use your own binding signature by calling +/// [`SubkeyBuilder::set_signature_template`], and override the key +/// expiration time using [`SubkeyBuilder::set_key_expiration_time`]. +/// In general, you should use an existing binding signature as a +/// template to preserve any customizations that the user may have +/// made. +/// +/// Because forgetting to set an expiration time can be security +/// relevant, this heuristic acts conservatively. If possible, the +/// user interface should show the expiration time, and allow the user +/// to adjust it manually. +/// +/// The heuristic is: +/// +/// - If the subkey is already present on the certificate, the default +/// binding signature is based on the subkey's active binding +/// signature, and the key expiration time is reused. +/// +/// If the key would expire before the binding signature becomes +/// valid then [`SubkeyBuilder::attach`] will fail. +/// +/// Note: if the subkey is present, but it does not have a valid +/// binding signature, then the subkey is treated as a new subkey. +/// +/// - If the subkey is new, then the active binding signature of the +/// newest live, non-revoked, valid subkey is used as the binding +/// signature template. Newest means the the key with the latest +/// Key Creation Time and not necessarily the newest binding +/// signature. (If multiple keys have the same key creation time, +/// the key to use is chosen in an undefined, but deterministic +/// manner.) +/// +/// If the certificate does not have a subkey, then a default +/// binding signature is created. In this case, the default +/// expiration is set to the same expiration as the primary key, if +/// any. +/// +/// As above, if the key would expire before the binding signature +/// becomes valid then [`SubkeyBuilder::attach`] will fail. +/// +/// # Examples +/// +/// Add a new, signing-capable subkey to a certificate: +/// +/// ``` +/// use sequoia_openpgp as openpgp; +/// use openpgp::cert::prelude::*; +/// use openpgp::policy::StandardPolicy; +/// use openpgp::types::KeyFlags; +/// +/// # fn main() -> openpgp::Result<()> { +/// let p = &StandardPolicy::new(); +/// +/// # let (cert, _) = +/// # CertBuilder::general_purpose(None, Some("alice@example.org")) +/// # .generate()?; +/// # +/// let vc = cert.with_policy(p, None)?; +/// # let vc1 = vc.clone(); +/// let cert_new = KeyBuilder::new(KeyFlags::empty().set_signing()) +/// .subkey(vc)? +/// .attach_cert()?; +/// # let vc2 = cert_new.with_policy(p, None)?; +/// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count()); +/// # Ok(()) +/// # } +/// ``` +/// +/// Import a raw encryption key: +/// +/// ``` +/// use std::time::SystemTime; +/// +/// use sequoia_openpgp as openpgp; +/// use openpgp::cert::prelude::*; +/// use openpgp::packet::Key; +/// use openpgp::packet::key::Key4; +/// use openpgp::policy::StandardPolicy; +/// use openpgp::types::KeyFlags; +/// +/// # fn main() -> openpgp::Result<()> { +/// let p = &StandardPolicy::new(); +/// +/// # let q = b"\x57\x15\x45\x1B\x68\xA5\x13\xA2\x20\x0F\x71\x9D\xE3\x05\x3B\xED\xA2\x21\xDE\x61\x5A\xF5\x67\x45\xBB\x97\x99\x43\x53\x59\x7C\x3F"; +/// let k: Key<_, _> +/// = Key4::import_public_ed25519(q, SystemTime::now())?.into(); +/// +/// # let (cert, _) = CertBuilder::new().generate()?; +/// # +/// let vc = cert.with_policy(p, None)?; +/// # let vc1 = vc.clone(); +/// let mut cert2 = SubkeyBuilder::new( +/// vc, k.parts_into_unspecified(), +/// KeyFlags::empty().set_transport_encryption())? +/// .attach_cert()?; +/// # +/// # let vc2 = cert2.with_policy(p, None)?; +/// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count()); +/// # Ok(()) +/// # } +/// ``` +/// +/// Change all valid, non-revoked subkeys to expire in a year from now: +/// +/// ``` +/// use std::time::{SystemTime, Duration}; +/// +/// use sequoia_openpgp as openpgp; +/// use openpgp::cert::prelude::*; +/// use openpgp::Packet; +/// use openpgp::policy::StandardPolicy; +/// use openpgp::types::KeyFlags; +/// +/// # fn main() -> openpgp::Result<()> { +/// let p = &StandardPolicy::new(); +/// +/// let now = SystemTime::now(); +/// let e = now + Duration::new(365 * 24 * 60 * 60, 0); +/// +/// # let v = Duration::new(24 * 60 * 60, 0); +/// # let (cert, _) = +/// # CertBuilder::new() +/// # .set_creation_time(now - Duration::new(60, 0)) +/// # .add_subkey(KeyFlags::empty().set_storage_encryption(), +/// # v, None) +/// # .add_subkey(KeyFlags::empty().set_signing(), +/// # v, None) +/// # .generate()?; +/// # assert_eq!(cert.keys().subkeys().count(), 2); +/// let vc = cert.with_policy(p, None)?; +/// # assert_eq!(vc.keys().subkeys().count(), 2); +/// # for ka in vc.keys().subkeys() { +/// # assert_eq!(ka.key_validity_period(), Some(v)); +/// # } +/// +/// // If you only want to extend non-expired keys, then add .alive(). +/// let packets = vc.keys().subkeys().revoked(false) +/// .map(|ka| { +/// SubkeyBuilder::from(ka) +/// .set_signature_creation_time(now)? +/// .set_key_expiration_time(e)? +/// .attach() +/// }) +/// .collect::>, _>>()?; +/// let cert = cert.insert_packets(packets.into_iter().flatten())?; +/// +/// let vc = cert.with_policy(p, now)?; +/// # assert_eq!(vc.keys().subkeys().count(), 2); +/// for ka in vc.keys().subkeys().revoked(false) { +/// // Check that the key's expiration time is really e. Note: We +/// // need to take into account that SystemTime has a subsecond +/// // resolution, but OpenPGP's timestamps only have a 1 second +/// // resolution. +/// assert!(e.duration_since(ka.key_expiration_time().unwrap()).unwrap() +/// < Duration::new(1, 0)); +/// } +/// # Ok(()) +/// # } +/// ``` +pub struct SubkeyBuilder<'a> { + vc: ValidCert<'a>, + primary_signer: Option>, + + subkey: Key, + subkey_signer: Option>, + + template: SignatureBuilder, +} +assert_send_and_sync!(SubkeyBuilder<'_>); + +impl<'a> SubkeyBuilder<'a> { + /// Returns a SubkeyBuilder that will add the key to the specified + /// certificate. + /// + /// If the subkey is already present on the certificate, then the + /// `SubkeyBuilder` effectively adds a new binding signature to + /// the certificate. + pub fn new

(vc: ValidCert<'a>, + subkey: Key, + subkey_flags: KeyFlags) + -> Result + where P: key::KeyParts, + { + // If the key is already present on the certificate, then we + // use the current self signature on that subkey as the + // template. + let (template, key_expiration): (SignatureBuilder, Option) + = vc.keys().subkeys() + .filter_map(|ka| { + if ka.key().parts_as_unspecified().public_eq(&subkey) { + let sig = ka.binding_signature().clone(); + let e = sig.key_validity_period().map(|v| { + ka.key().creation_time() + v + }); + Some((sig.into(), e)) + } else { + None + } + }) + .next() + .or_else(|| { + // The key is completely new. Use the active self + // signature on the newest, non-revoked, non-expired + // subkey. + vc.keys().subkeys().revoked(false).alive() + // Fallback to sorting by fingerprint to ensure + // this is deterministic. + .max_by_key(|ka| (ka.key().creation_time(), ka.fingerprint())) + .map(|ka| { + let sig = ka.binding_signature().clone(); + let e = sig.key_validity_period().map(|v| { + ka.key().creation_time() + v + }); + (sig.into(), e) + }) + }) + .unwrap_or_else(|| { + // The certificate doesn't have any valid subkeys, so + // we don't have existing signatures that we can use + // as a template. In this case, we use a default + // binding signature, and the primary key's expiration + // time. + (SignatureBuilder::new(SignatureType::SubkeyBinding), + vc.primary_key().key_validity_period().map(|v| { + vc.primary_key().creation_time() + v + })) + }); + + let template = template.set_key_flags(subkey_flags)?; + + let mut builder = SubkeyBuilder { + vc, + primary_signer: None, + subkey: subkey.parts_into_unspecified(), + subkey_signer: None, + template: SignatureBuilder::new(SignatureType::SubkeyBinding), + }; + builder = builder.set_signature_template(template); + builder = builder.set_key_expiration_time(key_expiration)?; + + Ok(builder) + } + + /// Like SubkeyBuilder::new, but the binding signature is supplied. + /// + /// # Security Considerations + /// + /// The key validity period (i.e., the [Key Expiration Time + /// subpacket]) is left as is. **The Key Expiration Time + /// subpacket contains a relative time.** Thus, if you are using a + /// signature from another key with a different key creation time + /// as a template, the effective key expiration time will be + /// different! In this case, you should set the key expiration + /// time explicitly by calling + /// [`SubkeyBuilder::set_key_expiration_time`] or + /// [`SubkeyBuilder::set_key_validity_period`]. + /// + /// [Key Expiration Time subpacket]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.6 + /// + /// # Examples + /// + /// Adjusting the key expiration time: + /// + /// ``` + /// use std::time::{SystemTime, Duration}; + /// + /// use sequoia_openpgp as openpgp; + /// use openpgp::cert::prelude::*; + /// use openpgp::packet::Key; + /// use openpgp::packet::key::Key4; + /// use openpgp::policy::StandardPolicy; + /// use openpgp::types::KeyFlags; + /// + /// # fn main() -> openpgp::Result<()> { + /// let p = &StandardPolicy::new(); + /// + /// let now = SystemTime::now(); + /// let year = Duration::new(365 * 24 * 60 * 60, 0); + /// let last_year = now - year; + /// // cert was created last year and expires after two years. + /// let (cert, _) = + /// CertBuilder::new() + /// .set_creation_time(now - year) + /// .add_subkey(KeyFlags::empty().set_transport_encryption(), + /// 2 * year, None) + /// .generate()?; + /// + /// // Import a raw key and add it to the certificate. We + /// // explicitly reuse the existing subkey's signature, and adjust + /// // the key expiration time. + /// + /// # let q = b"\x57\x15\x45\x1B\x68\xA5\x13\xA2\x20\x0F\x71\x9D\xE3\x05\x3B\xED\xA2\x21\xDE\x61\x5A\xF5\x67\x45\xBB\x97\x99\x43\x53\x59\x7C\x3F"; + /// let k: Key<_, _> = Key4::import_public_ed25519(q, now)?.into(); + /// + /// let vc = cert.with_policy(p, now)?; + /// let template + /// = vc.keys().subkeys().next().unwrap().binding_signature().clone(); + /// # let vc1 = vc.clone(); + /// let cert2 = SubkeyBuilder::new_with(vc, k, template) + /// .set_key_validity_period(year)? + /// .attach_cert()?; + /// let vc2 = cert2.with_policy(p, now)?; + /// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count()); + /// + /// // Observe that both keys expire one year from now. If we + /// // hadn't adjust the validity period of the new key, it would + /// // have expired in two years from now, because the key validity + /// // period is relative to the key's creation time! + /// vc2.keys().subkeys().for_each(|sig| { + /// // SystemTime has a subsection resolution. + /// assert!((now + year) + /// .duration_since(sig.key_expiration_time().unwrap()) + /// .unwrap() + /// < Duration::new(1, 0)); + /// }); + /// # Ok(()) + /// # } + /// ``` + pub fn new_with(vc: ValidCert<'a>, + subkey: Key, + template: T) + -> Self + where P: key::KeyParts, + T: Into, + { + let template = template.into(); + + let mut builder = SubkeyBuilder { + vc, + primary_signer: None, + subkey: subkey.parts_into_unspecified(), + subkey_signer: None, + template: SignatureBuilder::new(SignatureType::SubkeyBinding), + }; + builder = builder.set_signature_template(template); + builder + } + + /// Sets the signature template that will be used for the binding + /// signature. + /// + /// This effectively discards any previous calls to + /// [`SubkeyBuilder::set_signature_creation_time`], + /// [`SubkeyBuilder::set_key_expiration_time`], etc. + /// + /// This function modifies the template as follows: + /// + /// - The hash algorithm is set to a safe default. + /// + /// These changes can be overridden by using + /// [`SubkeyBuilder::with_signature_template`]. + /// + /// # Security Considerations + /// + /// The key validity period (i.e., the [Key Expiration Time + /// subpacket]) is left as is. **This packet contains a relative + /// time.** Thus, if you are using a Signature from another key + /// with a different key creation time as a template, the + /// effective key expiration time will be different! In this + /// case, you should set the key expiration time explicitly by + /// calling [`SubkeyBuilder::set_key_expiration_time`] or + /// [`SubkeyBuilder::set_key_validity_period`]. + /// + /// [Key Expiration Time subpacket]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.6 + pub fn set_signature_template(mut self, template: T) -> Self + where T: Into, + { + self.template = template.into(); + + // GnuPG wants at least a 512-bit hash for P521 keys. + self.template = self.template.set_hash_algo(HashAlgorithm::SHA512); + + self + } + + /// Allows a function to directly modify the signature template. + /// + /// This function does not fail; it returns the result of the + /// callback function. + /// + /// # Examples + /// + /// Add a notation to an existing key: + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// use openpgp::cert::prelude::*; + /// use openpgp::packet::signature::subpacket::NotationDataFlags; + /// use openpgp::policy::StandardPolicy; + /// use openpgp::types::KeyFlags; + /// + /// # fn main() -> openpgp::Result<()> { + /// let p = &StandardPolicy::new(); + /// + /// # let (cert, _) = CertBuilder::new().add_signing_subkey().generate()?; + /// let vc = cert.with_policy(p, None)?; + /// let cert2 = SubkeyBuilder::from(vc.keys().subkeys().next().unwrap()) + /// .with_signature_template(|sig| { + /// sig.add_notation("policy@example.org", b"1", + /// NotationDataFlags::empty().set_human_readable(), + /// false /* critical */) + /// })? + /// .attach_cert()?; + /// # let vc2 = cert2.with_policy(p, None)?; + /// # assert_eq!(vc2.keys().count(), 2); + /// # let ka = vc2.keys().subkeys().next().unwrap(); + /// # assert_eq!(ka.self_signatures().count(), 2); + /// # assert_eq!( + /// # ka.binding_signature().notation("policy@example.org") + /// # .collect::>(), + /// # vec![ b"1" ]); + /// # Ok(()) + /// # } + /// ``` + pub fn with_signature_template(mut self, f: F) -> Result + where F: FnOnce(SignatureBuilder) -> Result + { + self.template = f(self.template.clone())?; + + Ok(self) + } + + /// Sets the binding signature's creation time. + /// + /// This directly modifies the current signature template. + /// + /// This just calls + /// [`SignatureBuilder::set_signature_creation_time`] on the + /// signature template. + pub fn set_signature_creation_time(mut self, creation_time: T) + -> Result + where T: Into + { + self.template = self.template.set_signature_creation_time( + creation_time.into())?; + Ok(self) + } + + /// Preserves the signature creation time set in the template. + /// + /// This directly modifies the current signature template. + /// + /// This just calls + /// [`SignatureBuilder::preserve_signature_creation_time`] on the + /// signature template. + pub fn preserve_signature_creation_time(mut self) -> Result + { + self.template + = self.template.preserve_signature_creation_time()?; + Ok(self) + } + + /// Sets the key's expiration time. + /// + /// This directly modifies the current signature template. + /// + /// This returns an error if the expiration time is before the + /// key's creation time. + pub fn set_key_expiration_time(mut self, key_expiration_time: T) + -> Result + where T: Into> + { + let key_expiration_time = key_expiration_time.into(); + let validity_period = key_expiration_time + .map(|e| { + e.duration_since(self.subkey.creation_time()) + .map_err(|_| { + Error::InvalidArgument( + "expiration time precedes creation time".into()) + }) + }) + .transpose()?; + + self = self.with_signature_template(|sig| { + sig.set_key_validity_period(validity_period) + })?; + + Ok(self) + } + + /// Sets the key's validity period. + /// + /// The validity period is the amount of time after the key's + /// creation time that the key is considered fresh (i.e., not + /// expired). + /// + /// This directly modifies the current signature template. + pub fn set_key_validity_period(mut self, validity: T) + -> Result + where T: Into> + { + self = self.with_signature_template(|sig| { + sig.set_key_validity_period(validity.into()) + })?; + + Ok(self) + } + + /// Returns a reference to the subkey. + pub fn key(&self) -> &Key { + &self.subkey + } + + /// Adds a signer for the primary key. + /// + /// In order to attach a subkey to a certificate one or more + /// signatures need to be issued. First, the primary key needs to + /// issue a [subkey binding signature]. If the subkey is signing + /// capable, then it also needs to issue a [primary key binding + /// signature]. By default, [`SubkeyBuilder::attach`] will + /// automatically derive the signers from the key material. This + /// only works, however, if the key material is present, and it is + /// unencrypted. This method allows you to explicitly provide a + /// signer for the primary key. + /// + /// [subkey binding signature]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.1 + /// [primary binding signature]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.1 + pub fn set_primary_key_signer(mut self, signer: S) -> Self + where S: Signer + Send + Sync + 'a, + { + self.primary_signer = Some(Box::new(signer)); + self + } + + /// Adds a signer for the subkey. + /// + /// In order to attach a subkey to a certificate one or more + /// signatures need to be issued. First, the primary key needs to + /// issue a [subkey binding signature]. If the subkey is signing + /// capable, then it also needs to issue a [primary key binding + /// signature]. By default, [`SubkeyBuilder::attach`] will + /// automatically derive the signers from the key material. This + /// only works, however, if the key material is present, and it is + /// unencrypted. This method allows you to explicitly provide a + /// signer for the subkey. + /// + /// [subkey binding signature]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.1 + /// [primary binding signature]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.1 + pub fn set_subkey_signer(mut self, signer: S) -> Self + where S: Signer + Send + Sync + 'a, + { + self.subkey_signer = Some(Box::new(signer)); + self + } + + /// Attaches the subkey to the certificate. + /// + /// This method generates the appropriate signatures to attach the + /// subkey to the certificate. + /// + /// This function returns an error if the expiration time would + /// cause the key to expire before the binding signature's + /// expiration time. + /// + /// This method returns a number of packets, which need to be + /// merged into the cert. This can be done using + /// [`Cert::insert_packets`]. + pub fn attach(self) -> Result> { + let SubkeyBuilder { + vc, + primary_signer, + subkey, + subkey_signer, + template, + } = self; + + if template.typ() != SignatureType::SubkeyBinding { + return Err(Error::InvalidArgument( + format!("Expected a SubkeyBinding signature, got a {}", + template.typ())).into()); + } + + let mut builder = template; + + let creation_time = builder.effective_signature_creation_time()?; + + // creation_time is only None if + // preserve_signature_creation_time is done and that's a + // Highly Advanced Interface that doesn't need sanity checks. + if let Some(sig_ct) = creation_time { + if let Some(v) = builder.key_validity_period() { + let e = subkey.creation_time() + v; + if let Err(err) = e.duration_since(sig_ct) { + return Err(Error::InvalidArgument( + format!( + "key expiration precedes signature creation time \ + (by {:?})", + err.duration())).into()); + } + } + } + + if let Some(flags) = builder.key_flags() { + if flags.for_certification() || flags.for_signing() { + // We need to create a primary key binding signature. + let mut subkey_signer = if let Some(signer) = subkey_signer { + signer + } else { + Box::new( + subkey.clone().parts_into_secret()?.into_keypair()?) + }; + + let mut backsig = + SignatureBuilder::new( + SignatureType::PrimaryKeyBinding) + // GnuPG wants at least a 512-bit hash for P521 keys. + .set_hash_algo(HashAlgorithm::SHA512) + .set_reference_time(creation_time); + if let Some(t) = creation_time { + backsig = backsig.set_reference_time(t); + } else { + backsig = backsig.preserve_signature_creation_time()?; + } + let backsig = backsig.sign_primary_key_binding( + &mut *subkey_signer, &vc.primary_key(), &subkey)?; + builder = builder.set_embedded_signature(backsig)?; + } else { + // We don't need the embedded signature, remove it. + builder.hashed_area_mut() + .remove_all(SubpacketTag::EmbeddedSignature); + builder.unhashed_area_mut() + .remove_all(SubpacketTag::EmbeddedSignature); + } + } + + let mut primary_signer = if let Some(signer) = primary_signer { + signer + } else { + Box::new( + vc.primary_key().key().clone() + .parts_into_secret()?.into_keypair()?) + }; + + let signature = subkey.bind( + &mut *primary_signer, vc.cert(), builder)?; + + let subkey = if subkey.has_secret() { + Packet::SecretSubkey(subkey.parts_into_secret().unwrap()) + } else { + Packet::PublicSubkey(subkey.parts_into_public()) + }; + + Ok(vec![subkey, signature.into()]) + } + + /// Attaches the subkey directly to the certificate. + /// + /// This function is like [`SubkeyBuilder::attach`], but it also + /// merges the resulting packets into the certificate. + /// + /// Note: if you are adding multiple subkeys to a certificate or + /// updating multiple subkeys, it is usually more efficient to use + /// [`SubkeyBuilder::attach`], and then merge all of the packets + /// at once. + /// + /// # Examples + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// use openpgp::cert::prelude::*; + /// use openpgp::policy::StandardPolicy; + /// use openpgp::types::KeyFlags; + /// + /// # fn main() -> openpgp::Result<()> { + /// let p = &StandardPolicy::new(); + /// + /// # let (cert, _) = + /// # CertBuilder::general_purpose(None, Some("alice@example.org")) + /// # .generate()?; + /// # + /// let vc = cert.with_policy(p, None)?; + /// # let vc1 = vc.clone(); + /// let cert2 = KeyBuilder::new(KeyFlags::empty().set_signing()) + /// .subkey(vc)? + /// .attach_cert()?; + /// # let vc2 = cert2.with_policy(p, None)?; + /// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count()); + /// # Ok(()) + /// # } + /// ``` + pub fn attach_cert(self) -> Result { + let cert = self.vc.cert().clone(); + let packets = self.attach()?; + Ok(cert.insert_packets(packets)?) + } +} + +impl<'a, P> From> for SubkeyBuilder<'a> +where + P: key::KeyParts + Clone, +{ + fn from(ka: ValidPrimaryKeyAmalgamation<'a, P>) -> Self { + ValidErasedKeyAmalgamation::from(ka).into() + } +} + +impl<'a, P> From> for SubkeyBuilder<'a> +where + P: key::KeyParts + Clone, +{ + fn from(ka: ValidSubordinateKeyAmalgamation<'a, P>) -> Self { + ValidErasedKeyAmalgamation::from(ka).into() + } +} + +impl<'a, P> From> for SubkeyBuilder<'a> +where + P: key::KeyParts + Clone, +{ + fn from(ka: ValidErasedKeyAmalgamation<'a, P>) -> SubkeyBuilder<'a> { + let key = ka.key().clone().role_into_subordinate(); + SubkeyBuilder::new_with( + ka.cert().clone(), key, ka.binding_signature().clone()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::time::{Duration, UNIX_EPOCH}; + + use crate::policy::StandardPolicy; + + #[test] + fn expiry() -> Result<()> { + let p = &StandardPolicy::new(); + + // t0: Create certificate, keys expire at t2. + // t1: Add a new key, heuristic should have it expire at t2. + // t2: All keys expire. + + // We do it all in the past to make sure the current time is + // never used. + + // Avoid milliseconds. + let t1 = crate::now() - Duration::new(7 * 24 * 60 * 60, 0); + let t1 = t1.duration_since(UNIX_EPOCH)?.as_secs(); + let t1 = UNIX_EPOCH + Duration::new(t1, 0); + + let t0 = t1 - Duration::new(60 * 60, 0); + let t2 = t1 + Duration::new(60 * 60, 0); + let validity = t2.duration_since(t0).unwrap(); + + let (pre, _) = + CertBuilder::general_purpose(None, Some("alice@example.org")) + .set_creation_time(t0) + .set_validity_period(validity) + .generate()?; + + let vc_pre = pre.with_policy(p, t1)?; + let post = KeyBuilder::new(KeyFlags::empty().set_signing()) + .set_creation_time(t1) + .subkey(vc_pre)? + .set_signature_creation_time(t1)? + .attach_cert()?; + + let vc_post = post.with_policy(p, t1).unwrap(); + + // We should have one more key. + assert_eq!(pre.keys().count() + 1, post.keys().count()); + + // Make sure the signature and backsig are valid. + assert_eq!(post.keys().count(), vc_post.keys().count()); + + // And the new key should have inherited the other keys' + // expiration. + eprintln!("t0: {:?}", t0); + eprintln!("t1: {:?}", t1); + eprintln!("t2: {:?}", t2); + assert!(vc_post.keys().all(|ka| { + eprintln!("{}: {:?} -> {:?}", + ka.fingerprint(), + ka.creation_time(), + ka.key_expiration_time()); + ka.key_expiration_time() == Some(t2) + })); + + Ok(()) + } +} diff --git a/openpgp/src/cert/prelude.rs b/openpgp/src/cert/prelude.rs index b453c663..48ce47e8 100644 --- a/openpgp/src/cert/prelude.rs +++ b/openpgp/src/cert/prelude.rs @@ -19,6 +19,8 @@ pub use crate::cert::{ CertParser, CertRevocationBuilder, CipherSuite, + KeyBuilder, + SubkeyBuilder, Preferences as _, SubkeyRevocationBuilder, UserAttributeRevocationBuilder, -- cgit v1.2.3