summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2024-01-18 12:51:18 +0100
committerJustus Winter <justus@sequoia-pgp.org>2024-01-18 13:07:07 +0100
commit9582ba1a068402e880a1cf3e5b4bb481733953fb (patch)
treed093b1331a1790c6e037b9afc66add041177b0ae
parentb34c523015b1a59cf05391e78d16e394a3c98c84 (diff)
openpgp: Serialize TSKs without secrets with appropriate label.
- Previously, we emitted the PRIVATE KEY label when armoring TSKs that don't in fact contain secrets, confusing users. - See https://gitlab.com/sequoia-pgp/sequoia-sq/-/issues/14 - Fixes #1075.
-rw-r--r--openpgp/src/serialize/cert.rs34
-rw-r--r--openpgp/src/serialize/cert_armored.rs13
2 files changed, 44 insertions, 3 deletions
diff --git a/openpgp/src/serialize/cert.rs b/openpgp/src/serialize/cert.rs
index 2702fda6..764a16ac 100644
--- a/openpgp/src/serialize/cert.rs
+++ b/openpgp/src/serialize/cert.rs
@@ -280,6 +280,17 @@ impl<'a> TSK<'a> {
}
}
+ /// Returns whether we're emitting secret (sub)key packets when
+ /// serializing.
+ ///
+ /// Computes whether we have secrets, taking the filter into
+ /// account, and whether we are emitting stubs. This can be used
+ /// to determine the correct armor label to use.
+ pub(crate) fn emits_secret_key_packets(&self) -> bool {
+ self.emit_stubs
+ || self.cert.keys().secret().any(|skb| (self.filter)(skb.key()))
+ }
+
/// Filters which secret keys to export using the given predicate.
///
/// Note that the given filter replaces any existing filter.
@@ -985,4 +996,27 @@ mod test {
Ok(())
}
+
+ #[test]
+ fn issue_1075() -> Result<()> {
+ let cert = Cert::from_bytes(crate::tests::key("testy.pgp"))?;
+ let tsk = Cert::from_bytes(crate::tests::key("testy-private.pgp"))?;
+
+ // Filters out secrets.
+ let no_secrets = |_| false;
+
+ assert!(! cert.as_tsk().emits_secret_key_packets());
+ assert!(cert.as_tsk().emit_secret_key_stubs(true)
+ .emits_secret_key_packets());
+ assert!(tsk.as_tsk().emits_secret_key_packets());
+ assert!(! tsk.as_tsk().set_filter(no_secrets)
+ .emits_secret_key_packets());
+
+ assert!(cert.armored().to_vec()? != tsk.as_tsk().armored().to_vec()?);
+ assert_eq!(cert.armored().to_vec()?,
+ cert.as_tsk().armored().to_vec()?);
+ assert_eq!(cert.armored().to_vec()?,
+ tsk.as_tsk().set_filter(no_secrets).armored().to_vec()?);
+ Ok(())
+ }
}
diff --git a/openpgp/src/serialize/cert_armored.rs b/openpgp/src/serialize/cert_armored.rs
index a3060e8d..63642954 100644
--- a/openpgp/src/serialize/cert_armored.rs
+++ b/openpgp/src/serialize/cert_armored.rs
@@ -143,8 +143,11 @@ impl<'a> Encoder<'a> {
let (prelude, headers) = match self {
Encoder::Cert(cert) =>
(armor::Kind::PublicKey, cert.armor_headers()),
- Encoder::TSK(ref tsk) =>
- (armor::Kind::SecretKey, tsk.cert.armor_headers()),
+ Encoder::TSK(tsk) => if tsk.emits_secret_key_packets() {
+ (armor::Kind::SecretKey, tsk.cert.armor_headers())
+ } else {
+ (armor::Kind::PublicKey, tsk.cert.armor_headers())
+ },
};
// Convert the Vec<String> into Vec<(&str, &str)>
@@ -202,7 +205,11 @@ impl<'a> MarshalInto for Encoder<'a> {
let word = match self {
Self::Cert(_) => "PUBLIC",
- Self::TSK(_) => "PRIVATE",
+ Self::TSK(tsk) => if tsk.emits_secret_key_packets() {
+ "PRIVATE"
+ } else {
+ "PUBLIC"
+ },
}.len();
"-----BEGIN PGP ".len() + word + " KEY BLOCK-----\n\n".len()