196 lines
6.6 KiB
Rust
196 lines
6.6 KiB
Rust
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
// option. This file may not be copied, modified, or distributed
|
|
// except according to those terms.
|
|
|
|
#![allow(
|
|
clippy::module_name_repetitions,
|
|
reason = "<https://github.com/mozilla/neqo/issues/2284#issuecomment-2782711813>"
|
|
)]
|
|
|
|
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<RefCell<Context>>),
|
|
/// 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 {
|
|
pub 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.
|
|
pub fn extract(version: Version, cipher: Cipher, prk: &SymKey, label: &str) -> Res<Self> {
|
|
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()) })?
|
|
);
|
|
Ok(res)
|
|
}
|
|
|
|
const 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.
|
|
/// Or when the sample provided is not at least `self.sample_size()` bytes.
|
|
pub fn mask(&self, sample: &[u8]) -> Res<[u8; Self::SAMPLE_SIZE]> {
|
|
let mut output = [0; Self::SAMPLE_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)?,
|
|
)
|
|
})?;
|
|
debug_assert_eq!(usize::try_from(output_len)?, 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())?,
|
|
[0; Self::SAMPLE_SIZE].as_ptr(),
|
|
c_uint::try_from(Self::SAMPLE_SIZE)?,
|
|
)
|
|
})?;
|
|
debug_assert_eq!(usize::try_from(output_len)?, output.len());
|
|
Ok(output)
|
|
}
|
|
}
|
|
}
|
|
}
|