use log::debug;
use rand::{CryptoRng, Rng};
use x25519_dalek::{PublicKey, StaticSecret};
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
use super::hash::HashAlgorithm;
use crate::crypto::{
aes_kw, ecc_curve::ECCCurve, public_key::PublicKeyAlgorithm, sym::SymmetricKeyAlgorithm,
Decryptor, KeyParams,
};
use crate::errors::{Error, Result};
use crate::types::{EcdhPublicParams, Mpi, PkeskBytes, PlainSecretParams, PublicParams};
const ANON_SENDER: [u8; 20] = [
0x41, 0x6E, 0x6F, 0x6E, 0x79, 0x6D, 0x6F, 0x75, 0x73, 0x20, 0x53, 0x65, 0x6E, 0x64, 0x65, 0x72,
0x20, 0x20, 0x20, 0x20,
];
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop, derive_more::Debug)]
pub enum SecretKey {
Curve25519 {
#[debug("..")]
secret: [u8; ECCCurve::Curve25519.secret_key_length()],
hash: HashAlgorithm,
alg_sym: SymmetricKeyAlgorithm,
},
P256 {
#[debug("..")]
secret: [u8; ECCCurve::P256.secret_key_length()],
hash: HashAlgorithm,
alg_sym: SymmetricKeyAlgorithm,
},
P384 {
#[debug("..")]
secret: [u8; ECCCurve::P384.secret_key_length()],
hash: HashAlgorithm,
alg_sym: SymmetricKeyAlgorithm,
},
P521 {
#[debug("..")]
secret: [u8; ECCCurve::P521.secret_key_length()],
hash: HashAlgorithm,
alg_sym: SymmetricKeyAlgorithm,
},
}
impl KeyParams for SecretKey {
type KeyParams = (ECCCurve, SymmetricKeyAlgorithm, HashAlgorithm);
fn key_params(&self) -> Self::KeyParams {
match self {
SecretKey::Curve25519 { hash, alg_sym, .. } => (ECCCurve::Curve25519, *alg_sym, *hash),
SecretKey::P256 { hash, alg_sym, .. } => (ECCCurve::P256, *alg_sym, *hash),
SecretKey::P384 { hash, alg_sym, .. } => (ECCCurve::P384, *alg_sym, *hash),
SecretKey::P521 { hash, alg_sym, .. } => (ECCCurve::P521, *alg_sym, *hash),
}
}
}
pub struct EncryptionFields<'a> {
pub public_point: &'a Mpi,
pub encrypted_session_key: &'a [u8],
pub fingerprint: &'a [u8],
}
impl Decryptor for SecretKey {
type EncryptionFields<'a> = EncryptionFields<'a>;
fn decrypt(&self, data: Self::EncryptionFields<'_>) -> Result<Vec<u8>> {
debug!("ECDH decrypt");
let encrypted_key_len: usize = data.encrypted_session_key.len();
let (curve, alg_sym, hash) = self.key_params();
let shared_secret = match self {
SecretKey::Curve25519 { secret, .. } => {
ensure_eq!(
secret.len(),
curve.secret_key_length(),
"invalid secret point"
);
ensure_eq!(data.public_point.len(), 33, "invalid public point");
let their_public = {
let ephemeral_public_key = &data.public_point.as_bytes()[1..];
let mut ephemeral_public_key_arr = [0u8; 32];
ephemeral_public_key_arr[..].copy_from_slice(ephemeral_public_key);
x25519_dalek::PublicKey::from(ephemeral_public_key_arr)
};
let our_secret = {
let private_key = &secret[..];
let mut private_key_le = private_key.iter().rev().cloned().collect::<Vec<u8>>();
let mut private_key_arr = [0u8; 32];
private_key_arr[..].copy_from_slice(&private_key_le);
private_key_le.zeroize();
StaticSecret::from(private_key_arr)
};
let shared_secret = our_secret.diffie_hellman(&their_public);
shared_secret.to_bytes().to_vec()
}
SecretKey::P256 { secret, .. } => derive_shared_secret_decryption::<p256::NistP256>(
data.public_point,
secret,
&curve,
65,
)?,
SecretKey::P384 { secret, .. } => derive_shared_secret_decryption::<p384::NistP384>(
data.public_point,
secret,
&curve,
97,
)?,
SecretKey::P521 { secret, .. } => derive_shared_secret_decryption::<p521::NistP521>(
data.public_point,
secret,
&curve,
133,
)?,
};
derive_session_key(
&shared_secret,
data.encrypted_session_key,
encrypted_key_len,
&(curve, alg_sym, hash),
data.fingerprint,
)
}
}
fn derive_shared_secret_decryption<C>(
public_point: &Mpi,
secret: &[u8],
curve: &ECCCurve,
pub_bytes: usize,
) -> Result<Vec<u8>>
where
C: elliptic_curve::CurveArithmetic,
elliptic_curve::FieldBytesSize<C>: elliptic_curve::sec1::ModulusSize,
elliptic_curve::AffinePoint<C>:
elliptic_curve::sec1::FromEncodedPoint<C> + elliptic_curve::sec1::ToEncodedPoint<C>,
{
ensure_eq!(
secret.len(),
curve.secret_key_length(),
"invalid secret point"
);
ensure_eq!(public_point.len(), pub_bytes, "invalid public point");
let ephemeral_public_key =
elliptic_curve::PublicKey::<C>::from_sec1_bytes(public_point.as_bytes())?;
let our_secret = elliptic_curve::SecretKey::<C>::from_bytes(secret.into())?;
let shared_secret = elliptic_curve::ecdh::diffie_hellman(
our_secret.to_nonzero_scalar(),
ephemeral_public_key.as_affine(),
);
Ok(shared_secret.raw_secret_bytes().to_vec())
}
pub fn derive_session_key(
shared_secret: &[u8],
encrypted_session_key: &[u8],
encrypted_key_len: usize,
key_params: &<SecretKey as KeyParams>::KeyParams,
fingerprint: &[u8],
) -> Result<Vec<u8>> {
let (curve, alg_sym, hash) = key_params;
let param = build_ecdh_param(&curve.oid(), *alg_sym, *hash, fingerprint);
let z = kdf(*hash, shared_secret, alg_sym.key_size(), ¶m)?;
let mut encrypted_session_key_vec = vec![0; encrypted_key_len];
encrypted_session_key_vec[(encrypted_key_len - encrypted_session_key.len())..]
.copy_from_slice(encrypted_session_key);
let mut decrypted_key_padded = aes_kw::unwrap(&z, &encrypted_session_key_vec)?;
{
let len = decrypted_key_padded.len();
let block_size = 8;
ensure!(len % block_size == 0, "invalid key length {}", len);
ensure!(!decrypted_key_padded.is_empty(), "empty key is not valid");
let pad = decrypted_key_padded.last().expect("is not empty");
if *pad as usize > len {
return Err(Error::UnpadError);
}
let unpadded_len = len - *pad as usize;
if decrypted_key_padded[unpadded_len..]
.iter()
.any(|byte| byte != pad)
{
return Err(Error::UnpadError);
}
decrypted_key_padded.truncate(unpadded_len);
}
let decrypted_key = decrypted_key_padded;
ensure!(!decrypted_key.is_empty(), "empty unpadded key is not valid");
Ok(decrypted_key)
}
pub fn generate_key<R: Rng + CryptoRng>(
mut rng: R,
curve: &ECCCurve,
) -> Result<(PublicParams, PlainSecretParams)> {
match curve {
ECCCurve::Curve25519 => {
let mut secret_key_bytes =
Zeroizing::new([0u8; ECCCurve::Curve25519.secret_key_length()]);
rng.fill_bytes(&mut *secret_key_bytes);
let secret = StaticSecret::from(*secret_key_bytes);
let public = PublicKey::from(&secret);
let p_raw = public.to_bytes();
let mut p = Vec::with_capacity(33);
p.push(0x40);
p.extend_from_slice(&p_raw);
let q_raw = curve25519_dalek::scalar::clamp_integer(secret.to_bytes());
let q = q_raw.into_iter().rev().collect::<Vec<u8>>();
let curve = ECCCurve::Curve25519;
let hash = curve.hash_algo()?;
let alg_sym = curve.sym_algo()?;
Ok((
PublicParams::ECDH(EcdhPublicParams::Known {
curve: ECCCurve::Curve25519,
p: Mpi::from_raw(p),
hash,
alg_sym,
}),
PlainSecretParams::ECDH(Mpi::from_raw(q)),
))
}
ECCCurve::P256 => keygen::<p256::NistP256, R>(rng, curve),
ECCCurve::P384 => keygen::<p384::NistP384, R>(rng, curve),
ECCCurve::P521 => keygen::<p521::NistP521, R>(rng, curve),
_ => unsupported_err!("curve {:?} for ECDH", curve),
}
}
fn keygen<C, R: Rng + CryptoRng>(
mut rng: R,
curve: &ECCCurve,
) -> Result<(PublicParams, PlainSecretParams)>
where
C: elliptic_curve::CurveArithmetic + elliptic_curve::point::PointCompression,
elliptic_curve::FieldBytesSize<C>: elliptic_curve::sec1::ModulusSize,
elliptic_curve::AffinePoint<C>:
elliptic_curve::sec1::FromEncodedPoint<C> + elliptic_curve::sec1::ToEncodedPoint<C>,
{
let secret = elliptic_curve::SecretKey::<C>::random(&mut rng);
let public = secret.public_key();
Ok((
PublicParams::ECDH(EcdhPublicParams::Known {
curve: curve.clone(),
p: Mpi::from_slice(public.to_sec1_bytes().as_ref()),
hash: curve.hash_algo()?,
alg_sym: curve.sym_algo()?,
}),
PlainSecretParams::ECDH(Mpi::from_slice(secret.to_bytes().as_slice())),
))
}
pub fn build_ecdh_param(
oid: &[u8],
alg_sym: SymmetricKeyAlgorithm,
hash: HashAlgorithm,
fingerprint: &[u8],
) -> Vec<u8> {
let kdf_params = vec![
0x03, 0x01, hash.into(),
u8::from(alg_sym),
];
let oid_len = [oid.len() as u8];
let pkalgo = [u8::from(PublicKeyAlgorithm::ECDH)];
let values: Vec<&[u8]> = vec![
&oid_len,
oid,
&pkalgo,
&kdf_params,
&ANON_SENDER[..],
fingerprint,
];
values.concat()
}
pub fn kdf(hash: HashAlgorithm, x: &[u8], length: usize, param: &[u8]) -> Result<Vec<u8>> {
let prefix = vec![0, 0, 0, 1];
let values: Vec<&[u8]> = vec![&prefix, x, param];
let data = values.concat();
let mut digest = hash.digest(&data)?;
digest.truncate(length);
Ok(digest)
}
fn pad(plain: &[u8]) -> Vec<u8> {
let len = plain.len();
let remainder = len % 8; let padded_len = len + 8 - remainder; debug_assert!(padded_len % 8 == 0, "Unexpected padded_len {}", padded_len);
let padding = padded_len - len;
debug_assert!(
padding > 0 && u8::try_from(padding).is_ok(),
"Unexpected padding value {}",
padding
);
let padding = padding as u8;
let mut plain_padded = plain.to_vec();
plain_padded.resize(padded_len, padding);
plain_padded
}
pub fn encrypt<R: CryptoRng + Rng>(
mut rng: R,
curve: &ECCCurve,
alg_sym: SymmetricKeyAlgorithm,
hash: HashAlgorithm,
fingerprint: &[u8],
q: &[u8],
plain: &[u8],
) -> Result<PkeskBytes> {
debug!("ECDH encrypt");
ensure!(
hash != HashAlgorithm::MD5
&& hash != HashAlgorithm::SHA1
&& hash != HashAlgorithm::RIPEMD160,
"{:?} is not a legal hash function for ECDH KDF",
hash
);
const MAX_SIZE: usize = 239;
ensure!(
plain.len() <= MAX_SIZE,
"unable to encrypt larger than {} bytes",
MAX_SIZE
);
let (encoded_public, shared_secret) = match curve {
ECCCurve::Curve25519 => {
ensure_eq!(q.len(), 33, "invalid public key");
let their_public = {
let public_key = &q[1..];
let mut public_key_arr = [0u8; 32];
public_key_arr[..].copy_from_slice(public_key);
x25519_dalek::PublicKey::from(public_key_arr)
};
let mut our_secret_key_bytes =
Zeroizing::new([0u8; ECCCurve::Curve25519.secret_key_length()]);
rng.fill_bytes(&mut *our_secret_key_bytes);
let our_secret = StaticSecret::from(*our_secret_key_bytes);
let shared_secret = our_secret.diffie_hellman(&their_public);
let mut encoded_public = Vec::with_capacity(33);
encoded_public.push(0x40);
encoded_public.extend(x25519_dalek::PublicKey::from(&our_secret).as_bytes().iter());
(encoded_public, shared_secret.as_bytes().to_vec())
}
ECCCurve::P256 => derive_shared_secret_encryption::<p256::NistP256, R>(rng, q)?,
ECCCurve::P384 => derive_shared_secret_encryption::<p384::NistP384, R>(rng, q)?,
ECCCurve::P521 => derive_shared_secret_encryption::<p521::NistP521, R>(rng, q)?,
_ => unsupported_err!("curve {:?} for ECDH", curve),
};
let param = build_ecdh_param(&curve.oid(), alg_sym, hash, fingerprint);
let z = kdf(hash, &shared_secret, alg_sym.key_size(), ¶m)?;
let plain_padded = pad(plain);
let encrypted_session_key = aes_kw::wrap(&z, &plain_padded)?;
Ok(PkeskBytes::Ecdh {
public_point: Mpi::from_slice(&encoded_public),
encrypted_session_key,
})
}
fn derive_shared_secret_encryption<C, R: CryptoRng + Rng>(
mut rng: R,
q: &[u8],
) -> Result<(Vec<u8>, Vec<u8>)>
where
C: elliptic_curve::CurveArithmetic + elliptic_curve::point::PointCompression,
elliptic_curve::FieldBytesSize<C>: elliptic_curve::sec1::ModulusSize,
elliptic_curve::AffinePoint<C>:
elliptic_curve::sec1::FromEncodedPoint<C> + elliptic_curve::sec1::ToEncodedPoint<C>,
{
let their_public = elliptic_curve::PublicKey::<C>::from_sec1_bytes(q)?;
let our_secret = elliptic_curve::ecdh::EphemeralSecret::<C>::random(&mut rng);
let shared_secret = our_secret.diffie_hellman(&their_public);
let our_public = elliptic_curve::PublicKey::<C>::from(&our_secret);
let our_public = elliptic_curve::sec1::EncodedPoint::<C>::from(our_public);
Ok((
our_public.as_bytes().to_vec(),
shared_secret.raw_secret_bytes().to_vec(),
))
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use std::fs;
use rand::{RngCore, SeedableRng};
use rand_chacha::ChaChaRng;
use super::*;
use crate::types::SecretKeyRepr;
use crate::{Deserializable, Message, SignedSecretKey};
#[test]
fn test_encrypt_decrypt() {
for curve in [
ECCCurve::Curve25519,
ECCCurve::P256,
ECCCurve::P384,
ECCCurve::P521,
] {
let mut rng = ChaChaRng::from_seed([0u8; 32]);
let (pkey, skey) = generate_key(&mut rng, &curve).unwrap();
for text_size in 1..=239 {
for _i in 0..10 {
let mut fingerprint = vec![0u8; 20];
rng.fill_bytes(&mut fingerprint);
let mut plain = vec![0u8; text_size];
rng.fill_bytes(&mut plain);
let values = match pkey {
PublicParams::ECDH(EcdhPublicParams::Known {
ref curve,
ref p,
hash,
alg_sym,
}) => encrypt(
&mut rng,
curve,
alg_sym,
hash,
&fingerprint,
p.as_bytes(),
&plain[..],
)
.unwrap(),
_ => panic!("invalid key generated"),
};
let decrypted = match (skey.as_ref().as_repr(&pkey).unwrap(), values) {
(
SecretKeyRepr::ECDH(ref skey),
PkeskBytes::Ecdh {
public_point,
encrypted_session_key,
},
) => skey
.decrypt(EncryptionFields {
public_point: &public_point,
encrypted_session_key: &encrypted_session_key,
fingerprint: &fingerprint,
})
.unwrap(),
_ => panic!("invalid key generated"),
};
assert_eq!(&plain[..], &decrypted[..]);
}
}
}
}
#[test]
fn test_decrypt_padding() {
let (decrypt_key, _headers) = SignedSecretKey::from_armor_single(
fs::File::open("./tests/unit-tests/padding/alice.key").unwrap(),
)
.expect("failed to read decryption key");
for msg_file in [
"./tests/unit-tests/padding/msg-short-padding.pgp",
"./tests/unit-tests/padding/msg-long-padding.pgp",
] {
let (message, _headers) = Message::from_armor_single(fs::File::open(msg_file).unwrap())
.expect("failed to parse message");
let (msg, _ids) = message
.decrypt(String::default, &[&decrypt_key])
.expect("failed to init decryption");
let data = msg.get_literal().unwrap().data();
assert_eq!(data, "hello\n".as_bytes());
}
}
}