diff options
Diffstat (limited to 'third_party/rust/neqo-crypto/src/hp.rs')
-rw-r--r-- | third_party/rust/neqo-crypto/src/hp.rs | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/third_party/rust/neqo-crypto/src/hp.rs b/third_party/rust/neqo-crypto/src/hp.rs new file mode 100644 index 0000000000..f968943c00 --- /dev/null +++ b/third_party/rust/neqo-crypto/src/hp.rs @@ -0,0 +1,187 @@ +// 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. + +use crate::constants::{ + Cipher, Version, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, +}; +use crate::err::{secstatus_to_res, Error, Res}; +use crate::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, +}; + +use std::cell::RefCell; +use std::convert::TryFrom; +use std::fmt::{self, Debug}; +use std::os::raw::{c_char, c_int, c_uint}; +use std::ptr::{addr_of_mut, null, null_mut}; +use std::rc::Rc; + +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 { + 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<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()) }).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<Vec<u8>> { + 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() as *mut u8, + blockCounterBits: 32, + pNonce: sample[4..Self::SAMPLE_SIZE].as_ptr() as *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) + } + } + } +} |