summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2020-05-27 14:59:19 +0200
committerNeal H. Walfield <neal@pep.foundation>2020-05-27 15:34:03 +0200
commitac94c6e3bb24e0d425f23bf271ba6d23467acfb9 (patch)
tree402503ffee61c701fc19ced5a41edab43a20d9e5
parentcc0bb0817a0fbae5ba6c428780ad4131ab0efd0d (diff)
openpgp: Add methods to Key to handle encrypted secret key material
- Add `Key4::decrypt_secret`, `Key4::encrypt_secret`, `Key::decrypt_secret`, and `Key::encrypt_secret` to make it easier to deal with password-protected secret key material.
-rw-r--r--openpgp/src/packet/key.rs30
-rw-r--r--openpgp/src/packet/mod.rs143
2 files changed, 172 insertions, 1 deletions
diff --git a/openpgp/src/packet/key.rs b/openpgp/src/packet/key.rs
index b56f36e0..59de792a 100644
--- a/openpgp/src/packet/key.rs
+++ b/openpgp/src/packet/key.rs
@@ -1434,6 +1434,36 @@ impl<R> Key4<SecretParts, R>
(self.parts_into_secret().expect("secret just set"),
old.expect("Key<SecretParts, _> has a secret key material"))
}
+
+ /// Decrypts the secret key material using `password`.
+ ///
+ /// In OpenPGP, secret key material can be [protected with a
+ /// password]. The password is usually hardened using a [KDF].
+ ///
+ /// Refer to the documentation of [`Key::decrypt_secret`] for
+ /// details.
+ ///
+ /// This function returns an error if the secret key material is
+ /// not encrypted or the password is incorrect.
+ ///
+ /// [protected with a password]: https://tools.ietf.org/html/rfc4880#section-3.7
+ /// [`Key::decrypt_secret`]: enum.Key.html#method.decrypt_secret
+ pub fn decrypt_secret(mut self, password: &Password) -> Result<Self> {
+ let pk_algo = self.pk_algo;
+ self.secret_mut().decrypt_in_place(pk_algo, password)?;
+ Ok(self)
+ }
+
+ /// Encrypts the secret key material using `password`.
+ ///
+ /// This returns an error if the secret key material is already
+ /// encrypted.
+ pub fn encrypt_secret(mut self, password: &Password)
+ -> Result<Key4<SecretParts, R>>
+ {
+ self.secret_mut().encrypt_in_place(password)?;
+ Ok(self)
+ }
}
impl<P, R> From<Key4<P, R>> for super::Key<P, R>
diff --git a/openpgp/src/packet/mod.rs b/openpgp/src/packet/mod.rs
index 156b94dc..a1f6480f 100644
--- a/openpgp/src/packet/mod.rs
+++ b/openpgp/src/packet/mod.rs
@@ -173,7 +173,10 @@ pub use container::Body;
pub mod prelude;
-use crate::crypto::KeyPair;
+use crate::crypto::{
+ KeyPair,
+ Password,
+};
mod tag;
pub use self::tag::Tag;
@@ -1342,6 +1345,144 @@ impl<R: key::KeyRole> Key<key::SecretParts, R> {
KeyPair::new(key.role_into_unspecified(), secret)
}
+
+ /// Decrypts the secret key material.
+ ///
+ /// In OpenPGP, secret key material can be [protected with a
+ /// password]. The password is usually hardened using a [KDF].
+ ///
+ /// [protected with a password]: https://tools.ietf.org/html/rfc4880#section-5.5.3
+ /// [KDF]: https://tools.ietf.org/html/rfc4880#section-3.7
+ ///
+ /// This function takes ownership of the `Key`, decrypts the
+ /// secret key material using the password, and returns a new key
+ /// whose secret key material is not password protected.
+ ///
+ /// If the secret key material is not password protected or if the
+ /// password is wrong, this function returns an error.
+ ///
+ /// # Examples
+ ///
+ /// Sign a new revocation certificate using a password-protected
+ /// key:
+ ///
+ /// ```rust
+ /// use sequoia_openpgp as openpgp;
+ /// # use openpgp::Result;
+ /// use openpgp::cert::prelude::*;
+ /// use openpgp::types::ReasonForRevocation;
+ ///
+ /// # fn main() -> Result<()> {
+ /// // Generate a certificate whose secret key material is
+ /// // password protected.
+ /// let (cert, _) =
+ /// CertBuilder::general_purpose(None,
+ /// Some("Alice Lovelace <alice@example.org>"))
+ /// .set_password(Some("1234".into()))
+ /// .generate()?;
+ ///
+ /// // Use the secret key material to sign a revocation certificate.
+ /// let key = cert.primary_key().key().clone().parts_into_secret()?;
+ ///
+ /// // We can't turn it into a keypair without decrypting it.
+ /// assert!(key.clone().into_keypair().is_err());
+ ///
+ /// // And, we need to use the right password.
+ /// assert!(key.clone()
+ /// .decrypt_secret(&"correct horse battery staple".into())
+ /// .is_err());
+ ///
+ /// // Let's do it right:
+ /// let mut keypair = key.decrypt_secret(&"1234".into())?.into_keypair()?;
+ /// let rev = cert.revoke(&mut keypair,
+ /// ReasonForRevocation::KeyCompromised,
+ /// b"It was the maid :/")?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn decrypt_secret(self, password: &Password) -> Result<Self>
+ {
+ match self {
+ Key::V4(k) => Ok(Key::V4(k.decrypt_secret(password)?)),
+ // Match exhaustively so that when we add support for a
+ // new variant, the compiler reminds us to add support for
+ // it here.
+ Key::__Nonexhaustive => unreachable!(),
+ }
+ }
+
+ /// Encrypts the secret key material.
+ ///
+ /// In OpenPGP, secret key material can be [protected with a
+ /// password]. The password is usually hardened using a [KDF].
+ ///
+ /// [protected with a password]: https://tools.ietf.org/html/rfc4880#section-5.5.3
+ /// [KDF]: https://tools.ietf.org/html/rfc4880#section-3.7
+ ///
+ /// This function takes ownership of the `Key`, encrypts the
+ /// secret key material using the password, and returns a new key
+ /// whose secret key material is protected with the password.
+ ///
+ /// If the secret key material is already password protected, this
+ /// function returns an error.
+ ///
+ /// # Examples
+ ///
+ /// Encrypt the primary key:
+ ///
+ /// ```rust
+ /// use sequoia_openpgp as openpgp;
+ /// # use openpgp::Result;
+ /// use openpgp::cert::prelude::*;
+ /// use openpgp::packet::Packet;
+ ///
+ /// # fn main() -> Result<()> {
+ /// // Generate a certificate whose secret key material is
+ /// // not password protected.
+ /// let (cert, _) =
+ /// CertBuilder::general_purpose(None,
+ /// Some("Alice Lovelace <alice@example.org>"))
+ /// .generate()?;
+ /// let key = cert.primary_key().key().clone().parts_into_secret()?;
+ /// assert!(key.has_unencrypted_secret());
+ ///
+ /// // Encrypt the key's secret key material.
+ /// let key = key.encrypt_secret(&"1234".into())?;
+ /// assert!(! key.has_unencrypted_secret());
+ ///
+ /// // Merge it into the certificate. Note: `Cert::merge_packets`
+ /// // prefers added versions of keys. So, the encrypted version
+ /// // will override the decrypted version.
+ /// let cert = cert.merge_packets(Packet::from(key))?;
+ ///
+ /// // Now the primary key's secret key material is encrypted.
+ /// let key = cert.primary_key().key().parts_as_secret()?;
+ /// assert!(! key.has_unencrypted_secret());
+ ///
+ /// // We can't turn it into a keypair without decrypting it.
+ /// assert!(key.clone().into_keypair().is_err());
+ ///
+ /// // And, we need to use the right password.
+ /// assert!(key.clone()
+ /// .decrypt_secret(&"correct horse battery staple".into())
+ /// .is_err());
+ ///
+ /// // Let's do it right:
+ /// let mut keypair = key.clone()
+ /// .decrypt_secret(&"1234".into())?.into_keypair()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn encrypt_secret(self, password: &Password) -> Result<Self>
+ {
+ match self {
+ Key::V4(k) => Ok(Key::V4(k.encrypt_secret(password)?)),
+ // Match exhaustively so that when we add support for a
+ // new variant, the compiler reminds us to add support for
+ // it here.
+ Key::__Nonexhaustive => unreachable!(),
+ }
+ }
}
impl<R: key::KeyRole> key::Key4<key::SecretParts, R> {