summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@sequoia-pgp.org>2024-01-23 09:13:16 +0100
committerNeal H. Walfield <neal@sequoia-pgp.org>2024-01-23 11:14:04 +0100
commit04edffa4665da09e38e08ff0800a2c29e48924d1 (patch)
tree8509792f16b423b29f81e6f7e07bea39c4b1664f
parent51e75567206d9645a36b2d1df5fac1dee1279129 (diff)
openpgp: Only export a certificate if it is exportable.
- Currently, we are careful to not export components (user IDs, and subkeys) if they don't have any exportable self signatures, however, we still export the primary key. Further, the primary user ID is leaked when the output is armored. - When exporting a certificate, correctly check if the certificate is exportable. If it isn't, don't export anything.
-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()),