From a1e226f8f1418de43e577fdaa1d087b68bbb09ae Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Thu, 25 Apr 2019 14:21:45 +0200 Subject: openpgp: Add interface to bind, certify, and revoke. - This adds a flexible interface to create binding signatures, certificates, and revocation signatures for userids, user attributes, and subkeys. --- openpgp/src/tpk/bindings.rs | 531 ++++++++++++++++++++++++++++++++++++++++++++ openpgp/src/tpk/mod.rs | 23 +- 2 files changed, 536 insertions(+), 18 deletions(-) create mode 100644 openpgp/src/tpk/bindings.rs diff --git a/openpgp/src/tpk/bindings.rs b/openpgp/src/tpk/bindings.rs new file mode 100644 index 00000000..eb355df0 --- /dev/null +++ b/openpgp/src/tpk/bindings.rs @@ -0,0 +1,531 @@ +use Error; +use Result; +use TPK; +use constants::{HashAlgorithm, SignatureType, ReasonForRevocation}; +use crypto::Signer; +use packet::{UserID, UserAttribute, Key, signature, Signature}; + +impl Key { + /// Creates a binding signature. + /// + /// The signature binds this userid to `tpk`. `signer` will be used + /// to create a signature using `signature` as builder. + /// The`hash_algo` defaults to SHA512, `creation_time` to the + /// current time. + /// + /// This function adds a creation time subpacket, a issuer + /// fingerprint subpacket, and a issuer subpacket to the + /// signature. + /// + /// # Example + /// + /// This example demonstrates how to bind this key to a TPK. Note + /// that in general, the `TPKBuilder` is a better way to add + /// subkeys to a TPK. + /// + /// ``` + /// # use sequoia_openpgp::{*, packet::prelude::*, constants::*, tpk::*}; + /// # f().unwrap(); + /// # fn f() -> Result<()> { + /// // Generate a TPK, and create a keypair from the primary key. + /// let (tpk, _) = TPKBuilder::default().generate()?; + /// let mut keypair = tpk.primary().clone().into_keypair()?; + /// + /// // Let's add an encryption subkey. + /// let flags = KeyFlags::default().set_encrypt_at_rest(true); + /// assert_eq!(tpk.keys_valid().key_flags(flags.clone()).count(), 0); + /// + /// // Generate a subkey and a binding signature. + /// let subkey = Key::V4(Key4::generate_ecc(false, Curve::Cv25519)?); + /// let builder = signature::Builder::new(SignatureType::SubkeyBinding) + /// .set_key_flags(&flags)?; + /// let binding = subkey.bind(&mut keypair, &tpk, builder, None, None)?; + /// + /// // Now merge the key and binding signature into the TPK. + /// let tpk = tpk.merge_packets(vec![subkey.into_packet(Tag::SecretSubkey)?, + /// binding.into()])?; + /// + /// // Check that we have an encryption subkey. + /// assert_eq!(tpk.keys_valid().key_flags(flags).count(), 1); + /// # Ok(()) } + pub fn bind(&self, signer: &mut Signer, tpk: &TPK, + signature: signature::Builder, + hash_algo: H, creation_time: T) + -> Result + where H: Into>, + T: Into> + { + signature + .set_signature_creation_time( + creation_time.into().unwrap_or_else(time::now_utc))? + .set_issuer_fingerprint(signer.public().fingerprint())? + .set_issuer(signer.public().keyid())? + .sign_subkey_binding( + signer, tpk.primary(), self, + hash_algo.into().unwrap_or(HashAlgorithm::SHA512)) + } + + /// Returns a revocation certificate for the subkey. + /// + /// The revocation signature revokes the binding between this user + /// attribute and `tpk`. `signer` will be used to create a + /// signature with the given reason in `code` and `reason`. + /// `signature_type`. `hash_algo` defaults to SHA512, + /// `creation_time` to the current time. + /// + /// This function adds a creation time subpacket, a issuer + /// fingerprint subpacket, and a issuer subpacket to the + /// signature. + /// + /// # Example + /// + /// ``` + /// # use sequoia_openpgp::{*, packet::*, constants::*, tpk::*}; + /// # f().unwrap(); + /// # fn f() -> Result<()> { + /// // Generate a TPK, and create a keypair from the primary key. + /// let (tpk, _) = TPKBuilder::default() + /// .add_encryption_subkey() + /// .generate()?; + /// let mut keypair = tpk.primary().clone().into_keypair()?; + /// + /// // Generate the revocation for the first and only Subkey. + /// let revocation = + /// tpk.subkeys().nth(0).unwrap().subkey() + /// .revoke(&mut keypair, &tpk, + /// ReasonForRevocation::KeyRetired, + /// b"Smells funny.", None, None)?; + /// assert_eq!(revocation.sigtype(), SignatureType::SubkeyRevocation); + /// + /// // Now merge the revocation signature into the TPK. + /// let tpk = tpk.merge_packets(vec![revocation.clone().into()])?; + /// + /// // Check that it is revoked. + /// let subkey = tpk.subkeys().nth(0).unwrap(); + /// if let RevocationStatus::Revoked(revocations) = subkey.revoked(None) { + /// assert_eq!(revocations.len(), 1); + /// assert_eq!(revocations[0], revocation); + /// } else { + /// panic!("Subkey is not revoked."); + /// } + /// # Ok(()) } + /// ``` + pub fn revoke(&self, signer: &mut Signer, tpk: &TPK, + code: ReasonForRevocation, reason: &[u8], + hash_algo: H, creation_time: T) + -> Result + where H: Into>, + T: Into> + { + self.bind(signer, tpk, + signature::Builder::new(SignatureType::SubkeyRevocation) + .set_reason_for_revocation(code, reason)?, + // Unwrap arguments to prevent further + // monomorphization of bind(). + hash_algo.into().unwrap_or(HashAlgorithm::SHA512), + creation_time.into().unwrap_or_else(time::now_utc)) + } +} + +impl UserID { + /// Creates a binding signature. + /// + /// The signature binds this userid to `tpk`. `signer` will be used + /// to create a signature using `signature` as builder. + /// The`hash_algo` defaults to SHA512, `creation_time` to the + /// current time. + /// + /// This function adds a creation time subpacket, a issuer + /// fingerprint subpacket, and a issuer subpacket to the + /// signature. + /// + /// # Example + /// + /// This example demonstrates how to bind this userid to a TPK. + /// Note that in general, the `TPKBuilder` is a better way to add + /// userids to a TPK. + /// + /// ``` + /// # use sequoia_openpgp::{*, packet::prelude::*, constants::*, tpk::*}; + /// # f().unwrap(); + /// # fn f() -> Result<()> { + /// // Generate a TPK, and create a keypair from the primary key. + /// let (tpk, _) = TPKBuilder::default().generate()?; + /// let mut keypair = tpk.primary().clone().into_keypair()?; + /// assert_eq!(tpk.userids().len(), 0); + /// + /// // Generate a userid and a binding signature. + /// let userid = UserID::from("test@example.org"); + /// let builder = + /// signature::Builder::new(SignatureType::PositiveCertificate); + /// let binding = userid.bind(&mut keypair, &tpk, builder, None, None)?; + /// + /// // Now merge the userid and binding signature into the TPK. + /// let tpk = tpk.merge_packets(vec![userid.into(), binding.into()])?; + /// + /// // Check that we have a userid. + /// assert_eq!(tpk.userids().len(), 1); + /// # Ok(()) } + pub fn bind(&self, signer: &mut Signer, tpk: &TPK, + signature: signature::Builder, + hash_algo: H, creation_time: T) + -> Result + where H: Into>, + T: Into> + { + signature + .set_signature_creation_time( + creation_time.into().unwrap_or_else(time::now_utc))? + .set_issuer_fingerprint(signer.public().fingerprint())? + .set_issuer(signer.public().keyid())? + .sign_userid_binding( + signer, tpk.primary(), self, + hash_algo.into().unwrap_or(HashAlgorithm::SHA512)) + } + + /// Returns a certificate for the user id. + /// + /// The signature binds this userid to `tpk`. `signer` will be + /// used to create a certification signature of type + /// `signature_type`. `signature_type` defaults to + /// `SignatureType::GenericCertificate`, `hash_algo` to SHA512, + /// `creation_time` to the current time. + /// + /// This function adds a creation time subpacket, a issuer + /// fingerprint subpacket, and a issuer subpacket to the + /// signature. + /// + /// # Errors + /// + /// Returns `Error::InvalidArgument` if `signature_type` is not + /// one of `SignatureType::{Generic, Persona, Casual, + /// Positive}Certificate` + /// + /// # Example + /// + /// This example demonstrates how to certify a userid. + /// + /// ``` + /// # use sequoia_openpgp::{*, packet::prelude::*, constants::*, tpk::*}; + /// # f().unwrap(); + /// # fn f() -> Result<()> { + /// // Generate a TPK, and create a keypair from the primary key. + /// let (alice, _) = TPKBuilder::default() + /// .primary_keyflags(KeyFlags::default().set_certify(true)) + /// .add_userid("alice@example.org") + /// .generate()?; + /// let mut keypair = alice.primary().clone().into_keypair()?; + /// + /// // Generate a TPK for Bob. + /// let (bob, _) = TPKBuilder::default() + /// .primary_keyflags(KeyFlags::default().set_certify(true)) + /// .add_userid("bob@example.org") + /// .generate()?; + /// + /// // Alice now certifies the binding between `bob@example.org` and `bob`. + /// let certificate = + /// bob.userids().nth(0).unwrap().userid() + /// .certify(&mut keypair, &bob, SignatureType::PositiveCertificate, + /// None, None)?; + /// + /// // `certificate` can now be used, e.g. by merging it into `bob`. + /// let bob = bob.merge_packets(vec![certificate.into()])?; + /// + /// // Check that we have a certification on the userid. + /// assert_eq!(bob.userids().nth(0).unwrap().certifications().len(), 1); + /// # Ok(()) } + pub fn certify(&self, signer: &mut Signer, tpk: &TPK, + signature_type: S, + hash_algo: H, creation_time: T) + -> Result + where S: Into>, + H: Into>, + T: Into> + { + let typ = signature_type.into(); + let typ = match typ { + Some(SignatureType::GenericCertificate) + | Some(SignatureType::PersonaCertificate) + | Some(SignatureType::CasualCertificate) + | Some(SignatureType::PositiveCertificate) => typ.unwrap(), + Some(t) => return Err(Error::InvalidArgument( + format!("Invalid signature type: {}", t)).into()), + None => SignatureType::GenericCertificate, + }; + self.bind(signer, tpk, signature::Builder::new(typ), + // Unwrap arguments to prevent further + // monomorphization of bind(). + hash_algo.into().unwrap_or(HashAlgorithm::SHA512), + creation_time.into().unwrap_or_else(time::now_utc)) + } + + /// Returns a revocation certificate for the user id. + /// + /// The revocation signature revokes the binding between this user + /// attribute and `tpk`. `signer` will be used to create a + /// signature with the given reason in `code` and `reason`. + /// `signature_type`. `hash_algo` defaults to SHA512, + /// `creation_time` to the current time. + /// + /// This function adds a creation time subpacket, a issuer + /// fingerprint subpacket, and a issuer subpacket to the + /// signature. + /// + /// # Example + /// + /// ``` + /// # use sequoia_openpgp::{*, constants::*, tpk::*}; + /// # f().unwrap(); + /// # fn f() -> Result<()> { + /// // Generate a TPK, and create a keypair from the primary key. + /// let (tpk, _) = TPKBuilder::default() + /// .add_userid("some@example.org") + /// .generate()?; + /// let mut keypair = tpk.primary().clone().into_keypair()?; + /// + /// // Generate the revocation for the first and only UserID. + /// let revocation = + /// tpk.userids().nth(0).unwrap().userid() + /// .revoke(&mut keypair, &tpk, + /// ReasonForRevocation::UIDRetired, + /// b"Left example.org.", None, None)?; + /// assert_eq!(revocation.sigtype(), SignatureType::CertificateRevocation); + /// + /// // Now merge the revocation signature into the TPK. + /// let tpk = tpk.merge_packets(vec![revocation.clone().into()])?; + /// + /// // Check that it is revoked. + /// let uid = tpk.userids().nth(0).unwrap(); + /// if let RevocationStatus::Revoked(revocations) = uid.revoked(None) { + /// assert_eq!(revocations.len(), 1); + /// assert_eq!(revocations[0], revocation); + /// } else { + /// panic!("UserID is not revoked."); + /// } + /// # Ok(()) } + /// ``` + pub fn revoke(&self, signer: &mut Signer, tpk: &TPK, + code: ReasonForRevocation, reason: &[u8], + hash_algo: H, creation_time: T) + -> Result + where H: Into>, + T: Into> + { + self.bind(signer, tpk, + signature::Builder::new(SignatureType::CertificateRevocation) + .set_reason_for_revocation(code, reason)?, + // Unwrap arguments to prevent further + // monomorphization of bind(). + hash_algo.into().unwrap_or(HashAlgorithm::SHA512), + creation_time.into().unwrap_or_else(time::now_utc)) + } +} + +impl UserAttribute { + /// Creates a binding signature. + /// + /// The signature binds this user attribute to `tpk`. `signer` + /// will be used to create a signature using `signature` as + /// builder. The`hash_algo` defaults to SHA512, `creation_time` + /// to the current time. + /// + /// This function adds a creation time subpacket, a issuer + /// fingerprint subpacket, and a issuer subpacket to the + /// signature. + /// + /// # Example + /// + /// This example demonstrates how to bind this user attribute to a + /// TPK. Note that in general, the `TPKBuilder` is a better way + /// to add userids to a TPK. + /// + /// ``` + /// # use sequoia_openpgp::{*, packet::prelude::*, constants::*, tpk::*, + /// packet::user_attribute::*}; + /// # f().unwrap(); + /// # fn f() -> Result<()> { + /// // Generate a TPK, and create a keypair from the primary key. + /// let (tpk, _) = TPKBuilder::default() + /// .generate()?; + /// let mut keypair = tpk.primary().clone().into_keypair()?; + /// assert_eq!(tpk.userids().len(), 0); + /// + /// // Generate a user attribute and a binding signature. + /// let user_attr = UserAttribute::new(&[ + /// Subpacket::Image( + /// Image::Private(100, vec![0, 1, 2].into_boxed_slice())), + /// ])?; + /// let builder = + /// signature::Builder::new(SignatureType::PositiveCertificate); + /// let binding = user_attr.bind(&mut keypair, &tpk, builder, None, None)?; + /// + /// // Now merge the user attribute and binding signature into the TPK. + /// let tpk = tpk.merge_packets(vec![user_attr.into(), binding.into()])?; + /// + /// // Check that we have a user attribute. + /// assert_eq!(tpk.user_attributes().len(), 1); + /// # Ok(()) } + pub fn bind(&self, signer: &mut Signer, tpk: &TPK, + signature: signature::Builder, + hash_algo: H, creation_time: T) + -> Result + where H: Into>, + T: Into> + { + signature + .set_signature_creation_time( + creation_time.into().unwrap_or_else(time::now_utc))? + .set_issuer_fingerprint(signer.public().fingerprint())? + .set_issuer(signer.public().keyid())? + .sign_user_attribute_binding( + signer, tpk.primary(), self, + hash_algo.into().unwrap_or(HashAlgorithm::SHA512)) + } + + /// Returns a certificate for the user attribute. + /// + /// The signature binds this user attribute to `tpk`. `signer` will be + /// used to create a certification signature of type + /// `signature_type`. `signature_type` defaults to + /// `SignatureType::GenericCertificate`, `hash_algo` to SHA512, + /// `creation_time` to the current time. + /// + /// This function adds a creation time subpacket, a issuer + /// fingerprint subpacket, and a issuer subpacket to the + /// signature. + /// + /// # Errors + /// + /// Returns `Error::InvalidArgument` if `signature_type` is not + /// one of `SignatureType::{Generic, Persona, Casual, + /// Positive}Certificate` + /// + /// # Example + /// + /// This example demonstrates how to certify a userid. + /// + /// ``` + /// # use sequoia_openpgp::{*, packet::prelude::*, constants::*, tpk::*, + /// packet::user_attribute::*}; + /// # f().unwrap(); + /// # fn f() -> Result<()> { + /// // Generate a TPK, and create a keypair from the primary key. + /// let (alice, _) = TPKBuilder::default() + /// .add_userid("alice@example.org") + /// .generate()?; + /// let mut keypair = alice.primary().clone().into_keypair()?; + /// + /// // Generate a TPK for Bob. + /// let user_attr = UserAttribute::new(&[ + /// Subpacket::Image( + /// Image::Private(100, vec![0, 1, 2].into_boxed_slice())), + /// ])?; + /// let (bob, _) = TPKBuilder::default() + /// .primary_keyflags(KeyFlags::default().set_certify(true)) + /// .add_user_attribute(user_attr) + /// .generate()?; + /// + /// // Alice now certifies the binding between `bob@example.org` and `bob`. + /// let certificate = + /// bob.user_attributes().nth(0).unwrap().user_attribute() + /// .certify(&mut keypair, &bob, SignatureType::PositiveCertificate, + /// None, None)?; + /// + /// // `certificate` can now be used, e.g. by merging it into `bob`. + /// let bob = bob.merge_packets(vec![certificate.into()])?; + /// + /// // Check that we have a certification on the userid. + /// assert_eq!(bob.user_attributes().nth(0).unwrap().certifications().len(), + /// 1); + /// # Ok(()) } + pub fn certify(&self, signer: &mut Signer, tpk: &TPK, + signature_type: S, + hash_algo: H, creation_time: T) + -> Result + where S: Into>, + H: Into>, + T: Into> + { + let typ = signature_type.into(); + let typ = match typ { + Some(SignatureType::GenericCertificate) + | Some(SignatureType::PersonaCertificate) + | Some(SignatureType::CasualCertificate) + | Some(SignatureType::PositiveCertificate) => typ.unwrap(), + Some(t) => return Err(Error::InvalidArgument( + format!("Invalid signature type: {}", t)).into()), + None => SignatureType::GenericCertificate, + }; + self.bind(signer, tpk, signature::Builder::new(typ), + // Unwrap arguments to prevent further + // monomorphization of bind(). + hash_algo.into().unwrap_or(HashAlgorithm::SHA512), + creation_time.into().unwrap_or_else(time::now_utc)) + } + + /// Returns a revocation certificate for the user attribute. + /// + /// The revocation signature revokes the binding between this user + /// attribute and `tpk`. `signer` will be used to create a + /// signature with the given reason in `code` and `reason`. + /// `signature_type`. `hash_algo` defaults to SHA512, + /// `creation_time` to the current time. + /// + /// This function adds a creation time subpacket, a issuer + /// fingerprint subpacket, and a issuer subpacket to the + /// signature. + /// + /// # Example + /// + /// ``` + /// # use sequoia_openpgp::{*, constants::*, tpk::*, + /// packet::user_attribute::*}; + /// # f().unwrap(); + /// # fn f() -> Result<()> { + /// // Generate a TPK, and create a keypair from the primary key. + /// let user_attr = UserAttribute::new(&[ + /// Subpacket::Image( + /// Image::Private(100, vec![0, 1, 2].into_boxed_slice())), + /// ])?; + /// let (tpk, _) = TPKBuilder::default() + /// .add_user_attribute(user_attr) + /// .generate()?; + /// let mut keypair = tpk.primary().clone().into_keypair()?; + /// + /// // Generate the revocation for the first and only UserAttribute. + /// let revocation = + /// tpk.user_attributes().nth(0).unwrap().user_attribute() + /// .revoke(&mut keypair, &tpk, + /// ReasonForRevocation::UIDRetired, + /// b"I look different now.", None, None)?; + /// assert_eq!(revocation.sigtype(), SignatureType::CertificateRevocation); + /// + /// // Now merge the revocation signature into the TPK. + /// let tpk = tpk.merge_packets(vec![revocation.clone().into()])?; + /// + /// // Check that it is revoked. + /// let ua = tpk.user_attributes().nth(0).unwrap(); + /// if let RevocationStatus::Revoked(revocations) = ua.revoked(None) { + /// assert_eq!(revocations.len(), 1); + /// assert_eq!(revocations[0], revocation); + /// } else { + /// panic!("UserAttribute is not revoked."); + /// } + /// # Ok(()) } + /// ``` + pub fn revoke(&self, signer: &mut Signer, tpk: &TPK, + code: ReasonForRevocation, reason: &[u8], + hash_algo: H, creation_time: T) + -> Result + where H: Into>, + T: Into> + { + self.bind(signer, tpk, + signature::Builder::new(SignatureType::CertificateRevocation) + .set_reason_for_revocation(code, reason)?, + // Unwrap arguments to prevent further + // monomorphization of bind(). + hash_algo.into().unwrap_or(HashAlgorithm::SHA512), + creation_time.into().unwrap_or_else(time::now_utc)) + } +} diff --git a/openpgp/src/tpk/mod.rs b/openpgp/src/tpk/mod.rs index 18254a78..fabc8082 100644 --- a/openpgp/src/tpk/mod.rs +++ b/openpgp/src/tpk/mod.rs @@ -41,6 +41,7 @@ use constants::ReasonForRevocation; mod lexer; mod grammar; mod builder; +mod bindings; use self::lexer::Lexer; pub use self::lexer::Token; @@ -741,21 +742,6 @@ impl UserIDBinding { RevocationStatus::NotAsFarAsWeKnow } } - - /// Returns a revocation certificate for the user id. - pub fn revoke(&self, signer: &mut Signer, - code: ReasonForRevocation, reason: &[u8]) - -> Result - { - let key = signer.public().clone(); - - signature::Builder::new(SignatureType::CertificateRevocation) - .set_signature_creation_time(time::now_utc())? - .set_issuer_fingerprint(signer.public().fingerprint())? - .set_issuer(signer.public().keyid())? - .set_reason_for_revocation(code, reason)? - .sign_userid_binding(signer, &key, self.userid(), HashAlgorithm::SHA512) - } } /// A User Attribute and any associated signatures. @@ -3799,9 +3785,10 @@ mod test { assert_eq!(RevocationStatus::NotAsFarAsWeKnow, uid.revoked(None)); let mut keypair = tpk.primary().clone().into_keypair().unwrap(); - uid.revoke(&mut keypair, - ReasonForRevocation::UIDRetired, - b"It was the maid :/").unwrap() + uid.userid() + .revoke(&mut keypair, &tpk, + ReasonForRevocation::UIDRetired, + b"It was the maid :/", None, None).unwrap() }; assert_eq!(sig.sigtype(), SignatureType::CertificateRevocation); let tpk = tpk.merge_packets(vec![sig.into()]).unwrap(); -- cgit v1.2.3