summaryrefslogtreecommitdiffstats
path: root/openpgp/src/crypto/backend
diff options
context:
space:
mode:
authorIgor Matuszewski <igor@sequoia-pgp.org>2020-06-17 02:19:18 +0200
committerIgor Matuszewski <igor@sequoia-pgp.org>2020-08-13 15:19:58 +0200
commitd673821c1467a0ddc9ff3e1fa755ccb718e36125 (patch)
treefe6285cf80b0c3460f46821f2e78592a0d023c39 /openpgp/src/crypto/backend
parent18b00b17a57f824ad5a872fffffa54ad09444647 (diff)
openpgp: Implement ECDH and RSA encryption
Diffstat (limited to 'openpgp/src/crypto/backend')
-rw-r--r--openpgp/src/crypto/backend/cng/asymmetric.rs94
-rw-r--r--openpgp/src/crypto/backend/cng/ecdh.rs264
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)
}