summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--openpgp/src/cert/builder.rs141
-rw-r--r--openpgp/src/serialize/cert.rs8
-rw-r--r--openpgp/src/serialize/cert_armored.rs10
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()),