diff options
-rw-r--r-- | openpgp/src/cert/builder.rs | 141 | ||||
-rw-r--r-- | openpgp/src/serialize/cert.rs | 8 | ||||
-rw-r--r-- | openpgp/src/serialize/cert_armored.rs | 10 |
3 files changed, 159 insertions, 0 deletions
diff --git a/openpgp/src/cert/builder.rs b/openpgp/src/cert/builder.rs index db864ed0..4b958e2f 100644 --- a/openpgp/src/cert/builder.rs +++ b/openpgp/src/cert/builder.rs @@ -516,6 +516,37 @@ impl CertBuilder<'_> { /// non-exportable certificate, then all of the signatures that it /// creates include the an [Exportable Certification] subpacket /// that is set to `false`. + /// + /// [Exportable Certification]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.11 + /// + /// # Examples + /// + /// When exporting a non-exportable certificate, nothing will be + /// exported. This is also the case when the output is ASCII + /// armored. + /// + /// ``` + /// use sequoia_openpgp as openpgp; + /// use openpgp::Result; + /// use openpgp::cert::prelude::*; + /// use openpgp::parse::Parse; + /// use openpgp::serialize::Serialize; + /// + /// # fn main() -> openpgp::Result<()> { + /// let (cert, _) = + /// CertBuilder::general_purpose(None, Some("alice@example.org")) + /// .set_exportable(false) + /// .generate()?; + /// let mut exported = Vec::new(); + /// cert.armored().export(&mut exported)?; + /// + /// let certs = CertParser::from_bytes(&exported)? + /// .collect::<Result<Vec<Cert>>>()?; + /// assert_eq!(certs.len(), 0); + /// assert_eq!(exported.len(), 0, "{}", String::from_utf8_lossy(&exported)); + /// # Ok(()) + /// # } + /// ``` pub fn set_exportable(mut self, exportable: bool) -> Self { self.exportable = exportable; self @@ -1584,7 +1615,9 @@ mod tests { use crate::Fingerprint; use crate::packet::signature::subpacket::{SubpacketTag, SubpacketValue}; use crate::types::PublicKeyAlgorithm; + use crate::parse::Parse; use crate::policy::StandardPolicy as P; + use crate::serialize::Serialize; #[test] fn all_opts() { @@ -1964,4 +1997,112 @@ mod tests { Ok(()) } + + #[test] + fn non_exportable_cert() -> Result<()> { + // Make sure that when we export a non-exportable cert, + // nothing is exported. + + let (cert, _) = + CertBuilder::general_purpose(None, Some("alice@example.org")) + .set_exportable(false) + .generate()?; + + let (bob, _) = + CertBuilder::general_purpose(None, Some("bob@example.org")) + .generate()?; + + // Have Bob certify Alice's primary User ID with an exportable + // signature. This shouldn't make Alice's certificate + // exportable. + let mut keypair = bob.primary_key().key().clone() + .parts_into_secret()?.into_keypair()?; + let certification = cert.userids().nth(0).unwrap() + .certify(&mut keypair, &cert, + SignatureType::PositiveCertification, + None, None)?; + let cert = cert.insert_packets(certification)?; + + macro_rules! check { + ($cert: expr, $export: ident, $expected: expr) => { + let mut exported = Vec::new(); + $cert.$export(&mut exported)?; + + let certs = CertParser::from_bytes(&exported)? + .collect::<Result<Vec<Cert>>>()?; + + assert_eq!(certs.len(), $expected); + + if $expected == 0 { + assert_eq!(exported.len(), 0, + "{}", String::from_utf8_lossy(&exported)); + } else { + assert!(exported.len() > 0); + } + } + } + + // Binary cert: + check!(cert, export, 0); + check!(cert, serialize, 1); + + // Binary TSK: + check!(cert.as_tsk(), export, 0); + check!(cert.as_tsk(), serialize, 1); + + // Armored cert: + check!(cert.armored(), export, 0); + check!(cert.armored(), serialize, 1); + + // Armored TSK: + check!(cert.as_tsk().armored(), export, 0); + check!(cert.as_tsk().armored(), serialize, 1); + + // Have Alice add a exportable self signature. Now her's + // certificate should be exportable. + let mut keypair = cert.primary_key().key().clone() + .parts_into_secret()?.into_keypair()?; + let certification = cert.userids().nth(0).unwrap() + .certify(&mut keypair, &cert, + SignatureType::PositiveCertification, + None, None)?; + let cert = cert.insert_packets(certification)?; + + macro_rules! check { + ($cert: expr, $export: ident, $expected: expr) => { + let mut exported = Vec::new(); + $cert.$export(&mut exported)?; + + let certs = CertParser::from_bytes(&exported)? + .collect::<Result<Vec<Cert>>>()?; + + assert_eq!(certs.len(), $expected); + + if $expected == 0 { + assert_eq!(exported.len(), 0, + "{}", String::from_utf8_lossy(&exported)); + } else { + assert!(exported.len() > 0); + } + } + } + + // Binary cert: + check!(cert, export, 1); + check!(cert, serialize, 1); + + // Binary TSK: + check!(cert.as_tsk(), export, 1); + check!(cert.as_tsk(), serialize, 1); + + // Armored cert: + check!(cert.armored(), export, 1); + check!(cert.armored(), serialize, 1); + + // Armored TSK: + check!(cert.as_tsk().armored(), export, 1); + check!(cert.as_tsk().armored(), serialize, 1); + + Ok(()) + } } diff --git a/openpgp/src/serialize/cert.rs b/openpgp/src/serialize/cert.rs index 3278ea2b..4d98569a 100644 --- a/openpgp/src/serialize/cert.rs +++ b/openpgp/src/serialize/cert.rs @@ -91,6 +91,10 @@ impl Cert { fn serialize_common(&self, o: &mut dyn std::io::Write, export: bool) -> Result<()> { + if export && ! self.exportable() { + return Ok(()) + } + let primary = self.primary_key(); PacketRef::PublicKey(primary.key()) .serialize(o)?; @@ -552,6 +556,10 @@ impl<'a> TSK<'a> { } }; + if export && ! self.cert.exportable() { + return Ok(()) + } + let primary = self.cert.primary_key(); serialize_key(o, primary.key().into(), Tag::PublicKey, Tag::SecretKey)?; diff --git a/openpgp/src/serialize/cert_armored.rs b/openpgp/src/serialize/cert_armored.rs index 63642954..7cbfe3a4 100644 --- a/openpgp/src/serialize/cert_armored.rs +++ b/openpgp/src/serialize/cert_armored.rs @@ -140,6 +140,16 @@ impl<'a> Encoder<'a> { fn serialize_common(&self, o: &mut dyn io::Write, export: bool) -> Result<()> { + if export { + let exportable = match self { + Encoder::Cert(cert) => cert.exportable(), + Encoder::TSK(tsk) => tsk.cert.exportable(), + }; + if ! exportable { + return Ok(()); + } + } + let (prelude, headers) = match self { Encoder::Cert(cert) => (armor::Kind::PublicKey, cert.armor_headers()), |