use ring::{agreement, aead, rand, hkdf};
use anyhow::{Result, anyhow};
pub struct CttpsCrypto {
opening_key: aead::LessSafeKey,
sealing_key: aead::LessSafeKey,
}
impl CttpsCrypto {
pub fn new(shared_secret: &[u8], transcript_hash: &[u8]) -> Result<Self> {
if shared_secret.len() != 32 {
return Err(anyhow!("Shared secret must be 32 bytes"));
}
let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, &[]);
let prk = salt.extract(shared_secret);
let info = [transcript_hash];
let okm = prk.expand(&info, hkdf::HKDF_SHA256)
.map_err(|_| anyhow!("HKDF expansion failed"))?;
let mut session_key = [0u8; 32];
okm.fill(&mut session_key).map_err(|_| anyhow!("Failed to fill session key"))?;
let unbound_key = aead::UnboundKey::new(&aead::AES_256_GCM, &session_key)
.map_err(|_| anyhow!("Failed to create AEAD key"))?;
let opening_key = aead::LessSafeKey::new(unbound_key);
let unbound_key_seal = aead::UnboundKey::new(&aead::AES_256_GCM, &session_key)
.map_err(|_| anyhow!("Failed to create AEAD key"))?;
let sealing_key = aead::LessSafeKey::new(unbound_key_seal);
Ok(Self {
opening_key,
sealing_key,
})
}
pub fn seal(&self, nonce_bytes: [u8; 12], payload: &mut Vec<u8>) -> Result<Vec<u8>> {
let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);
let aad = aead::Aad::empty();
self.sealing_key
.seal_in_place_append_tag(nonce, aad, payload)
.map_err(|_| anyhow!("Encryption failed"))?;
Ok(payload.clone())
}
pub fn open<'a>(&self, nonce_bytes: [u8; 12], encrypted_payload_with_tag: &'a mut [u8]) -> Result<&'a [u8]> {
let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);
let aad = aead::Aad::empty();
let decrypted = self.opening_key
.open_in_place(nonce, aad, encrypted_payload_with_tag)
.map_err(|_| anyhow!("Decryption failed - integrity check failed"))?;
Ok(decrypted)
}
}
pub fn generate_keypair() -> (agreement::EphemeralPrivateKey, Vec<u8>) {
let rng = rand::SystemRandom::new();
let private_key = agreement::EphemeralPrivateKey::generate(&agreement::X25519, &rng).unwrap();
let public_key = private_key.compute_public_key().unwrap();
(private_key, public_key.as_ref().to_vec())
}
pub fn derive_shared_secret(my_private_key: agreement::EphemeralPrivateKey, peer_public_key: &[u8]) -> Result<Vec<u8>> {
let peer_public_key = agreement::UnparsedPublicKey::new(&agreement::X25519, peer_public_key);
agreement::agree_ephemeral(
my_private_key,
&peer_public_key,
|shared_secret| {
Ok(shared_secret.to_vec())
}
).map_err(|_| anyhow!("Agreement failed"))?
}
pub fn compute_transcript_hash(client_pub: &[u8], server_pub: &[u8]) -> Vec<u8> {
use ring::digest;
let mut ctx = digest::Context::new(&digest::SHA256);
ctx.update(client_pub);
ctx.update(server_pub);
ctx.finish().as_ref().to_vec()
}