diff --git a/src/crypto/aead.rs b/src/crypto/aead.rs new file mode 100644 index 0000000..c215fb9 --- /dev/null +++ b/src/crypto/aead.rs @@ -0,0 +1,70 @@ +use ring::aead::Nonce; + +use crate::proto; + +use super::{keys, TlsCiphertext, TlsInnerPlaintext}; + + + +pub struct AeadKey { + key: ring::aead::LessSafeKey, +} + +impl AeadKey { + fn new(algorithm: proto::CipherSuite, key_bytes: &[u8]) -> Self { + Self { + key: ring::aead::LessSafeKey::new( + ring::aead::UnboundKey::new(proto_algo_to_ring(algorithm), key_bytes) + .expect("invalid key"), + ), + } + } +} + + +fn proto_algo_to_ring(algo: proto::CipherSuite) -> &'static ring::aead::Algorithm { + match algo { + proto::CipherSuite::TLS_AES_128_GCM_SHA256 => &ring::aead::AES_128_GCM, + proto::CipherSuite::TLS_AES_256_GCM_SHA384 => &ring::aead::AES_256_GCM, + proto::CipherSuite::TLS_CHACHA20_POLY1305_SHA256 => &ring::aead::CHACHA20_POLY1305, + proto::CipherSuite::TLS_AES_128_CCM_SHA256 => todo!("TLS_AES_128_CCM_SHA256"), + proto::CipherSuite::TLS_AES_128_CCM_8_SHA256 => todo!("TLS_AES_128_CCM_8_SHA256"), + } +} + +fn encrypt(key: AeadKey, message: &[u8], seq: u8, nonce: Nonce) -> Vec { + let total_len = message.len() + key.key.algorithm().tag_len(); + let mut ciphertext_payload = Vec::with_capacity(total_len); + ciphertext_payload.extend_from_slice(message); + + // FIXME: fill out the AAD properly + let aad = ring::aead::Aad::from([0; 5]); + key.key + .seal_in_place_append_tag(nonce, aad, &mut ciphertext_payload) + .unwrap(); + + ciphertext_payload +} + +fn decrypt(key: AeadKey, msg: &mut [u8], nonce: ring::aead::Nonce) { + // FIXME: fill out the AAD properly + let aad = ring::aead::Aad::from([0; 5]); + key.key.open_in_place(nonce, aad, msg); +} + +impl TlsCiphertext { + pub fn decrypt(mut self, secret: &[u8], nonce: Nonce) -> TlsInnerPlaintext { + let key = keys::hkdf_expand_label::(secret, b"key", &[]); + let iv = keys::hkdf_expand_label::(secret, b"iv", &[]); + + let key = AeadKey::new(proto::CipherSuite::TLS_AES_128_GCM_SHA256, secret); + + decrypt(key, &mut self.encrypted_record, nonce); + + TlsInnerPlaintext { + content: self.encrypted_record, + content_type: 0, + padding_len: 0, + } + } +} diff --git a/src/crypt.rs b/src/crypto/keys.rs similarity index 53% rename from src/crypt.rs rename to src/crypto/keys.rs index 2ec4d05..b6fd71b 100644 --- a/src/crypt.rs +++ b/src/crypto/keys.rs @@ -1,69 +1,16 @@ -//! Module for encrypting `TLSPlaintext` records. - -use crate::proto::{ - self, - ser_de::{proto_enum, proto_struct, Value}, - Handshake, -}; -use hkdf::{hmac::Hmac, HmacImpl}; -use ring::aead::{self, Nonce}; -pub use seq::{SeqId, SeqIdGen}; -use sha2::{ - digest::{ - core_api::{self, CoreProxy}, - generic_array::ArrayLength, - typenum::Unsigned, - OutputSizeUser, - }, - Digest, -}; +use sha2::{digest::OutputSizeUser, Digest}; use x25519_dalek::SharedSecret; -pub struct TlsCiphertext { - /// The encrypted [`TlsInnerPlaintext`] record. - pub encrypted_record: Vec, -} -impl From> for TlsCiphertext { - fn from(value: Vec) -> Self { - TlsCiphertext { - encrypted_record: value, - } - } -} +use crate::proto::{self, ser_de::Value}; -trait TlsHasher: OutputSizeUser { - const ZEROED: &'static [u8]; - fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength>; - fn extract(salt: &[u8], ikm: &[u8]) -> Vec; -} -macro_rules! impl_hkdf_hasher { - () => { - const ZEROED: &'static [u8] = - &[0; <::OutputSize as Unsigned>::USIZE]; - fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength> { - hkdf::Hkdf::::new(None, ikm).expand(&label, okm) - } - fn extract(salt: &[u8], ikm: &[u8]) -> Vec { - hkdf::Hkdf::::extract(Some(&[]), &[]) - .0 - .as_slice() - .to_vec() - } - }; -} -impl TlsHasher for sha2::Sha256 { - impl_hkdf_hasher!(); -} -impl TlsHasher for sha2::Sha384 { - impl_hkdf_hasher!(); -} +use super::{CryptoProvider, TlsHasher}; // Key Schedule // https://datatracker.ietf.org/doc/html/rfc8446#section-7.1 // The Hash function used by Transcript-Hash and HKDF is the cipher suite hash algorithm -fn hkdf_expand_label(secret: &[u8], label: &[u8], context: &[u8]) -> Vec { - proto_struct! { +pub(super) fn hkdf_expand_label(secret: &[u8], label: &[u8], context: &[u8]) -> Vec { + proto::ser_de::proto_struct! { #[derive(Debug)] pub struct HkdfLabel { pub length: u16, @@ -93,25 +40,10 @@ fn hkdf_expand_label(secret: &[u8], label: &[u8], context: &[u8]) /// Messages is the concatenation of the indicated handshake messages, /// including the handshake message type and length fields, but not /// including record layer headers. -fn derive_secret(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec { +pub(super) fn derive_secret(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec { hkdf_expand_label::(secret, label, messages_hash) } -pub struct CryptoProvider { - zeroed_of_hash_size: &'static [u8], - hkdf_extract: fn(salt: &[u8], ikm: &[u8]) -> Vec, - derive_secret: fn(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec, -} -impl CryptoProvider { - fn new() -> Self { - CryptoProvider { - zeroed_of_hash_size: H::ZEROED, - hkdf_extract: H::extract, - derive_secret: derive_secret::, - } - } -} - pub struct TranscriptHash { state: sha2::Sha256, } @@ -121,9 +53,9 @@ impl TranscriptHash { state: sha2::Sha256::new(), } } - pub fn handshake(&mut self, handshake: &Handshake) { + pub fn handshake(&mut self, handshake: &proto::Handshake) { let mut buf = Vec::new(); - handshake.write(&mut buf); + proto::ser_de::Value::write(handshake, &mut buf).unwrap(); self.state.update(&buf); } pub fn get_current(&self) -> Vec { @@ -256,104 +188,3 @@ impl KeysAfterServerHello { todo!() } } - -pub struct AeadKey { - key: aead::LessSafeKey, -} - -impl AeadKey { - fn new(algorithm: proto::CipherSuite, key_bytes: &[u8]) -> Self { - Self { - key: aead::LessSafeKey::new( - aead::UnboundKey::new(proto_algo_to_ring(algorithm), key_bytes) - .expect("invalid key"), - ), - } - } -} - -mod seq { - use std::cell::Cell; - - use ring::aead::{self, Nonce}; - - /// The sequence ID generator. - /// There is a separate one maintained for reading and writing. - pub struct SeqIdGen { - next: Cell, - } - impl SeqIdGen { - pub fn new() -> SeqIdGen { - SeqIdGen { next: Cell::new(0) } - } - pub fn next(&self) -> SeqId { - let next = self.next.get(); - self.next.set(next.checked_add(1).unwrap()); - SeqId(next) - } - } - // Don't implement `Clone` to ensure every seq id is only used once. - pub struct SeqId(u64); - impl SeqId { - pub fn to_nonce(self) -> Nonce { - let mut nonce = [0; aead::NONCE_LEN]; - nonce[4..].copy_from_slice(&self.0.to_be_bytes()); - Nonce::assume_unique_for_key(nonce) - } - } -} - -fn proto_algo_to_ring(algo: proto::CipherSuite) -> &'static aead::Algorithm { - match algo { - proto::CipherSuite::TLS_AES_128_GCM_SHA256 => &aead::AES_128_GCM, - proto::CipherSuite::TLS_AES_256_GCM_SHA384 => &aead::AES_256_GCM, - proto::CipherSuite::TLS_CHACHA20_POLY1305_SHA256 => &aead::CHACHA20_POLY1305, - proto::CipherSuite::TLS_AES_128_CCM_SHA256 => todo!("TLS_AES_128_CCM_SHA256"), - proto::CipherSuite::TLS_AES_128_CCM_8_SHA256 => todo!("TLS_AES_128_CCM_8_SHA256"), - } -} - -fn encrypt(key: AeadKey, message: &[u8], seq: u8, nonce: Nonce) -> Vec { - let total_len = message.len() + key.key.algorithm().tag_len(); - let mut ciphertext_payload = Vec::with_capacity(total_len); - ciphertext_payload.extend_from_slice(message); - - // FIXME: fill out the AAD properly - let aad = aead::Aad::from([0; 5]); - key.key - .seal_in_place_append_tag(nonce, aad, &mut ciphertext_payload) - .unwrap(); - - ciphertext_payload -} - -fn decrypt(key: AeadKey, msg: &mut [u8], nonce: Nonce) { - // FIXME: fill out the AAD properly - let aad = aead::Aad::from([0; 5]); - key.key.open_in_place(nonce, aad, msg); -} - -pub struct TlsInnerPlaintext { - /// The `TLSPlaintext.fragment`` value - pub content: Vec, - /// The `TLSPlaintext.type` value - pub content_type: u8, - pub padding_len: u16, -} - -impl TlsCiphertext { - pub fn decrypt(mut self, secret: &[u8], nonce: Nonce) -> TlsInnerPlaintext { - let key = hkdf_expand_label::(secret, b"key", &[]); - let iv = hkdf_expand_label::(secret, b"iv", &[]); - - let key = AeadKey::new(proto::CipherSuite::TLS_AES_128_GCM_SHA256, secret); - - decrypt(key, &mut self.encrypted_record, nonce); - - TlsInnerPlaintext { - content: self.encrypted_record, - content_type: 0, - padding_len: 0, - } - } -} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..0ff52ec --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,116 @@ +//! Cryptographic operations. + +pub mod aead; +pub mod keys; + +use crate::proto::{ + self, + ser_de::{proto_enum, proto_struct, Value}, + Handshake, +}; +use hkdf::{hmac::Hmac, HmacImpl}; +use ring::aead::Nonce; +pub use seq::{SeqId, SeqIdGen}; +use sha2::{ + digest::{ + core_api::{self, CoreProxy}, + generic_array::ArrayLength, + typenum::Unsigned, + OutputSizeUser, + }, + Digest, +}; +use x25519_dalek::SharedSecret; + +pub struct TlsCiphertext { + /// The encrypted [`TlsInnerPlaintext`] record. + pub encrypted_record: Vec, +} +impl From> for TlsCiphertext { + fn from(value: Vec) -> Self { + TlsCiphertext { + encrypted_record: value, + } + } +} + +trait TlsHasher: OutputSizeUser { + const ZEROED: &'static [u8]; + fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength>; + fn extract(salt: &[u8], ikm: &[u8]) -> Vec; +} +macro_rules! impl_hkdf_hasher { + () => { + const ZEROED: &'static [u8] = + &[0; <::OutputSize as Unsigned>::USIZE]; + fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength> { + hkdf::Hkdf::::new(None, ikm).expand(&label, okm) + } + fn extract(salt: &[u8], ikm: &[u8]) -> Vec { + hkdf::Hkdf::::extract(Some(&[]), &[]) + .0 + .as_slice() + .to_vec() + } + }; +} +impl TlsHasher for sha2::Sha256 { + impl_hkdf_hasher!(); +} +impl TlsHasher for sha2::Sha384 { + impl_hkdf_hasher!(); +} + +pub struct CryptoProvider { + zeroed_of_hash_size: &'static [u8], + hkdf_extract: fn(salt: &[u8], ikm: &[u8]) -> Vec, + derive_secret: fn(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec, +} +impl CryptoProvider { + fn new() -> Self { + CryptoProvider { + zeroed_of_hash_size: H::ZEROED, + hkdf_extract: H::extract, + derive_secret: keys::derive_secret::, + } + } +} + +mod seq { + use std::cell::Cell; + + use ring::aead::{self, Nonce}; + + /// The sequence ID generator. + /// There is a separate one maintained for reading and writing. + pub struct SeqIdGen { + next: Cell, + } + impl SeqIdGen { + pub fn new() -> SeqIdGen { + SeqIdGen { next: Cell::new(0) } + } + pub fn next(&self) -> SeqId { + let next = self.next.get(); + self.next.set(next.checked_add(1).unwrap()); + SeqId(next) + } + } + // Don't implement `Clone` to ensure every seq id is only used once. + pub struct SeqId(u64); + impl SeqId { + pub fn to_nonce(self) -> Nonce { + let mut nonce = [0; aead::NONCE_LEN]; + nonce[4..].copy_from_slice(&self.0.to_be_bytes()); + Nonce::assume_unique_for_key(nonce) + } + } +} + +pub struct TlsInnerPlaintext { + /// The `TLSPlaintext.fragment`` value + pub content: Vec, + /// The `TLSPlaintext.type` value + pub content_type: u8, + pub padding_len: u16, +} diff --git a/src/lib.rs b/src/lib.rs index 02523f0..a04ce4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![allow(unused)] -mod crypt; +mod crypto; pub mod proto; use std::{ @@ -9,7 +9,7 @@ use std::{ io::{self, Read, Write}, }; -use crypt::{KeysAfterServerHello, TranscriptHash}; +use crypto::keys::{KeysAfterServerHello, TranscriptHash}; use proto::{ser_de::Value, CipherSuite}; use crate::proto::TLSPlaintext; @@ -38,7 +38,7 @@ struct ClientSetupConnection { mod stream_state { use std::io::{Read, Write}; - use crate::crypt::{SeqId, SeqIdGen}; + use crate::crypto::{SeqId, SeqIdGen}; use crate::proto::{self, TLSPlaintext}; use crate::Result; @@ -317,7 +317,7 @@ impl ClientSetupConnection { dh_shared_secret.as_bytes() ); - let keys = crypt::KeysAfterServerHello::compute( + let keys = KeysAfterServerHello::compute( dh_shared_secret, *cipher_suite, &transcript.borrow(), @@ -336,7 +336,7 @@ impl ClientSetupConnection { return unexpected_message!("expected ApplicationData, got {frame:?}"); }; // Encrypted with server_handshake_traffic_secret - crypt::TlsCiphertext::from(data).decrypt( + crypto::TlsCiphertext::from(data).decrypt( &keys .borrow() .as_ref()