mirror of
https://github.com/Noratrieb/tls.git
synced 2026-01-14 08:35:03 +01:00
moves
This commit is contained in:
parent
d4b0e9809f
commit
b930bc0c5d
4 changed files with 199 additions and 182 deletions
70
src/crypto/aead.rs
Normal file
70
src/crypto/aead.rs
Normal file
|
|
@ -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<u8> {
|
||||
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::<sha2::Sha256>(secret, b"key", &[]);
|
||||
let iv = keys::hkdf_expand_label::<sha2::Sha256>(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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<u8>,
|
||||
}
|
||||
impl From<Vec<u8>> for TlsCiphertext {
|
||||
fn from(value: Vec<u8>) -> 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<u8>;
|
||||
}
|
||||
macro_rules! impl_hkdf_hasher {
|
||||
() => {
|
||||
const ZEROED: &'static [u8] =
|
||||
&[0; <<Self as OutputSizeUser>::OutputSize as Unsigned>::USIZE];
|
||||
fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength> {
|
||||
hkdf::Hkdf::<Self>::new(None, ikm).expand(&label, okm)
|
||||
}
|
||||
fn extract(salt: &[u8], ikm: &[u8]) -> Vec<u8> {
|
||||
hkdf::Hkdf::<Self>::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<H: TlsHasher>(secret: &[u8], label: &[u8], context: &[u8]) -> Vec<u8> {
|
||||
proto_struct! {
|
||||
pub(super) fn hkdf_expand_label<H: TlsHasher>(secret: &[u8], label: &[u8], context: &[u8]) -> Vec<u8> {
|
||||
proto::ser_de::proto_struct! {
|
||||
#[derive(Debug)]
|
||||
pub struct HkdfLabel {
|
||||
pub length: u16,
|
||||
|
|
@ -93,25 +40,10 @@ fn hkdf_expand_label<H: TlsHasher>(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<H: TlsHasher>(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec<u8> {
|
||||
pub(super) fn derive_secret<H: TlsHasher>(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec<u8> {
|
||||
hkdf_expand_label::<H>(secret, label, messages_hash)
|
||||
}
|
||||
|
||||
pub struct CryptoProvider {
|
||||
zeroed_of_hash_size: &'static [u8],
|
||||
hkdf_extract: fn(salt: &[u8], ikm: &[u8]) -> Vec<u8>,
|
||||
derive_secret: fn(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec<u8>,
|
||||
}
|
||||
impl CryptoProvider {
|
||||
fn new<H: TlsHasher>() -> Self {
|
||||
CryptoProvider {
|
||||
zeroed_of_hash_size: H::ZEROED,
|
||||
hkdf_extract: H::extract,
|
||||
derive_secret: derive_secret::<H>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<u8> {
|
||||
|
|
@ -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<u64>,
|
||||
}
|
||||
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<u8> {
|
||||
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<u8>,
|
||||
/// 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::<sha2::Sha256>(secret, b"key", &[]);
|
||||
let iv = hkdf_expand_label::<sha2::Sha256>(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,
|
||||
}
|
||||
}
|
||||
}
|
||||
116
src/crypto/mod.rs
Normal file
116
src/crypto/mod.rs
Normal file
|
|
@ -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<u8>,
|
||||
}
|
||||
impl From<Vec<u8>> for TlsCiphertext {
|
||||
fn from(value: Vec<u8>) -> 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<u8>;
|
||||
}
|
||||
macro_rules! impl_hkdf_hasher {
|
||||
() => {
|
||||
const ZEROED: &'static [u8] =
|
||||
&[0; <<Self as OutputSizeUser>::OutputSize as Unsigned>::USIZE];
|
||||
fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength> {
|
||||
hkdf::Hkdf::<Self>::new(None, ikm).expand(&label, okm)
|
||||
}
|
||||
fn extract(salt: &[u8], ikm: &[u8]) -> Vec<u8> {
|
||||
hkdf::Hkdf::<Self>::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<u8>,
|
||||
derive_secret: fn(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec<u8>,
|
||||
}
|
||||
impl CryptoProvider {
|
||||
fn new<H: TlsHasher>() -> Self {
|
||||
CryptoProvider {
|
||||
zeroed_of_hash_size: H::ZEROED,
|
||||
hkdf_extract: H::extract,
|
||||
derive_secret: keys::derive_secret::<H>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<u64>,
|
||||
}
|
||||
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<u8>,
|
||||
/// The `TLSPlaintext.type` value
|
||||
pub content_type: u8,
|
||||
pub padding_len: u16,
|
||||
}
|
||||
10
src/lib.rs
10
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<W> {
|
|||
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<W: Read + Write> ClientSetupConnection<W> {
|
|||
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<W: Read + Write> ClientSetupConnection<W> {
|
|||
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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue