// Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::{ cell::RefCell, fmt::{self, Debug}, os::raw::{c_char, c_int, c_uint}, ptr::{addr_of_mut, null, null_mut}, rc::Rc, }; use crate::{ constants::{ Cipher, Version, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, }, err::{secstatus_to_res, Error, Res}, p11::{ Context, Item, PK11SymKey, PK11_CipherOp, PK11_CreateContextBySymKey, PK11_Encrypt, PK11_GetBlockSize, SymKey, CKA_ENCRYPT, CKM_AES_ECB, CKM_CHACHA20, CK_ATTRIBUTE_TYPE, CK_CHACHA20_PARAMS, CK_MECHANISM_TYPE, }, }; experimental_api!(SSL_HkdfExpandLabelWithMech( version: Version, cipher: Cipher, prk: *mut PK11SymKey, handshake_hash: *const u8, handshake_hash_len: c_uint, label: *const c_char, label_len: c_uint, mech: CK_MECHANISM_TYPE, key_size: c_uint, secret: *mut *mut PK11SymKey, )); #[derive(Clone)] pub enum HpKey { /// An AES encryption context. /// Note: as we need to clone this object, we clone the pointer and /// track references using `Rc`. `PK11Context` can't be used with `PK11_CloneContext` /// as that is not supported for these contexts. Aes(Rc>), /// The `ChaCha20` mask has to invoke a new `PK11_Encrypt` every time as it needs to /// change the counter and nonce on each invocation. Chacha(SymKey), } impl Debug for HpKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "HpKey") } } impl HpKey { const SAMPLE_SIZE: usize = 16; /// QUIC-specific API for extracting a header-protection key. /// /// # Errors /// /// Errors if HKDF fails or if the label is too long to fit in a `c_uint`. /// /// # Panics /// /// When `cipher` is not known to this code. #[allow(clippy::cast_sign_loss)] // Cast for PK11_GetBlockSize is safe. pub fn extract(version: Version, cipher: Cipher, prk: &SymKey, label: &str) -> Res { const ZERO: &[u8] = &[0; 12]; let l = label.as_bytes(); let mut secret: *mut PK11SymKey = null_mut(); let (mech, key_size) = match cipher { TLS_AES_128_GCM_SHA256 => (CK_MECHANISM_TYPE::from(CKM_AES_ECB), 16), TLS_AES_256_GCM_SHA384 => (CK_MECHANISM_TYPE::from(CKM_AES_ECB), 32), TLS_CHACHA20_POLY1305_SHA256 => (CK_MECHANISM_TYPE::from(CKM_CHACHA20), 32), _ => unreachable!(), }; // Note that this doesn't allow for passing null() for the handshake hash. // A zero-length slice produces an identical result. unsafe { SSL_HkdfExpandLabelWithMech( version, cipher, **prk, null(), 0, l.as_ptr().cast(), c_uint::try_from(l.len())?, mech, key_size, &mut secret, ) }?; let key = SymKey::from_ptr(secret).or(Err(Error::HkdfError))?; let res = match cipher { TLS_AES_128_GCM_SHA256 | TLS_AES_256_GCM_SHA384 => { let context_ptr = unsafe { PK11_CreateContextBySymKey( mech, CK_ATTRIBUTE_TYPE::from(CKA_ENCRYPT), *key, &Item::wrap(&ZERO[..0]), // Borrow a zero-length slice of ZERO. ) }; let context = Context::from_ptr(context_ptr).or(Err(Error::CipherInitFailure))?; Self::Aes(Rc::new(RefCell::new(context))) } TLS_CHACHA20_POLY1305_SHA256 => Self::Chacha(key), _ => unreachable!(), }; debug_assert_eq!( res.block_size(), usize::try_from(unsafe { PK11_GetBlockSize(mech, null_mut()) }).unwrap() ); Ok(res) } /// Get the sample size, which is also the output size. #[must_use] #[allow(clippy::unused_self)] // To maintain an API contract. pub fn sample_size(&self) -> usize { Self::SAMPLE_SIZE } fn block_size(&self) -> usize { match self { Self::Aes(_) => 16, Self::Chacha(_) => 64, } } /// Generate a header protection mask for QUIC. /// /// # Errors /// /// An error is returned if the NSS functions fail; a sample of the /// wrong size is the obvious cause. /// /// # Panics /// /// When the mechanism for our key is not supported. pub fn mask(&self, sample: &[u8]) -> Res> { let mut output = vec![0_u8; self.block_size()]; match self { Self::Aes(context) => { let mut output_len: c_int = 0; secstatus_to_res(unsafe { PK11_CipherOp( **context.borrow_mut(), output.as_mut_ptr(), &mut output_len, c_int::try_from(output.len())?, sample[..Self::SAMPLE_SIZE].as_ptr().cast(), c_int::try_from(Self::SAMPLE_SIZE).unwrap(), ) })?; assert_eq!(usize::try_from(output_len).unwrap(), output.len()); Ok(output) } Self::Chacha(key) => { let params: CK_CHACHA20_PARAMS = CK_CHACHA20_PARAMS { pBlockCounter: sample.as_ptr().cast_mut(), blockCounterBits: 32, pNonce: sample[4..Self::SAMPLE_SIZE].as_ptr().cast_mut(), ulNonceBits: 96, }; let mut output_len: c_uint = 0; let mut param_item = Item::wrap_struct(¶ms); secstatus_to_res(unsafe { PK11_Encrypt( **key, CK_MECHANISM_TYPE::from(CKM_CHACHA20), addr_of_mut!(param_item), output[..].as_mut_ptr(), &mut output_len, c_uint::try_from(output.len())?, output[..].as_ptr(), c_uint::try_from(output.len())?, ) })?; assert_eq!(usize::try_from(output_len).unwrap(), output.len()); Ok(output) } } } }