use hkdf::Hkdf;
use log::debug;
use rand::{CryptoRng, Rng};
use sha2::Sha512;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
use crate::crypto::{aes_kw, Decryptor, KeyParams};
use crate::errors::Result;
use crate::types::{PlainSecretParams, PublicParams};
#[derive(Clone, derive_more::Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct SecretKey {
#[debug("..")]
pub(crate) secret: [u8; 56],
}
impl KeyParams for SecretKey {
type KeyParams = ();
fn key_params(&self) {}
}
pub struct EncryptionFields<'a> {
pub ephemeral_public_point: [u8; 56],
pub recipient_public: [u8; 56],
pub encrypted_session_key: &'a [u8],
}
impl Decryptor for SecretKey {
type EncryptionFields<'a> = EncryptionFields<'a>;
fn decrypt(&self, data: Self::EncryptionFields<'_>) -> Result<Vec<u8>> {
debug!("X448 decrypt");
let shared_secret = {
let Some(their_public) = x448::PublicKey::from_bytes(&data.ephemeral_public_point)
else {
bail!("x448: invalid public key");
};
let our_secret = x448::Secret::from(self.secret);
let Some(shared_secret) = our_secret.as_diffie_hellman(&their_public) else {
bail!("x448 Secret::as_diffie_hellman returned None");
};
*shared_secret.as_bytes()
};
derive_session_key(
data.ephemeral_public_point,
data.recipient_public,
shared_secret,
data.encrypted_session_key,
)
}
}
pub fn derive_session_key(
ephemeral: [u8; 56],
recipient_public: [u8; 56],
shared_secret: [u8; 56],
encrypted_session_key: &[u8],
) -> Result<Vec<u8>> {
let okm = hkdf(&ephemeral, &recipient_public, &shared_secret)?;
let decrypted_key = aes_kw::unwrap(&okm, encrypted_session_key)?;
ensure!(!decrypted_key.is_empty(), "empty key is not valid");
Ok(decrypted_key)
}
pub fn generate_key<R: Rng + CryptoRng>(mut rng: R) -> (PublicParams, PlainSecretParams) {
let mut secret_key_bytes = Zeroizing::new([0u8; 56]);
rng.fill_bytes(&mut *secret_key_bytes);
let secret = x448::Secret::from(*secret_key_bytes); let public = *x448::PublicKey::from(&secret).as_bytes();
(
PublicParams::X448 { public },
PlainSecretParams::X448(*secret.as_bytes()),
)
}
pub fn hkdf(
ephemeral: &[u8; 56],
recipient_public: &[u8; 56],
shared_secret: &[u8; 56],
) -> Result<[u8; 32]> {
const INFO: &[u8] = b"OpenPGP X448";
let mut input = vec![];
input.extend_from_slice(ephemeral);
input.extend_from_slice(recipient_public);
input.extend_from_slice(shared_secret);
let hk = Hkdf::<Sha512>::new(None, &input);
let mut okm = [0u8; 32];
hk.expand(INFO, &mut okm)
.expect("32 is a valid length for Sha512 to output");
Ok(okm)
}
pub fn encrypt<R: CryptoRng + Rng>(
mut rng: R,
recipient_public: [u8; 56],
plain: &[u8],
) -> Result<([u8; 56], Vec<u8>)> {
debug!("X448 encrypt");
const MAX_SIZE: usize = 255;
ensure!(
plain.len() <= MAX_SIZE,
"unable to encrypt larger than {} bytes",
MAX_SIZE
);
let (ephemeral_public, shared_secret) = {
let Some(their_public) = x448::PublicKey::from_bytes(&recipient_public) else {
bail!("x448: invalid public key");
};
let mut ephemeral_secret_key_bytes = Zeroizing::new([0u8; 56]);
rng.fill_bytes(&mut *ephemeral_secret_key_bytes);
let our_secret = x448::Secret::from(*ephemeral_secret_key_bytes);
let Some(shared_secret) = our_secret.as_diffie_hellman(&their_public) else {
bail!("x448 Secret::as_diffie_hellman returned None");
};
let ephemeral_public = x448::PublicKey::from(&our_secret);
(ephemeral_public, shared_secret)
};
let okm = hkdf(
ephemeral_public.as_bytes(),
&recipient_public,
shared_secret.as_bytes(),
)?;
let wrapped = aes_kw::wrap(&okm, plain)?;
Ok((*ephemeral_public.as_bytes(), wrapped))
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use std::ops::Deref;
use rand::{RngCore, SeedableRng};
use rand_chacha::ChaChaRng;
use super::*;
use crate::types::SecretKeyRepr;
#[test]
fn test_encrypt_decrypt() {
let mut rng = ChaChaRng::from_seed([0u8; 32]);
let (pkey, skey) = generate_key(&mut rng);
let PublicParams::X448 { public } = pkey else {
panic!("invalid key generated")
};
let SecretKeyRepr::X448(ref secret) = skey.as_ref().as_repr(&pkey).unwrap() else {
panic!("invalid key generated")
};
for text_size in (8..=248).step_by(8) {
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 (ephemeral, enc_sk) = encrypt(&mut rng, public, &plain[..]).unwrap();
let data = EncryptionFields {
ephemeral_public_point: ephemeral,
recipient_public: public,
encrypted_session_key: enc_sk.deref(),
};
let decrypted = secret.decrypt(data).unwrap();
assert_eq!(&plain[..], &decrypted[..]);
}
}
}
}