summaryrefslogtreecommitdiffstats
path: root/openpgp
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2019-04-26 14:34:36 +0200
committerJustus Winter <justus@sequoia-pgp.org>2019-04-29 14:55:56 +0200
commit1fa018acc90194e1cd1ddf2bd0c6706000a3bc9e (patch)
treec29ecc4aac1d2d85e1ff08a111d1462b74c6f3e8 /openpgp
parentd53cb0c4df33ebabd1af7e97e8469ed159763a6e (diff)
openpgp: New TSK type.
- With a1e226f8f1418de43e577fdaa1d087b68bbb09ae in place, we have a more general way to add components to a TPK. Retire the current `TSK` type and replace it with a thin shim that only allows serialization of secret keys. - Fixes #107.
Diffstat (limited to 'openpgp')
-rw-r--r--openpgp/src/autocrypt.rs4
-rw-r--r--openpgp/src/lib.rs13
-rw-r--r--openpgp/src/tpk/mod.rs14
-rw-r--r--openpgp/src/tpk/tsk.rs280
-rw-r--r--openpgp/src/tsk.rs3
-rw-r--r--openpgp/tests/for-each-artifact.rs5
6 files changed, 304 insertions, 15 deletions
diff --git a/openpgp/src/autocrypt.rs b/openpgp/src/autocrypt.rs
index 38567743..3353bbf7 100644
--- a/openpgp/src/autocrypt.rs
+++ b/openpgp/src/autocrypt.rs
@@ -486,9 +486,7 @@ impl AutocryptSetupMessage {
&[ (&"Autocrypt-Prefer-Encrypt"[..],
self.prefer_encrypt().unwrap_or(&"nopreference"[..])) ])?;
- // XXX
- let tsk = self.tpk.clone().into_tsk();
- tsk.serialize(&mut w)?;
+ self.tpk.as_tsk().serialize(&mut w)?;
Ok(w.finalize()?)
}
diff --git a/openpgp/src/lib.rs b/openpgp/src/lib.rs
index 584ae406..f9bf609a 100644
--- a/openpgp/src/lib.rs
+++ b/openpgp/src/lib.rs
@@ -126,9 +126,6 @@ use constants::{
mod fingerprint;
mod keyid;
-
-mod tsk;
-pub use tsk::TSK;
#[cfg(test)]
use std::path::PathBuf;
@@ -394,6 +391,16 @@ pub struct PacketPile {
///
/// [RFC 4880, section 11.1]: https://tools.ietf.org/html/rfc4880#section-11.1
///
+/// # Secret keys
+///
+/// Any key in a `TPK` may have a secret key attached to it. To
+/// protect secret keys from being leaked, secret keys are not written
+/// out if a `TPK` is serialized. To also serialize the secret keys,
+/// you need to use [`TPK::as_tsk()`] to get an object that writes
+/// them out during serialization.
+///
+/// [`TPK::as_tsk()`]: #method.as_tsk
+///
/// # Example
///
/// ```rust
diff --git a/openpgp/src/tpk/mod.rs b/openpgp/src/tpk/mod.rs
index 35ed123d..305aa8f8 100644
--- a/openpgp/src/tpk/mod.rs
+++ b/openpgp/src/tpk/mod.rs
@@ -31,7 +31,6 @@ use {
TPK,
KeyID,
Fingerprint,
- TSK,
};
use parse::{Parse, PacketParserResult, PacketParser};
use serialize::{Serialize, SerializeInto, SerializeKey};
@@ -42,6 +41,8 @@ mod lexer;
mod grammar;
mod builder;
mod bindings;
+mod tsk;
+pub use self::tsk::TSK;
use self::lexer::Lexer;
pub use self::lexer::Token;
@@ -2823,10 +2824,13 @@ impl TPK {
TPK::from_packet_pile(PacketPile::from(combined))
}
- /// Cast the public key into a secret key that allows using the secret
- /// parts of the containing keys.
- pub fn into_tsk(self) -> TSK {
- TSK::from_tpk(self)
+ /// Derive a [`TSK`] object from this key.
+ ///
+ /// This object writes out secret keys during serialization.
+ ///
+ /// [`TSK`]: tpk/struct.TSK.html
+ pub fn as_tsk<'a>(&'a self) -> TSK<'a> {
+ TSK::new(self)
}
/// Returns whether at least one of the keys includes a secret
diff --git a/openpgp/src/tpk/tsk.rs b/openpgp/src/tpk/tsk.rs
new file mode 100644
index 00000000..978affcb
--- /dev/null
+++ b/openpgp/src/tpk/tsk.rs
@@ -0,0 +1,280 @@
+use std::io;
+
+use Result;
+use TPK;
+use packet::{Key, Tag};
+use serialize::{Serialize, SerializeKey};
+
+/// A reference to a TPK that allows serialization of secret keys.
+///
+/// To avoid accidental leakage `TPK::serialize()` skips secret keys.
+/// To serialize `TPK`s with secret keys, use [`TPK::as_tsk()`] to
+/// create a `TSK`, which is a shim on top of the `TPK`, and serialize
+/// this.
+///
+/// [`TPK::as_tsk()`]: ../struct.TPK.html#method.as_tsk
+///
+/// # Example
+/// ```
+/// # use sequoia_openpgp::{*, tpk::*, parse::Parse, serialize::Serialize};
+/// # f().unwrap();
+/// # fn f() -> Result<()> {
+/// let (tpk, _) = TPKBuilder::default().generate()?;
+/// assert!(tpk.is_tsk());
+///
+/// let mut buf = Vec::new();
+/// tpk.as_tsk().serialize(&mut buf)?;
+///
+/// let tpk_ = TPK::from_bytes(&buf)?;
+/// assert!(tpk_.is_tsk());
+/// assert_eq!(tpk, tpk_);
+/// # Ok(()) }
+pub struct TSK<'a> {
+ tpk: &'a TPK,
+ filter: Option<Box<'a + Fn(&'a Key) -> bool>>,
+}
+
+impl<'a> TSK<'a> {
+ /// Creates a new view for the given `TPK`.
+ pub(crate) fn new(tpk: &'a TPK) -> Self {
+ Self {
+ tpk: tpk,
+ filter: None,
+ }
+ }
+
+ /// Filters which secret keys to export using the given predicate.
+ ///
+ /// Note that the given filter replaces any existing filter.
+ ///
+ /// # Example
+ /// ```
+ /// # use sequoia_openpgp::{*, tpk::*, parse::Parse, serialize::Serialize};
+ /// # f().unwrap();
+ /// # fn f() -> Result<()> {
+ /// let (tpk, _) = TPKBuilder::default().add_signing_subkey().generate()?;
+ /// assert_eq!(tpk.keys_valid().secret(true).count(), 2);
+ ///
+ /// // Only write out the primary key's secret.
+ /// let mut buf = Vec::new();
+ /// tpk.as_tsk().set_filter(|k| k == tpk.primary()).serialize(&mut buf)?;
+ ///
+ /// let tpk_ = TPK::from_bytes(&buf)?;
+ /// assert_eq!(tpk_.keys_valid().secret(true).count(), 1);
+ /// assert!(tpk_.primary().secret().is_some());
+ /// # Ok(()) }
+ pub fn set_filter<P>(mut self, predicate: P) -> Self
+ where P: 'a + Fn(&'a Key) -> bool
+ {
+ self.filter = Some(Box::new(predicate));
+ self
+ }
+}
+
+impl<'a> Serialize for TSK<'a> {
+ fn serialize<W: io::Write>(&self, o: &mut W) -> Result<()> {
+ // Serializes public or secret key depending on the filter.
+ let serialize_key = |o: &mut W, key: &'a Key, tag_public, tag_secret| {
+ key.serialize(o,
+ if self.filter.as_ref().map(
+ |f| f(key)).unwrap_or(true)
+ {
+ tag_secret
+ } else {
+ tag_public
+ })
+ };
+ serialize_key(o, &self.tpk.primary, Tag::PublicKey, Tag::SecretKey)?;
+
+ for s in self.tpk.primary_selfsigs.iter() {
+ s.serialize(o)?;
+ }
+ for s in self.tpk.primary_self_revocations.iter() {
+ s.serialize(o)?;
+ }
+ for s in self.tpk.primary_certifications.iter() {
+ s.serialize(o)?;
+ }
+ for s in self.tpk.primary_other_revocations.iter() {
+ s.serialize(o)?;
+ }
+
+ for u in self.tpk.userids() {
+ u.userid().serialize(o)?;
+ for s in u.self_revocations() {
+ s.serialize(o)?;
+ }
+ for s in u.selfsigs() {
+ s.serialize(o)?;
+ }
+ for s in u.other_revocations() {
+ s.serialize(o)?;
+ }
+ for s in u.certifications() {
+ s.serialize(o)?;
+ }
+ }
+
+ for u in self.tpk.user_attributes() {
+ u.user_attribute().serialize(o)?;
+ for s in u.self_revocations() {
+ s.serialize(o)?;
+ }
+ for s in u.selfsigs() {
+ s.serialize(o)?;
+ }
+ for s in u.other_revocations() {
+ s.serialize(o)?;
+ }
+ for s in u.certifications() {
+ s.serialize(o)?;
+ }
+ }
+
+ for k in self.tpk.subkeys() {
+ serialize_key(o, k.subkey(), Tag::PublicSubkey, Tag::SecretSubkey)?;
+ for s in k.self_revocations() {
+ s.serialize(o)?;
+ }
+ for s in k.selfsigs() {
+ s.serialize(o)?;
+ }
+ for s in k.other_revocations() {
+ s.serialize(o)?;
+ }
+ for s in k.certifications() {
+ s.serialize(o)?;
+ }
+ }
+
+ for u in self.tpk.unknowns.iter() {
+ u.unknown.serialize(o)?;
+
+ for s in u.sigs.iter() {
+ s.serialize(o)?;
+ }
+ }
+
+ for s in self.tpk.bad.iter() {
+ s.serialize(o)?;
+ }
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use parse::Parse;
+ use serialize::Serialize;
+
+ fn test_tpk(name: &str) -> Result<TPK> {
+ let path = format!("tests/data/keys/{}.pgp", name);
+ TPK::from_file(path)
+ }
+
+ fn test_tsk(name: &str) -> Result<TPK> {
+ let path = format!("tests/data/keys/{}-private.pgp", name);
+ TPK::from_file(path)
+ }
+
+ const PUBLIC_TESTS: &[&str] = &[
+ "dennis-simon-anton",
+ "dsa2048-elgamal3072",
+ "emmelie-dorothea-dina-samantha-awina-ed25519",
+ "erika-corinna-daniela-simone-antonia-nistp256",
+ "erika-corinna-daniela-simone-antonia-nistp384",
+ "erika-corinna-daniela-simone-antonia-nistp521",
+ "testy-new",
+ "testy",
+ "neal",
+ "dkg-sigs-out-of-order",
+ ];
+ const SECRET_TESTS: &[&str] = &[
+ "dennis-simon-anton",
+ "dsa2048-elgamal3072",
+ "emmelie-dorothea-dina-samantha-awina-ed25519",
+ "erika-corinna-daniela-simone-antonia-nistp256",
+ "erika-corinna-daniela-simone-antonia-nistp384",
+ "erika-corinna-daniela-simone-antonia-nistp521",
+ "testy-new",
+ "testy-nistp256",
+ "testy-nistp384",
+ "testy-nistp521",
+ "testy",
+ ];
+
+ /// Demonstrates that public keys and all components are
+ /// serialized.
+ #[test]
+ fn roundtrip_tpk() {
+ for test in PUBLIC_TESTS {
+ let tpk = match test_tpk(dbg!(test)) {
+ Ok(t) => t,
+ Err(_) => continue,
+ };
+ assert!(! tpk.is_tsk());
+
+ let mut buf = Vec::new();
+ tpk.as_tsk().serialize(&mut buf).unwrap();
+ let tpk_ = TPK::from_bytes(&buf).unwrap();
+
+ assert_eq!(tpk, tpk_, "roundtripping {}.pgp failed", test);
+ }
+ }
+
+ /// Demonstrates that secret keys and all components are
+ /// serialized.
+ #[test]
+ fn roundtrip_tsk() {
+ for test in SECRET_TESTS {
+ let tpk = test_tsk(test).unwrap();
+ assert!(tpk.is_tsk());
+
+ let mut buf = Vec::new();
+ tpk.as_tsk().serialize(&mut buf).unwrap();
+ let tpk_ = TPK::from_bytes(&buf).unwrap();
+
+ assert_eq!(tpk, tpk_, "roundtripping {}-private.pgp failed", test);
+
+ // This time, use a trivial filter.
+ let mut buf = Vec::new();
+ tpk.as_tsk().set_filter(|_| true).serialize(&mut buf).unwrap();
+ let tpk_ = TPK::from_bytes(&buf).unwrap();
+
+ assert_eq!(tpk, tpk_, "roundtripping {}-private.pgp failed", test);
+ }
+ }
+
+ /// Demonstrates that TSK::serialize() with the right filter
+ /// reduces to TPK::serialize().
+ #[test]
+ fn reduce_to_tpk_serialize() {
+ for test in SECRET_TESTS {
+ let tpk = test_tsk(test).unwrap();
+ assert!(tpk.is_tsk());
+
+ // First, use TPK::serialize().
+ let mut buf_tpk = Vec::new();
+ tpk.serialize(&mut buf_tpk).unwrap();
+
+ // When serializing using TSK::serialize, filter out all
+ // secret keys.
+ let mut buf_tsk = Vec::new();
+ tpk.as_tsk().set_filter(|_| false).serialize(&mut buf_tsk).unwrap();
+
+ // Check for equality.
+ let tpk_ = TPK::from_bytes(&buf_tpk).unwrap();
+ let tsk_ = TPK::from_bytes(&buf_tsk).unwrap();
+ assert_eq!(tpk_, tsk_,
+ "reducing failed on {}-private.pgp: not TPK::eq",
+ test);
+
+ // Check for identinty.
+ assert_eq!(buf_tpk, buf_tsk,
+ "reducing failed on {}-private.pgp: serialized identity",
+ test);
+ }
+ }
+}
diff --git a/openpgp/src/tsk.rs b/openpgp/src/tsk.rs
index 676e3c30..49c4da27 100644
--- a/openpgp/src/tsk.rs
+++ b/openpgp/src/tsk.rs
@@ -1,3 +1,6 @@
+//! This file is no longer used, but serves as reference for
+//! high-level operations that can be done on TPKs.
+
use std::borrow::Cow;
use std::io;
use std::ops::{Deref, DerefMut};
diff --git a/openpgp/tests/for-each-artifact.rs b/openpgp/tests/for-each-artifact.rs
index d1744dd5..8979ccaa 100644
--- a/openpgp/tests/for-each-artifact.rs
+++ b/openpgp/tests/for-each-artifact.rs
@@ -34,10 +34,7 @@ mod for_each_artifact {
};
let mut v = Vec::new();
- // Temporarily convert to TSK to serialize secrets.
- let p = p.into_tsk();
- p.serialize(&mut v)?;
- let p = p.into_tpk();
+ p.as_tsk().serialize(&mut v)?;
let q = openpgp::TPK::from_bytes(&v)?;
assert_eq!(p, q, "roundtripping {:?} failed", src);
Ok(())