Implement provider-wide FIPS semantics by filtering non-FIPS suites in provider_with_ciphers() and wiring fips() reporting across provider components, KX groups, AEADs, and signature verifiers. Replace panic-prone hotpath behavior with error returns across TLS/QUIC AEAD setup and header protection, enforce HKDF output bounds, and remove shared HMAC context cloning to tighten runtime safety. Rework signing and verification paths to support SEC1 EC key loading, curve-aware scheme selection, and consistent malformed-input error handling without panics. Add comprehensive regression coverage for malformed KX shares, verifier inputs, AEAD truncation and constructor failures, plus a panic-surface test that scans runtime provider/additions code for new panic constructs unless explicitly allowlisted. Update the example client to return Result and eliminate non-test unwrap-style exits.
233 lines
7.3 KiB
Rust
233 lines
7.3 KiB
Rust
use std::ptr;
|
|
|
|
use boring::error::ErrorStack;
|
|
use foreign_types::ForeignType;
|
|
|
|
mod types;
|
|
|
|
use crate::helper::{cvt, cvt_p};
|
|
|
|
pub use self::types::*;
|
|
|
|
pub struct Algorithm(*const boring_sys::EVP_AEAD);
|
|
|
|
impl Algorithm {
|
|
/// AES-128 in Galois Counter Mode.
|
|
///
|
|
/// Note: AES-GCM should only be used with 12-byte (96-bit) nonces. Although it is specified to take a variable-length nonce, nonces with other lengths are effectively randomized, which means one must consider collisions. Unless implementing an existing protocol which has already specified incorrect parameters, only use 12-byte nonces.
|
|
#[must_use]
|
|
pub fn aes_128_gcm() -> Self {
|
|
Self(unsafe { boring_sys::EVP_aead_aes_128_gcm() })
|
|
}
|
|
|
|
/// AES-256 in Galois Counter Mode.
|
|
///
|
|
/// Note: AES-GCM should only be used with 12-byte (96-bit) nonces. Although it is specified to take a variable-length nonce, nonces with other lengths are effectively randomized, which means one must consider collisions. Unless implementing an existing protocol which has already specified incorrect parameters, only use 12-byte nonces.
|
|
#[must_use]
|
|
pub fn aes_256_gcm() -> Self {
|
|
Self(unsafe { boring_sys::EVP_aead_aes_256_gcm() })
|
|
}
|
|
|
|
/// `ChaCha20` with `Poly1305` as described in RFC 8439.
|
|
#[must_use]
|
|
pub fn chacha20_poly1305() -> Self {
|
|
Self(unsafe { boring_sys::EVP_aead_chacha20_poly1305() })
|
|
}
|
|
|
|
/// ChaCha20-Poly1305 with an extended nonce that makes random generation of nonces safe.
|
|
#[allow(unused)]
|
|
#[must_use]
|
|
pub fn xchacha20_poly1305() -> Self {
|
|
Self(unsafe { boring_sys::EVP_aead_xchacha20_poly1305() })
|
|
}
|
|
|
|
/// Returns the length, in bytes, of the keys used by aead
|
|
#[must_use]
|
|
pub fn key_length(&self) -> usize {
|
|
unsafe { boring_sys::EVP_AEAD_key_length(self.0) }
|
|
}
|
|
|
|
/// Returns the maximum number of additional bytes added by the act of sealing data with aead.
|
|
#[must_use]
|
|
pub fn max_overhead(&self) -> usize {
|
|
unsafe { boring_sys::EVP_AEAD_max_overhead(self.0) }
|
|
}
|
|
|
|
/// Returns the maximum tag length when using aead.
|
|
#[allow(unused)]
|
|
#[must_use]
|
|
pub fn max_tag_len(&self) -> usize {
|
|
unsafe { boring_sys::EVP_AEAD_max_tag_len(self.0) }
|
|
}
|
|
|
|
/// Returns the length, in bytes, of the per-message nonce for aead.
|
|
#[must_use]
|
|
pub fn nonce_len(&self) -> usize {
|
|
unsafe { boring_sys::EVP_AEAD_nonce_length(self.0) }
|
|
}
|
|
}
|
|
|
|
pub struct Crypter {
|
|
ctx: EvpAeadCtx,
|
|
max_overhead: usize,
|
|
nonce_len: usize,
|
|
}
|
|
|
|
impl Crypter {
|
|
/// Constructs a new AEAD crypter with the given algorithm and key
|
|
///
|
|
/// # Errors
|
|
/// Returns the `BoringSSL` error in case of an internal error
|
|
pub fn new(aead_alg: &Algorithm, key: &[u8]) -> Result<Self, ErrorStack> {
|
|
boring_sys::init();
|
|
|
|
let this = unsafe {
|
|
Self {
|
|
ctx: EvpAeadCtx::from_ptr(cvt_p(boring_sys::EVP_AEAD_CTX_new(
|
|
aead_alg.0,
|
|
key.as_ptr(),
|
|
key.len(),
|
|
boring_sys::EVP_AEAD_DEFAULT_TAG_LENGTH as usize,
|
|
))?),
|
|
max_overhead: aead_alg.max_overhead(),
|
|
nonce_len: aead_alg.nonce_len(),
|
|
}
|
|
};
|
|
|
|
Ok(this)
|
|
}
|
|
|
|
/// Returns the maximum required overhead in bytes
|
|
/// that will be added to a ciphertext, e.g.,
|
|
/// to hold an authentication tag.
|
|
#[must_use]
|
|
pub fn max_overhead(&self) -> usize {
|
|
self.max_overhead
|
|
}
|
|
|
|
/// Encrypts and authenticates `buffer` and authenticates `associated_data`.
|
|
/// It writes the ciphertext to `buffer` and the authentication tag to `tag`.
|
|
/// `tag` needs to have sufficient space, see [`Self::max_overhead()`](fn@Self::max_overhead())
|
|
/// On success, it returns the actual length of the `tag`
|
|
///
|
|
/// # Errors
|
|
/// In case of an error, returns the `BoringSSL` error
|
|
pub fn seal_in_place(
|
|
&self,
|
|
nonce: &[u8],
|
|
associated_data: &[u8],
|
|
buffer: &mut [u8],
|
|
tag: &mut [u8],
|
|
) -> Result<usize, ErrorStack> {
|
|
if tag.len() < self.max_overhead || nonce.len() != self.nonce_len {
|
|
return Err(ErrorStack::get());
|
|
}
|
|
|
|
let mut tag_len = tag.len();
|
|
unsafe {
|
|
cvt(boring_sys::EVP_AEAD_CTX_seal_scatter(
|
|
self.ctx.as_ptr(),
|
|
buffer.as_mut_ptr(),
|
|
tag.as_mut_ptr(),
|
|
&mut tag_len,
|
|
tag.len(),
|
|
nonce.as_ptr(),
|
|
nonce.len(),
|
|
buffer.as_ptr(),
|
|
buffer.len(),
|
|
ptr::null_mut(),
|
|
0,
|
|
associated_data.as_ptr(),
|
|
associated_data.len(),
|
|
))?;
|
|
}
|
|
Ok(tag_len)
|
|
}
|
|
|
|
/// Decrypts and authenticates `buffer` and authenticates `associated_data`.
|
|
/// It writes the cleartext to `buffer` and validates using `tag`.
|
|
///
|
|
/// # Errors
|
|
/// In case of an error, returns the `BoringSSL` error
|
|
pub fn open_in_place(
|
|
&self,
|
|
nonce: &[u8],
|
|
associated_data: &[u8],
|
|
buffer: &mut [u8],
|
|
tag: &[u8],
|
|
) -> Result<(), ErrorStack> {
|
|
if nonce.len() != self.nonce_len {
|
|
return Err(ErrorStack::get());
|
|
}
|
|
|
|
unsafe {
|
|
cvt(boring_sys::EVP_AEAD_CTX_open_gather(
|
|
self.ctx.as_ptr(),
|
|
buffer.as_mut_ptr(),
|
|
nonce.as_ptr(),
|
|
nonce.len(),
|
|
buffer.as_ptr(),
|
|
buffer.len(),
|
|
tag.as_ptr(),
|
|
tag.len(),
|
|
associated_data.as_ptr(),
|
|
associated_data.len(),
|
|
))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{Algorithm, Crypter};
|
|
|
|
#[test]
|
|
fn in_out() {
|
|
let key = Crypter::new(&Algorithm::aes_128_gcm(), &[0u8; 16]).unwrap();
|
|
let nonce = [0u8; 12];
|
|
let associated_data = b"this is authenticated";
|
|
let mut buffer = Vec::with_capacity(26);
|
|
buffer.extend_from_slice(b"ABCDE");
|
|
|
|
let mut tag = [0u8; 16];
|
|
key.seal_in_place(&nonce, associated_data, buffer.as_mut_slice(), &mut tag)
|
|
.unwrap();
|
|
|
|
key.open_in_place(&nonce, associated_data, buffer.as_mut_slice(), &tag[..])
|
|
.unwrap();
|
|
|
|
assert_eq!(b"ABCDE", buffer.as_slice());
|
|
}
|
|
|
|
#[test]
|
|
fn new_rejects_invalid_key_length() {
|
|
let result = Crypter::new(&Algorithm::aes_128_gcm(), &[0u8; 15]);
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn seal_rejects_invalid_nonce_and_tag_lengths() {
|
|
let key = Crypter::new(&Algorithm::aes_128_gcm(), &[0u8; 16]).unwrap();
|
|
let mut payload = [0u8; 8];
|
|
|
|
let mut short_tag = [0u8; 8];
|
|
let short_tag_result = key.seal_in_place(&[0u8; 12], b"", &mut payload, &mut short_tag);
|
|
assert!(short_tag_result.is_err());
|
|
|
|
let mut tag = [0u8; 16];
|
|
let wrong_nonce_result = key.seal_in_place(&[0u8; 11], b"", &mut payload, &mut tag);
|
|
assert!(wrong_nonce_result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn open_rejects_invalid_nonce_length() {
|
|
let key = Crypter::new(&Algorithm::aes_128_gcm(), &[0u8; 16]).unwrap();
|
|
let mut payload = [0u8; 8];
|
|
let tag = [0u8; 16];
|
|
|
|
let result = key.open_in_place(&[0u8; 11], b"", &mut payload, &tag);
|
|
assert!(result.is_err());
|
|
}
|
|
}
|