diff options
author | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-06-17 02:19:18 +0200 |
---|---|---|
committer | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-08-13 15:19:58 +0200 |
commit | d673821c1467a0ddc9ff3e1fa755ccb718e36125 (patch) | |
tree | fe6285cf80b0c3460f46821f2e78592a0d023c39 /openpgp/src/crypto/backend | |
parent | 18b00b17a57f824ad5a872fffffa54ad09444647 (diff) |
openpgp: Implement ECDH and RSA encryption
Diffstat (limited to 'openpgp/src/crypto/backend')
-rw-r--r-- | openpgp/src/crypto/backend/cng/asymmetric.rs | 94 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/cng/ecdh.rs | 264 |
2 files changed, 350 insertions, 8 deletions
diff --git a/openpgp/src/crypto/backend/cng/asymmetric.rs b/openpgp/src/crypto/backend/cng/asymmetric.rs index a8d8c235..bab593f0 100644 --- a/openpgp/src/crypto/backend/cng/asymmetric.rs +++ b/openpgp/src/crypto/backend/cng/asymmetric.rs @@ -300,14 +300,104 @@ impl Decryptor for KeyPair { ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>, ) -> Result<SessionKey> { - unimplemented!() + use crate::PublicKeyAlgorithm::*; + + self.secret().map( + |secret| Ok(match (self.public().mpis(), secret, ciphertext) + { + (mpi::PublicKey::RSA { ref e, ref n }, + mpi::SecretKeyMaterial::RSA { ref p, ref q, ref d, .. }, + mpi::Ciphertext::RSA { ref c }) => { + use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId}; + use cng::asymmetric::{AsymmetricKey, Private, Rsa}; + use cng::asymmetric::EncryptionPadding; + use cng::key_blob::RsaKeyPrivatePayload; + + let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Rsa)?; + let key = AsymmetricKey::<Rsa, Private>::import_from_parts( + &provider, + &RsaKeyPrivatePayload { + modulus: n.value(), + pub_exp: e.value(), + prime1: p.value(), + prime2: q.value(), + } + )?; + + let decrypted = key.decrypt(Some(EncryptionPadding::Pkcs1), c.value())?; + + SessionKey::from(decrypted) + } + + (mpi::PublicKey::ElGamal { .. }, + mpi::SecretKeyMaterial::ElGamal { .. }, + mpi::Ciphertext::ElGamal { .. }) => + return Err( + Error::UnsupportedPublicKeyAlgorithm(ElGamalEncrypt).into()), + + (mpi::PublicKey::ECDH{ .. }, + mpi::SecretKeyMaterial::ECDH { .. }, + mpi::Ciphertext::ECDH { .. }) => + crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext)?, + + (public, secret, ciphertext) => + return Err(Error::InvalidOperation(format!( + "unsupported combination of key pair {:?}/{:?} \ + and ciphertext {:?}", + public, secret, ciphertext)).into()), + })) } } impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { /// Encrypts the given data with this key. pub fn encrypt(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { - unimplemented!() + use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId}; + use cng::asymmetric::{AsymmetricKey, Public, Rsa}; + use cng::key_blob::RsaKeyPublicPayload; + + use PublicKeyAlgorithm::*; + + #[allow(deprecated)] + match self.pk_algo() { + RSAEncryptSign | RSAEncrypt => { + // Extract the public recipient. + match self.mpis() { + mpi::PublicKey::RSA { e, n } => { + // The ciphertext has the length of the modulus. + let ciphertext_len = n.value().len(); + if data.len() + 11 > ciphertext_len { + return Err(Error::InvalidArgument( + "Plaintext data too large".into()).into()); + } + + let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Rsa)?; + let key = AsymmetricKey::<Rsa, Public>::import_from_parts( + &provider, + &RsaKeyPublicPayload { + modulus: n.value(), + pub_exp: e.value(), + } + )?; + + let padding = win_crypto_ng::asymmetric::EncryptionPadding::Pkcs1; + let ciphertext = key.encrypt(Some(padding), data)?; + + Ok(mpi::Ciphertext::RSA { + c: mpi::MPI::new(ciphertext.as_ref()), + }) + }, + pk => { + Err(Error::MalformedPacket( + format!( + "Key: Expected RSA public key, got {:?}", + pk)).into()) + }, + } + }, + ECDH => crate::crypto::ecdh::encrypt(self.parts_as_public(), data), + algo => Err(Error::UnsupportedPublicKeyAlgorithm(algo).into()), + } } /// Verifies the given signature. diff --git a/openpgp/src/crypto/backend/cng/ecdh.rs b/openpgp/src/crypto/backend/cng/ecdh.rs index c2014a75..98e5cfcc 100644 --- a/openpgp/src/crypto/backend/cng/ecdh.rs +++ b/openpgp/src/crypto/backend/cng/ecdh.rs @@ -1,21 +1,150 @@ //! Elliptic Curve Diffie-Hellman. -#![allow(unused_variables)] -use crate::crypto::mpi::{Ciphertext, SecretKeyMaterial}; +use crate::crypto::mem::Protected; +use crate::crypto::mpi::{self, Ciphertext, SecretKeyMaterial, MPI}; use crate::crypto::SessionKey; use crate::packet::{key, Key}; -use crate::Result; +use crate::types::Curve; +use crate::{Error, Result}; + +use crate::crypto::ecdh::{encrypt_shared, decrypt_shared}; + +use win_crypto_ng as cng; +use cng::asymmetric::{Ecdh, AsymmetricKey, Export}; +use cng::asymmetric::{Public, Private, AsymmetricAlgorithm, AsymmetricAlgorithmId}; +use cng::asymmetric::ecc::{NamedCurve, NistP256, NistP384, NistP521, Curve25519}; +use cng::key_blob::{EccKeyPublicPayload, EccKeyPrivatePayload}; /// Wraps a session key using Elliptic Curve Diffie-Hellman. #[allow(non_snake_case)] pub fn encrypt<R>( recipient: &Key<key::PublicParts, R>, session_key: &SessionKey, -) -> Result<Ciphertext> +) -> Result<mpi::Ciphertext> where R: key::KeyRole, { - unimplemented!() + let (curve, q) = match recipient.mpis() { + mpi::PublicKey::ECDH { curve, q, .. } => (curve, q), + _ => return Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), + }; + + match curve { + Curve::Cv25519 => { + // Obtain the authenticated recipient public key R + let R = q.decode_point(curve)?.0; + let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519))?; + let recipient_key = AsymmetricKey::<Ecdh<Curve25519>, Public>::import_from_parts( + &provider, + R, + )?; + + // Generate an ephemeral key pair {v, V=vG} + let ephemeral = AsymmetricKey::builder(Ecdh(Curve25519)).build().unwrap(); + + // Compute the public key. We need to add an encoding + // octet in front of the key. + let blob = ephemeral.export().unwrap(); + let mut VB = [0; 33]; + VB[0] = 0x40; + &mut VB[1..].copy_from_slice(blob.x()); + let VB = MPI::new(&VB); + + // Compute the shared point S = vR; + let secret = cng::asymmetric::agreement::secret_agreement(&ephemeral, &recipient_key)?; + let mut S = Protected::from(secret.derive_raw()?); + // Returned secret is little-endian, flip it to big-endian + S.reverse(); + + encrypt_shared(recipient, session_key, VB, &S) + } + Curve::NistP256 | Curve::NistP384 | Curve::NistP521 => { + let (Rx, Ry) = q.decode_point(curve)?; + + let (VB, S) = match curve { + Curve::NistP256 => { + // Obtain the authenticated recipient public key R and + // generate an ephemeral private key v. + let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP256))?; + let R = AsymmetricKey::<Ecdh<NistP256>, Public>::import_from_parts( + &provider, + &EccKeyPublicPayload { x: Rx, y: Ry }, + )?; + let v = AsymmetricKey::builder(Ecdh(NistP256)).build().unwrap(); + let VB = v.export()?; + let VB = MPI::new_point(&VB.x(), &VB.y(), 256); + // Compute the shared point S = vR + let secret = cng::asymmetric::agreement::secret_agreement(&v, &R)?; + // Get the X coordinate + let mut S = Protected::from(secret.derive_raw()?); + // Returned secret is little-endian, flip it to big-endian + S.reverse(); + + assert_eq!(S.len(), 32); + + (VB, S) + } + Curve::NistP384 => { + // Obtain the authenticated recipient public key R and + // generate an ephemeral private key v. + let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP384))?; + let R = AsymmetricKey::<Ecdh<NistP384>, Public>::import_from_parts( + &provider, + &EccKeyPublicPayload { x: Rx, y: Ry }, + )?; + let v = AsymmetricKey::builder(Ecdh(NistP384)).build().unwrap(); + let VB = v.export()?; + let VB = MPI::new_point(&VB.x(), &VB.y(), 384); + // Compute the shared point S = vR + let secret = cng::asymmetric::agreement::secret_agreement(&v, &R)?; + // Get the X coordinate + let mut S = Protected::from(secret.derive_raw()?); + // Returned secret is little-endian, flip it to big-endian + S.reverse(); + + assert_eq!(S.len(), 48); + + (VB, S) + } + Curve::NistP521 => { + // Obtain the authenticated recipient public key R and + // generate an ephemeral private key v. + let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP521))?; + let R = AsymmetricKey::<Ecdh<NistP521>, Public>::import_from_parts( + &provider, + &EccKeyPublicPayload { x: Rx, y: Ry }, + )?; + let v = AsymmetricKey::builder(Ecdh(NistP521)).build().unwrap(); + let VB = v.export()?; + let VB = MPI::new_point(&VB.x(), &VB.y(), 521); + // Compute the shared point S = vR + let secret = cng::asymmetric::agreement::secret_agreement(&v, &R)?; + + // Get the X coordinate + let mut S = Protected::from(secret.derive_raw()?); + // Returned secret is little-endian, flip it to big-endian + S.reverse(); + + assert_eq!(S.len(), 66); + + (VB, S) + } + _ => unreachable!(), + }; + + encrypt_shared(recipient, session_key, VB, &S) + } + + // Not implemented in Nettle + Curve::BrainpoolP256 | Curve::BrainpoolP512 => + Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), + + // N/A + Curve::Unknown(_) | Curve::Ed25519 => + Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), + + Curve::__Nonexhaustive => unreachable!(), + } } /// Unwraps a session key using Elliptic Curve Diffie-Hellman. @@ -28,5 +157,128 @@ pub fn decrypt<R>( where R: key::KeyRole, { - unimplemented!() + let (curve, scalar, e) = match (recipient.mpis(), recipient_sec, ciphertext) { + (mpi::PublicKey::ECDH { ref curve, ..}, + SecretKeyMaterial::ECDH { ref scalar, }, + Ciphertext::ECDH { ref e, .. }) => (curve, scalar, e), + _ => return Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), + }; + + let S: Protected = match curve { + Curve::Cv25519 => { + // Get the public part V of the ephemeral key. + let V = e.decode_point(curve)?.0; + + let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519))?; + let V = AsymmetricKey::<Ecdh<Curve25519>, Public>::import_from_parts( + &provider, + V, + )?; + + // We strip trailing zeroes so make sure to pad them back when + // importing via CNG + let scalar = scalar.value(); + let scalar = [&*vec![0; 32 - scalar.len()], scalar].concat(); + let mut scalar = Protected::from(scalar); + // Reverse the scalar. See + // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. + scalar.reverse(); + + let r = AsymmetricKey::<Ecdh<Curve25519>, Private>::import_from_parts( + &provider, + &scalar, + )?; + + let secret = cng::asymmetric::agreement::secret_agreement(&r, &V)?; + // Returned secret is little-endian, flip it to big-endian + let mut secret = secret.derive_raw()?; + secret.reverse(); + secret.into() + } + + Curve::NistP256 | Curve::NistP384 | Curve::NistP521 => { + // Get the public part V of the ephemeral key and + // compute the shared point S = rV = rvG, where (r, R) + // is the recipient's key pair. + let (Vx, Vy) = e.decode_point(curve)?; + match curve { + Curve::NistP256 => { + let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP256))?; + let V = AsymmetricKey::<Ecdh<NistP256>, Public>::import_from_parts( + &provider, + &EccKeyPublicPayload { x: Vx, y: Vy }, + )?; + let r = AsymmetricKey::<Ecdh<NistP256>, Private>::import_from_parts( + &provider, + &EccKeyPrivatePayload { + x: &[0; 32], + y: &[0; 32], + d: scalar.value(), + } + )?; + + let secret = cng::asymmetric::agreement::secret_agreement(&r, &V)?; + // Returned secret is little-endian, flip it to big-endian + let mut secret = secret.derive_raw()?; + secret.reverse(); + secret.into() + } + Curve::NistP384 => { + let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP384))?; + let V = AsymmetricKey::<Ecdh<NistP384>, Public>::import_from_parts( + &provider, + &EccKeyPublicPayload { x: Vx, y: Vy }, + )?; + let r = AsymmetricKey::<Ecdh<NistP384>, Private>::import_from_parts( + &provider, + &EccKeyPrivatePayload { + x: &[0; 48], + y: &[0; 48], + d: scalar.value(), + } + )?; + + let secret = cng::asymmetric::agreement::secret_agreement(&r, &V)?; + // Returned secret is little-endian, flip it to big-endian + let mut secret = secret.derive_raw()?; + secret.reverse(); + secret.into() + } + Curve::NistP521 => { + let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP521))?; + let V = AsymmetricKey::<Ecdh<NistP521>, Public>::import_from_parts( + &provider, + &EccKeyPublicPayload { x: Vx, y: Vy }, + )?; + + // We strip trailing zeroes so make sure to pad them back + // when importing via CNG + let scalar = scalar.value(); + let scalar = [&*vec![0; 66 - scalar.len()], scalar].concat(); + let scalar = Protected::from(scalar); + + let r = AsymmetricKey::<Ecdh<NistP521>, Private>::import_from_parts( + &provider, + &EccKeyPrivatePayload { + x: &[0; 66], + y: &[0; 66], + d: scalar.as_ref(), + } + )?; + + let secret = cng::asymmetric::agreement::secret_agreement(&r, &V)?; + // Returned secret is little-endian, flip it to big-endian + let mut secret = secret.derive_raw()?; + secret.reverse(); + secret.into() + } + _ => unreachable!(), + } + }, + _ => { + return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()); + } + }; + + decrypt_shared(recipient, &S, ciphertext) } |