diff options
author | Neal H. Walfield <neal@pep.foundation> | 2020-05-27 14:59:19 +0200 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2020-05-27 15:34:03 +0200 |
commit | ac94c6e3bb24e0d425f23bf271ba6d23467acfb9 (patch) | |
tree | 402503ffee61c701fc19ced5a41edab43a20d9e5 | |
parent | cc0bb0817a0fbae5ba6c428780ad4131ab0efd0d (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.rs | 30 | ||||
-rw-r--r-- | openpgp/src/packet/mod.rs | 143 |
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> { |