Enhance error handling and docs

This commit is contained in:
Jan Rüth 2023-11-24 19:38:18 +01:00 committed by Jan
commit bd80bfc4d7
18 changed files with 295 additions and 206 deletions

View file

@ -15,6 +15,7 @@ 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() })
}
@ -22,38 +23,45 @@ impl Algorithm {
/// 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 and Poly1305 as described in RFC 8439.
/// `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) }
}
@ -66,7 +74,14 @@ pub struct Crypter {
}
impl Crypter {
pub fn new(aead_alg: Algorithm, key: &[u8]) -> Result<Self, ErrorStack> {
/// Constructs a new AEAD crypter with the given algorithm and key
///
/// # Errors
/// Returns the `BoringSSL` error in case of an internal error
///
/// # Panics
/// * If the key length mismatches the `aead_alg` required key length
pub fn new(aead_alg: &Algorithm, key: &[u8]) -> Result<Self, ErrorStack> {
assert_eq!(aead_alg.key_length(), key.len());
boring_sys::init();
@ -86,13 +101,25 @@ impl Crypter {
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.
/// On success, it returns the actual length of the tag
/// 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
///
/// # Panics
/// * If the `nonce` is not the expected lenght
/// * If the `tag` has not enough space
pub fn seal_in_place(
&self,
nonce: &[u8],
@ -124,6 +151,14 @@ impl Crypter {
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
///
/// # Panics
/// * if the nonce has the wrong lenght
pub fn open_in_place(
&self,
nonce: &[u8],
@ -157,25 +192,19 @@ mod tests {
#[test]
fn in_out() {
let key = Crypter::new(super::Algorithm::aes_128_gcm(), &[0u8; 16]).unwrap();
let key = Crypter::new(&super::Algorithm::aes_128_gcm(), &[0u8; 16]).unwrap();
let nonce = [0u8; 12];
let associated_data = b"this is signed";
let associated_data = b"this is authenticated";
let mut buffer = Vec::with_capacity(26);
buffer.push(b'A');
buffer.push(b'B');
buffer.push(b'C');
buffer.push(b'D');
buffer.push(b'E');
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();
println!("Encrypted: {:02X?}, Tag: {:02X?}", buffer, tag);
key.open_in_place(&nonce, associated_data, buffer.as_mut_slice(), &tag[..])
.unwrap();
println!("Plaintext: {}", String::from_utf8(buffer).unwrap());
assert_eq!(b"ABCDE", buffer.as_slice());
}
}

View file

@ -2,11 +2,11 @@ use std::os::raw::c_int;
use boring::error::ErrorStack;
/// Check the value returned from a BoringSSL ffi call
/// Check the value returned from a `BoringSSL` ffi call
/// that returns a pointer.
///
/// If the pointer is null, this method returns the BoringSSL
/// ErrorStack as Err, the pointer otherwise.
/// If the pointer is null, this method returns the
/// [`boring::error::ErrorStack`] as Err, the pointer otherwise.
pub(crate) fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
if r.is_null() {
Err(ErrorStack::get())
@ -15,10 +15,10 @@ pub(crate) fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
}
}
/// Check the value returned from a BoringSSL ffi call that
/// Check the value returned from a `BoringSSL` ffi call that
/// returns a integer.
///
/// Returns the BoringSSL Errorstack when the result is <= 0.
/// Returns the [`boring::error::ErrorStack`] when the result is <= 0.
/// And forwards the return code otherwise
pub(crate) fn cvt(r: c_int) -> Result<i32, ErrorStack> {
if r <= 0 {

View file

@ -38,11 +38,14 @@ unsafe impl ForeignType for HmacCtx {
impl Clone for HmacCtx {
fn clone(&self) -> Self {
unsafe {
let ctx = HmacCtx::from_ptr(cvt_p(boring_sys::HMAC_CTX_new()).unwrap());
cvt(boring_sys::HMAC_CTX_copy(ctx.as_ptr(), self.0.as_ptr())).unwrap();
ctx
cvt_p(boring_sys::HMAC_CTX_new())
.map(|ctx| HmacCtx::from_ptr(ctx))
.and_then(|ctx| {
cvt(boring_sys::HMAC_CTX_copy(ctx.as_ptr(), self.0.as_ptr()))?;
Ok(ctx)
})
}
.expect("failed cloning hmac ctx")
}
}

View file

@ -6,7 +6,7 @@ use boring_additions::aead::Algorithm;
use rustls::crypto::cipher::{self, make_tls12_aad, make_tls13_aad, Iv};
use rustls::{ConnectionTrafficSecrets, ContentType, ProtocolVersion};
use crate::helper::error_stack_to_aead_error;
use crate::helper::log_and_map;
pub(crate) mod aes;
pub(crate) mod chacha20;
@ -47,6 +47,7 @@ impl<T: BoringAead> AeadCore for BoringAeadCrypter<T> {
}
impl<T: BoringAead> BoringAeadCrypter<T> {
/// Creates a new aead crypter
pub fn new(iv: Iv, key: &[u8], tls_version: ProtocolVersion) -> Result<Self, ErrorStack> {
assert!(match tls_version {
#[cfg(feature = "tls12")]
@ -63,7 +64,7 @@ impl<T: BoringAead> BoringAeadCrypter<T> {
);
let crypter = BoringAeadCrypter {
crypter: boring_additions::aead::Crypter::new(cipher, key)?,
crypter: boring_additions::aead::Crypter::new(&cipher, key)?,
iv,
tls_version,
phantom: PhantomData,
@ -82,7 +83,7 @@ impl<T: BoringAead> aead::AeadInPlace for BoringAeadCrypter<T> {
let mut tag = Tag::<Self>::default();
self.crypter
.seal_in_place(nonce, associated_data, buffer, &mut tag)
.map_err(|e| error_stack_to_aead_error("seal_in_place", e))?;
.map_err(|e| log_and_map("seal_in_place", e, aead::Error))?;
Ok(tag)
}
@ -96,7 +97,7 @@ impl<T: BoringAead> aead::AeadInPlace for BoringAeadCrypter<T> {
) -> aead::Result<()> {
self.crypter
.open_in_place(nonce, associated_data, buffer, tag)
.map_err(|e| error_stack_to_aead_error("open_in_place", e))?;
.map_err(|e| log_and_map("open_in_place", e, aead::Error))?;
Ok(())
}
}

View file

@ -3,11 +3,10 @@ use aead::consts::{U12, U16};
use boring_additions::aead::Algorithm;
use rustls::{crypto::cipher, ConnectionTrafficSecrets};
/// Aes128 AEAD cipher
pub struct Aes128 {}
impl BoringAead for Aes128 {}
unsafe impl Send for Aes128 {}
unsafe impl Sync for Aes128 {}
impl BoringCipher for Aes128 {
fn new_cipher() -> Algorithm {
@ -37,11 +36,10 @@ impl aead::AeadCore for Aes128 {
type CiphertextOverhead = U16;
}
/// Aes256 AEAD cipher
pub struct Aes256 {}
impl BoringAead for Aes256 {}
unsafe impl Send for Aes256 {}
unsafe impl Sync for Aes256 {}
impl BoringCipher for Aes256 {
fn new_cipher() -> Algorithm {

View file

@ -6,11 +6,10 @@ use aead::{
use boring_additions::aead::Algorithm;
use rustls::{crypto::cipher, ConnectionTrafficSecrets};
/// `ChaCha20` with `Poly1305` cipher
pub struct ChaCha20Poly1305 {}
impl BoringAead for ChaCha20Poly1305 {}
unsafe impl Send for ChaCha20Poly1305 {}
unsafe impl Sync for ChaCha20Poly1305 {}
impl BoringCipher for ChaCha20Poly1305 {
fn new_cipher() -> Algorithm {

View file

@ -1,3 +1,4 @@
use boring::hash::{Hasher, MessageDigest};
use rustls::crypto::hash;
pub const SHA256: &dyn hash::Hash = &Hash(boring::nid::Nid::SHA256);
@ -7,8 +8,8 @@ pub struct Hash(pub boring::nid::Nid);
impl hash::Hash for Hash {
fn start(&self) -> Box<dyn hash::Context> {
let digest = boring::hash::MessageDigest::from_nid(self.0).unwrap();
let hasher = boring::hash::Hasher::new(digest).unwrap();
let digest = MessageDigest::from_nid(self.0).expect("failed getting hash digest");
let hasher = Hasher::new(digest).expect("failed getting hasher");
Box::new(HasherContext(hasher))
}
@ -21,24 +22,26 @@ impl hash::Hash for Hash {
fn algorithm(&self) -> hash::HashAlgorithm {
match self.0 {
boring::nid::Nid::SHA256 => hash::HashAlgorithm::SHA256,
boring::nid::Nid::SHA384 => hash::HashAlgorithm::SHA384,
boring::nid::Nid::SHA512 => hash::HashAlgorithm::SHA512,
_ => unimplemented!(),
}
}
fn output_len(&self) -> usize {
boring::hash::MessageDigest::from_nid(self.0)
.unwrap()
MessageDigest::from_nid(self.0)
.expect("failed getting digest")
.size()
}
}
struct HasherContext(boring::hash::Hasher);
struct HasherContext(Hasher);
impl hash::Context for HasherContext {
fn fork_finish(&self) -> hash::Output {
let mut cloned = self.0.clone();
hash::Output::new(&cloned.finish().unwrap()[..])
hash::Output::new(&cloned.finish().expect("failed finishing hash")[..])
}
fn fork(&self) -> Box<dyn hash::Context> {
@ -46,11 +49,11 @@ impl hash::Context for HasherContext {
}
fn finish(mut self: Box<Self>) -> hash::Output {
hash::Output::new(&self.0.finish().unwrap()[..])
hash::Output::new(&self.0.finish().expect("failed finishing hash")[..])
}
fn update(&mut self, data: &[u8]) {
self.0.update(data).unwrap();
self.0.update(data).expect("failed adding data to hash");
}
}

View file

@ -4,11 +4,11 @@ use boring::error::ErrorStack;
#[cfg(feature = "log")]
use log::trace;
/// Check the value returned from a BoringSSL ffi call
/// Check the value returned from a `BoringSSL` ffi call
/// that returns a pointer.
///
/// If the pointer is null, this method returns the BoringSSL
/// ErrorStack as Err, the pointer otherwise.
/// If the pointer is null, this method returns the
/// [`boring::error::ErrorStack`] as Err, the pointer otherwise.
pub(crate) fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
if r.is_null() {
Err(ErrorStack::get())
@ -17,10 +17,10 @@ pub(crate) fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
}
}
/// Check the value returned from a BoringSSL ffi call that
/// Check the value returned from a `BoringSSL` ffi call that
/// returns a integer.
///
/// Returns the BoringSSL Errorstack when the result is <= 0.
/// Returns the [`boring::error::ErrorStack`] when the result is <= 0.
/// And forwards the return code otherwise
pub(crate) fn cvt(r: c_int) -> Result<i32, ErrorStack> {
if r <= 0 {
@ -30,17 +30,13 @@ pub(crate) fn cvt(r: c_int) -> Result<i32, ErrorStack> {
}
}
pub(crate) fn error_stack_to_aead_error(func: &'static str, e: ErrorStack) -> aead::Error {
map_error_stack(func, e, aead::Error)
}
#[cfg(feature = "log")]
pub(crate) fn map_error_stack<T>(func: &'static str, e: ErrorStack, mapped: T) -> T {
pub(crate) fn log_and_map<E: core::fmt::Display, T>(func: &'static str, e: E, mapped: T) -> T {
trace!("failed {}, error: {}", func, e);
mapped
}
#[cfg(not(feature = "log"))]
pub(crate) fn map_error_stack<T>(func: &'static str, e: ErrorStack, mapped: T) -> T {
pub(crate) fn log_and_map<E: core::fmt::Display, T>(func: &'static str, e: E, mapped: T) -> T {
mapped
}

View file

@ -5,10 +5,15 @@ use rustls::crypto::tls13::{self, Hkdf as RustlsHkdf};
use crate::helper::{cvt, cvt_p};
/// A trait that is required for a Hkdf function
pub trait BoringHash: Send + Sync {
/// Instantiate a new digest using
/// the hash function that this trait
/// is implemented for.
fn new_hash() -> MessageDigest;
}
/// SHA256-based for Hkdf
pub struct Sha256();
impl BoringHash for Sha256 {
fn new_hash() -> MessageDigest {
@ -16,6 +21,7 @@ impl BoringHash for Sha256 {
}
}
/// SHA384-based for Hkdf
pub struct Sha384();
impl BoringHash for Sha384 {
fn new_hash() -> MessageDigest {
@ -23,9 +29,12 @@ impl BoringHash for Sha384 {
}
}
/// A Hmac-based key derivation function
/// using T as the hash function
pub struct Hkdf<T: BoringHash>(PhantomData<T>);
impl<T: BoringHash> Hkdf<T> {
/// A default Hkdf implementation
pub const DEFAULT: Self = Self(PhantomData);
}
@ -130,7 +139,7 @@ impl<T: BoringHash> RustlsHkdf for Hkdf<T> {
}
}
pub struct HkdfExpander {
struct HkdfExpander {
prk: [u8; boring_sys::EVP_MAX_MD_SIZE as usize],
prk_len: usize,
digest: MessageDigest,
@ -181,7 +190,8 @@ impl tls13::HkdfExpander for HkdfExpander {
let mut output = [0u8; boring_sys::EVP_MAX_MD_SIZE as usize];
let output_len = self.hash_len();
self.expand_slice(info, &mut output[..output_len]).unwrap();
self.expand_slice(info, &mut output[..output_len])
.expect("failed hkdf expand");
tls13::OkmBlock::new(&output[..output_len])
}

View file

@ -7,30 +7,34 @@ use rustls::crypto;
use crate::helper::{cvt, cvt_p};
/// A SHA256-based Hmac
#[allow(unused)]
pub const SHA256: &dyn crypto::hmac::Hmac = &BoringHmac(boring::nid::Nid::SHA256);
/// A SHA384-based Hmac
pub const SHA384: &dyn crypto::hmac::Hmac = &BoringHmac(boring::nid::Nid::SHA384);
pub struct BoringHmac(pub boring::nid::Nid);
struct BoringHmac(pub boring::nid::Nid);
impl crypto::hmac::Hmac for BoringHmac {
fn with_key(&self, key: &[u8]) -> Box<dyn crypto::hmac::Key> {
Box::new(unsafe {
let ctx = HmacCtx::from_ptr(cvt_p(boring_sys::HMAC_CTX_new()).unwrap());
let ctx = unsafe {
HmacCtx::from_ptr(
cvt_p(boring_sys::HMAC_CTX_new()).expect("failed getting hmac context"),
)
};
let md = boring::hash::MessageDigest::from_nid(self.0).unwrap();
let md = MessageDigest::from_nid(self.0).expect("failed getting digest");
BoringHmacKey {
ctx,
md,
key: key.to_vec(),
}
Box::new(BoringHmacKey {
ctx,
md,
key: key.to_vec(),
})
}
fn hash_output_len(&self) -> usize {
boring::hash::MessageDigest::from_nid(self.0)
.unwrap()
MessageDigest::from_nid(self.0)
.expect("failed getting digest")
.size()
}
}
@ -42,11 +46,9 @@ struct BoringHmacKey {
key: Vec<u8>,
}
impl crypto::hmac::Key for BoringHmacKey {
fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> crypto::hmac::Tag {
let mut out = [0u8; 32];
crypto::hmac::Tag::new(unsafe {
impl BoringHmacKey {
fn init(&self) {
unsafe {
// initialize a new hmac
cvt(boring_sys::HMAC_Init_ex(
self.ctx.as_ptr(),
@ -55,41 +57,50 @@ impl crypto::hmac::Key for BoringHmacKey {
self.md.as_ptr(),
ptr::null_mut(),
))
.unwrap();
}
.expect("failed initializing hmac");
}
fn update(&self, bytes: &[u8]) {
unsafe {
cvt(boring_sys::HMAC_Update(
self.ctx.as_ptr(),
first.as_ptr(),
first.len(),
bytes.as_ptr(),
bytes.len(),
))
.unwrap();
}
.expect("failed updating hmac");
}
for m in middle {
cvt(boring_sys::HMAC_Update(
self.ctx.as_ptr(),
m.as_ptr(),
m.len(),
))
.unwrap();
}
cvt(boring_sys::HMAC_Update(
self.ctx.as_ptr(),
last.as_ptr(),
last.len(),
))
.unwrap();
let mut out_len = 0;
fn finish(&self, out: &mut [u8]) -> usize {
let mut out_len = 0;
unsafe {
cvt(boring_sys::HMAC_Final(
self.ctx.as_ptr(),
out.as_mut_ptr(),
&mut out_len,
))
.unwrap();
}
.expect("failed hmac final");
out_len as usize
}
}
&out[..out_len as usize]
})
impl crypto::hmac::Key for BoringHmacKey {
fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> crypto::hmac::Tag {
self.init();
self.update(first);
for m in middle {
self.update(m);
}
self.update(last);
let mut out = [0u8; 32];
let out_len = self.finish(&mut out);
crypto::hmac::Tag::new(&out[..out_len])
}
fn tag_len(&self) -> usize {

View file

@ -1,6 +1,6 @@
use rustls::crypto::{self, ActiveKeyExchange};
use crate::helper::map_error_stack;
use crate::helper::log_and_map;
mod dh;
mod ex;
@ -11,13 +11,14 @@ enum DhKeyType {
FFDHE2048,
}
/// A X25519-based key exchange
#[derive(Debug)]
pub struct X25519;
impl crypto::SupportedKxGroup for X25519 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(ex::ExKeyExchange::with_x25519().map_err(|e| {
map_error_stack("X25519.start", e, crypto::GetRandomFailed)
Ok(Box::new(ex::KeyExchange::with_x25519().map_err(|e| {
log_and_map("X25519.start", e, crypto::GetRandomFailed)
})?))
}
@ -26,13 +27,14 @@ impl crypto::SupportedKxGroup for X25519 {
}
}
/// A X448-based key exchange
#[derive(Debug)]
pub struct X448;
impl crypto::SupportedKxGroup for X448 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(ex::ExKeyExchange::with_x448().map_err(|e| {
map_error_stack("X448.start", e, crypto::GetRandomFailed)
Ok(Box::new(ex::KeyExchange::with_x448().map_err(|e| {
log_and_map("X448.start", e, crypto::GetRandomFailed)
})?))
}
@ -41,13 +43,14 @@ impl crypto::SupportedKxGroup for X448 {
}
}
/// A secp256r1-based key exchange
#[derive(Debug)]
pub struct Secp256r1;
impl crypto::SupportedKxGroup for Secp256r1 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(ex::ExKeyExchange::with_secp256r1().map_err(
|e| map_error_stack("Secp256r1.start", e, crypto::GetRandomFailed),
Ok(Box::new(ex::KeyExchange::with_secp256r1().map_err(
|e| log_and_map("Secp256r1.start", e, crypto::GetRandomFailed),
)?))
}
@ -56,13 +59,14 @@ impl crypto::SupportedKxGroup for Secp256r1 {
}
}
/// A secp384r1-based key exchange
#[derive(Debug)]
pub struct Secp384r1;
impl crypto::SupportedKxGroup for Secp384r1 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(ex::ExKeyExchange::with_secp384r1().map_err(
|e| map_error_stack("Secp384r1.start", e, crypto::GetRandomFailed),
Ok(Box::new(ex::KeyExchange::with_secp384r1().map_err(
|e| log_and_map("Secp384r1.start", e, crypto::GetRandomFailed),
)?))
}
@ -71,13 +75,14 @@ impl crypto::SupportedKxGroup for Secp384r1 {
}
}
/// A secp521r1-based key exchange
#[derive(Debug)]
pub struct Secp521r1;
impl crypto::SupportedKxGroup for Secp521r1 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(ex::ExKeyExchange::with_secp521r1().map_err(
|e| map_error_stack("Secp521r1.start", e, crypto::GetRandomFailed),
Ok(Box::new(ex::KeyExchange::with_secp521r1().map_err(
|e| log_and_map("Secp521r1.start", e, crypto::GetRandomFailed),
)?))
}
@ -86,13 +91,14 @@ impl crypto::SupportedKxGroup for Secp521r1 {
}
}
/// A ffedhe2048-based key exchange
#[derive(Debug)]
pub struct FfDHe2048;
impl crypto::SupportedKxGroup for FfDHe2048 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(dh::DhKeyExchange::generate_ffdhe_2048().map_err(
|e| map_error_stack("FfDHe2048.start", e, crypto::GetRandomFailed),
Ok(Box::new(dh::KeyExchange::generate_ffdhe_2048().map_err(
|e| log_and_map("FfDHe2048.start", e, crypto::GetRandomFailed),
)?))
}

View file

@ -2,17 +2,19 @@ use boring::{dh::Dh, error::ErrorStack, pkey::Private};
use foreign_types::ForeignType;
use rustls::crypto;
use crate::helper::{cvt, cvt_p, map_error_stack};
use crate::helper::{cvt, cvt_p, log_and_map};
use super::DhKeyType;
pub struct DhKeyExchange {
/// This type can be used to perform a
/// Diffie-Hellman key exchange.
pub struct KeyExchange {
dh: Dh<Private>,
pub_bytes: Vec<u8>,
key_type: DhKeyType,
}
impl DhKeyExchange {
impl KeyExchange {
// Generate a new KeyExchange with a random FFDHE_2048 private key
pub fn generate_ffdhe_2048() -> Result<Self, ErrorStack> {
let mut me = Self {
@ -49,9 +51,11 @@ impl DhKeyExchange {
/// Generate a shared secret with the other's raw public key
fn diffie_hellman(&self, raw_public_key: &[u8]) -> Result<Vec<u8>, ErrorStack> {
let peer = boring::bn::BigNum::from_slice(raw_public_key).unwrap();
let peer = boring::bn::BigNum::from_slice(raw_public_key)?;
let secret_len = unsafe { cvt(boring_sys::DH_size(self.dh.as_ptr()))? } as usize;
let mut secret = vec![0u8; secret_len];
let secret_len = unsafe {
cvt(boring_sys::DH_compute_key_padded(
secret.as_mut_ptr(),
@ -59,12 +63,13 @@ impl DhKeyExchange {
self.dh.as_ptr(),
))?
} as usize;
secret.truncate(secret_len);
Ok(secret)
}
}
impl crypto::ActiveKeyExchange for DhKeyExchange {
impl crypto::ActiveKeyExchange for KeyExchange {
fn complete(
self: Box<Self>,
peer_pub_key: &[u8],
@ -78,8 +83,8 @@ impl crypto::ActiveKeyExchange for DhKeyExchange {
Ok(crypto::SharedSecret::from(
self.diffie_hellman(peer_pub_key)
.map_err(|e| {
map_error_stack(
"dh.diffie_hellman",
log_and_map(
"dh::KeyExchange::diffie_hellman",
e,
rustls::PeerMisbehaved::InvalidKeyShare,
)
@ -102,13 +107,13 @@ impl crypto::ActiveKeyExchange for DhKeyExchange {
#[cfg(test)]
mod tests {
use crate::kx::dh::DhKeyExchange;
use crate::kx::dh::KeyExchange;
use rustls::crypto::ActiveKeyExchange;
#[test]
fn test_derive_dh() {
let alice = DhKeyExchange::generate_ffdhe_2048().unwrap();
let bob = DhKeyExchange::generate_ffdhe_2048().unwrap();
let alice = KeyExchange::generate_ffdhe_2048().unwrap();
let bob = KeyExchange::generate_ffdhe_2048().unwrap();
let shared_secret1 = alice.diffie_hellman(bob.pub_key()).unwrap();
let shared_secret2 = bob.diffie_hellman(alice.pub_key()).unwrap();

View file

@ -14,56 +14,59 @@ use foreign_types::ForeignType;
use rustls::crypto;
use spki::der::Decode;
use crate::helper::{cvt, cvt_p, map_error_stack};
use crate::helper::{cvt, cvt_p, log_and_map};
use super::DhKeyType;
pub struct ExKeyExchange {
/// This type can be used to perform an
/// Eliptic Curve or Edwards Curve key
/// exchange.
pub struct KeyExchange {
own_key: PKey<Private>,
pub_bytes: Vec<u8>,
key_type: DhKeyType,
}
impl ExKeyExchange {
/// Creates a new KeyExchange using a random
/// private key for the X25519 Edwards curve
impl KeyExchange {
/// Creates a new `KeyExchange` using a random
/// private key for the `X25519` Edwards curve
pub fn with_x25519() -> Result<Self, ErrorStack> {
Self::ed_from_curve(Nid::from_raw(boring_sys::NID_X25519))
}
/// Creates a new KeyExchange using a random
/// private key for the X448 Edwards curve
/// Creates a new `KeyExchange` using a random
/// private key for the `X448` Edwards curve
pub fn with_x448() -> Result<Self, ErrorStack> {
Self::ed_from_curve(Nid::from_raw(boring_sys::NID_X448))
}
/// Creates a new KeyExchange using a random
/// private key for sepc256r1 curve
/// Also known as X9_62_PRIME256V1
/// Creates a new `KeyExchange` using a random
/// private key for `sepc256r1` curve
/// Also known as `X9_62_PRIME256V1`
pub fn with_secp256r1() -> Result<Self, ErrorStack> {
Self::ec_from_curve(Nid::X9_62_PRIME256V1)
}
/// Creates a new KeyExchange using a random
/// private key for sepc384r1 curve
/// Creates a new `KeyExchange` using a random
/// private key for `sepc384r1` curve
pub fn with_secp384r1() -> Result<Self, ErrorStack> {
Self::ec_from_curve(Nid::SECP384R1)
}
/// Creates a new KeyExchange using a random
/// private key for sep521r1 curve
/// Creates a new `KeyExchange` using a random
/// private key for `sep521r1` curve
pub fn with_secp521r1() -> Result<Self, ErrorStack> {
Self::ec_from_curve(Nid::SECP521R1)
}
/// Allows getting a new KeyExchange using Eliptic Curves
/// Allows getting a new `KeyExchange` using Eliptic Curves
/// on the specified curve
fn ec_from_curve(nid: Nid) -> Result<Self, ErrorStack> {
let ec_group = EcGroup::from_curve_name(nid)?;
let ec_key = EcKey::generate(&ec_group)?;
let own_key = PKey::from_ec_key(ec_key)?;
let pub_bytes = Self::raw_public_key(&own_key);
let pub_bytes = Self::raw_public_key(&own_key)?;
Ok(Self {
own_key,
pub_bytes,
@ -71,7 +74,7 @@ impl ExKeyExchange {
})
}
/// Allows getting a new KeyExchange using Edwards Curves
/// Allows getting a new `KeyExchange` using Edwards Curves
/// on the specified curve
fn ed_from_curve(nid: Nid) -> Result<Self, ErrorStack> {
let pkey_ctx = unsafe {
@ -91,7 +94,7 @@ impl ExKeyExchange {
PKey::from_ptr(pkey)
};
let pub_bytes = Self::raw_public_key(&own_key);
let pub_bytes = Self::raw_public_key(&own_key)?;
Ok(Self {
own_key,
@ -101,14 +104,19 @@ impl ExKeyExchange {
}
/// Decodes a SPKI public key to it's raw public key component
fn raw_public_key(pkey: &PKeyRef<Private>) -> Vec<u8> {
let spki = pkey.public_key_to_der().unwrap();
fn raw_public_key(pkey: &PKeyRef<Private>) -> Result<Vec<u8>, ErrorStack> {
let spki = pkey.public_key_to_der()?;
// parse the key
let key = spki::SubjectPublicKeyInfoRef::from_der(spki.as_ref()).unwrap();
let pkey = spki::SubjectPublicKeyInfoRef::from_der(spki.as_ref())
.expect("failed parsing spki bytes");
// return the raw public key as a new vec
Vec::from(key.subject_public_key.as_bytes().unwrap())
Ok(Vec::from(
pkey.subject_public_key
.as_bytes()
.expect("failed getting raw spki bytes"),
))
}
/// Derives a shared secret using the peer's raw public key
@ -117,13 +125,11 @@ impl ExKeyExchange {
DhKeyType::EC((group, _)) => {
let mut bn_ctx = boring::bn::BigNumContext::new()?;
let point = crate::verify::ec::ec_point(group, &mut bn_ctx, peer_pub_key)?;
let point = crate::verify::ec::get_ec_point(group, &mut bn_ctx, peer_pub_key)?;
crate::verify::ec::ec_public_key(group, point.as_ref())?
}
DhKeyType::ED(nid) => {
crate::verify::ed::ed_public_key(peer_pub_key, Nid::from_raw(*nid))?
crate::verify::ec::create_public_key(group, point.as_ref())?
}
DhKeyType::ED(nid) => crate::verify::ed::public_key(peer_pub_key, Nid::from_raw(*nid))?,
_ => unimplemented!(),
};
@ -135,7 +141,7 @@ impl ExKeyExchange {
}
}
impl crypto::ActiveKeyExchange for ExKeyExchange {
impl crypto::ActiveKeyExchange for KeyExchange {
fn complete(
self: Box<Self>,
peer_pub_key: &[u8],
@ -143,8 +149,8 @@ impl crypto::ActiveKeyExchange for ExKeyExchange {
self.diffie_hellman(peer_pub_key)
.map(|x| crypto::SharedSecret::from(x.as_slice()))
.map_err(|e| {
map_error_stack(
"ex.diffie_hellman",
log_and_map(
"ex::KeyExchange::diffie_hellman",
e,
rustls::Error::PeerMisbehaved(rustls::PeerMisbehaved::InvalidKeyShare),
)
@ -169,13 +175,13 @@ impl crypto::ActiveKeyExchange for ExKeyExchange {
#[cfg(test)]
mod tests {
use super::ExKeyExchange;
use super::KeyExchange;
use rustls::crypto::ActiveKeyExchange;
#[test]
fn test_derive_ec() {
let alice = Box::new(ExKeyExchange::with_secp256r1().unwrap());
let bob = ExKeyExchange::with_secp256r1().unwrap();
let alice = Box::new(KeyExchange::with_secp256r1().unwrap());
let bob = KeyExchange::with_secp256r1().unwrap();
assert_eq!(
alice.diffie_hellman(bob.pub_key()).unwrap(),
@ -185,8 +191,8 @@ mod tests {
#[test]
fn test_derive_ed() {
let alice = Box::new(ExKeyExchange::with_x25519().unwrap());
let bob = ExKeyExchange::with_x25519().unwrap();
let alice = Box::new(KeyExchange::with_x25519().unwrap());
let bob = KeyExchange::with_x25519().unwrap();
assert_eq!(
alice.diffie_hellman(bob.pub_key()).unwrap(),

View file

@ -1,5 +1,6 @@
use std::sync::Arc;
use helper::log_and_map;
use rustls::{
crypto::{CryptoProvider, GetRandomFailed, SupportedKxGroup},
SupportedCipherSuite,
@ -18,6 +19,7 @@ mod tls12;
mod tls13;
mod verify;
/// The boringssl-based Rustls Crypto provider
pub static PROVIDER: &'static dyn CryptoProvider = &Provider;
#[derive(Debug)]
@ -25,7 +27,7 @@ struct Provider;
impl CryptoProvider for Provider {
fn fill_random(&self, bytes: &mut [u8]) -> Result<(), GetRandomFailed> {
boring::rand::rand_bytes(bytes).map_err(|_| GetRandomFailed)
boring::rand::rand_bytes(bytes).map_err(|e| log_and_map("rand_bytes", e, GetRandomFailed))
}
fn default_cipher_suites(&self) -> &'static [SupportedCipherSuite] {
@ -54,9 +56,7 @@ impl CryptoProvider for Provider {
&self,
key_der: PrivateKeyDer<'static>,
) -> Result<std::sync::Arc<dyn rustls::sign::SigningKey>, rustls::Error> {
sign::BoringPrivateKey::try_from(key_der)
.map(|x| Arc::new(x) as _)
.map_err(|_| rustls::Error::General("invalid private key".into()))
sign::BoringPrivateKey::try_from(key_der).map(|x| Arc::new(x) as _)
}
fn signature_verification_algorithms(&self) -> rustls::WebPkiSupportedAlgorithms {

View file

@ -9,6 +9,8 @@ use boring::{
use rustls::{sign::SigningKey, SignatureScheme};
use rustls_pki_types::PrivateKeyDer;
use crate::helper::log_and_map;
const ALL_RSA_SCHEMES: &[SignatureScheme] = &[
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::RSA_PSS_SHA384,
@ -24,6 +26,8 @@ const ALL_EC_SCHEMES: &[SignatureScheme] = &[
SignatureScheme::ECDSA_NISTP521_SHA512,
];
/// An abstraction over a boringssl private key
/// used for signing
#[derive(Debug)]
pub struct BoringPrivateKey(Arc<boring::pkey::PKey<Private>>, rustls::SignatureAlgorithm);
@ -33,10 +37,12 @@ impl TryFrom<PrivateKeyDer<'static>> for BoringPrivateKey {
fn try_from(value: PrivateKeyDer<'static>) -> Result<Self, Self::Error> {
let pkey = match value {
PrivateKeyDer::Pkcs8(der) => {
boring::pkey::PKey::private_key_from_pkcs8(der.secret_pkcs8_der()).map_err(|_| ())
boring::pkey::PKey::private_key_from_pkcs8(der.secret_pkcs8_der())
.map_err(|e| log_and_map("private_key_from_pkcs8", e, ()))
}
PrivateKeyDer::Pkcs1(der) => {
boring::pkey::PKey::private_key_from_der(der.secret_pkcs1_der()).map_err(|_| ())
boring::pkey::PKey::private_key_from_der(der.secret_pkcs1_der())
.map_err(|e| log_and_map("private_key_from_der", e, ()))
}
_ => Err(()),
}
@ -120,6 +126,7 @@ impl SigningKey for BoringPrivateKey {
}
}
/// A boringssl-based Signer
#[derive(Debug)]
pub struct BoringSigner(Arc<boring::pkey::PKey<Private>>, rustls::SignatureScheme);
@ -156,10 +163,7 @@ impl BoringSigner {
ec_signer_from_params(self.0.as_ref(), MessageDigest::sha512())
}
SignatureScheme::ED25519 => {
Signer::new_without_digest(self.0.as_ref()).expect("failed getting signer")
}
SignatureScheme::ED448 => {
SignatureScheme::ED25519 | SignatureScheme::ED448 => {
Signer::new_without_digest(self.0.as_ref()).expect("failed getting signer")
}
@ -178,7 +182,7 @@ impl rustls::sign::Signer for BoringSigner {
let toatl_len = signer
.sign(&mut msg_with_sig[..])
.map_err(|_| rustls::Error::General("failed signing".into()))?;
.map_err(|e| log_and_map("sign", e, rustls::Error::General("failed signing".into())))?;
msg_with_sig.truncate(toatl_len);
Ok(msg_with_sig)
}

View file

@ -2,6 +2,8 @@ use boring::{error::ErrorStack, hash::MessageDigest};
use rustls::SignatureScheme;
use rustls_pki_types::{InvalidSignature, SignatureVerificationAlgorithm};
use crate::helper;
pub struct BoringEcVerifier(SignatureScheme);
impl BoringEcVerifier {
@ -17,11 +19,15 @@ impl SignatureVerificationAlgorithm for BoringEcVerifier {
message: &[u8],
signature: &[u8],
) -> Result<(), rustls_pki_types::InvalidSignature> {
let (group, mut bn_ctx) = setup_ec_key(self.0);
let ec_point =
ec_point(group.as_ref(), bn_ctx.as_mut(), public_key).map_err(|_| InvalidSignature)?;
let public_key =
ec_public_key(group.as_ref(), ec_point.as_ref()).map_err(|_| InvalidSignature)?;
let (group, mut bn_ctx) = setup_ec_key(self.0)
.map_err(|e| helper::log_and_map("setup_ec_key", e, InvalidSignature))?;
let ec_point = get_ec_point(group.as_ref(), bn_ctx.as_mut(), public_key)
.map_err(|e| helper::log_and_map("ec_point", e, InvalidSignature))?;
let public_key = create_public_key(group.as_ref(), ec_point.as_ref())
.map_err(|e| helper::log_and_map("ec_public_key", e, InvalidSignature))?;
let mut verifier = match self.0 {
SignatureScheme::ECDSA_NISTP256_SHA256 => {
ec_verifier_from_params(public_key.as_ref(), MessageDigest::sha256())
@ -34,9 +40,11 @@ impl SignatureVerificationAlgorithm for BoringEcVerifier {
}
_ => unimplemented!(),
};
}
.map_err(|e| helper::log_and_map("ec_verifier_from_params", e, InvalidSignature))?;
verifier.verify_oneshot(signature, message).map_or_else(
|_| Err(InvalidSignature),
|e| Err(helper::log_and_map("verify_oneshot", e, InvalidSignature)),
|res| if res { Ok(()) } else { Err(InvalidSignature) },
)
}
@ -74,30 +82,27 @@ impl SignatureVerificationAlgorithm for BoringEcVerifier {
fn ec_verifier_from_params(
key: &boring::pkey::PKeyRef<boring::pkey::Public>,
digest: MessageDigest,
) -> boring::sign::Verifier {
let verifier = boring::sign::Verifier::new(digest, key).expect("failed getting verifier");
verifier
) -> Result<boring::sign::Verifier, ErrorStack> {
boring::sign::Verifier::new(digest, key)
}
fn group_for_scheme(scheme: SignatureScheme) -> boring::ec::EcGroup {
fn group_for_scheme(scheme: SignatureScheme) -> Result<boring::ec::EcGroup, ErrorStack> {
let nid = match scheme {
SignatureScheme::ECDSA_NISTP256_SHA256 => boring::nid::Nid::X9_62_PRIME256V1,
SignatureScheme::ECDSA_NISTP384_SHA384 => boring::nid::Nid::SECP384R1,
SignatureScheme::ECDSA_NISTP521_SHA512 => boring::nid::Nid::SECP521R1,
_ => unimplemented!(),
};
boring::ec::EcGroup::from_curve_name(nid).expect("failed getting verify curve")
boring::ec::EcGroup::from_curve_name(nid)
}
fn setup_ec_key(scheme: SignatureScheme) -> (boring::ec::EcGroup, boring::bn::BigNumContext) {
(
group_for_scheme(scheme),
boring::bn::BigNumContext::new().unwrap(),
)
fn setup_ec_key(
scheme: SignatureScheme,
) -> Result<(boring::ec::EcGroup, boring::bn::BigNumContext), ErrorStack> {
Ok((group_for_scheme(scheme)?, boring::bn::BigNumContext::new()?))
}
pub(crate) fn ec_point(
pub(crate) fn get_ec_point(
group: &boring::ec::EcGroupRef,
bignum_ctx: &mut boring::bn::BigNumContextRef,
spki_spk: &[u8],
@ -105,7 +110,7 @@ pub(crate) fn ec_point(
boring::ec::EcPoint::from_bytes(group, spki_spk, bignum_ctx)
}
pub(crate) fn ec_public_key(
pub(crate) fn create_public_key(
group: &boring::ec::EcGroupRef,
ec_point: &boring::ec::EcPointRef,
) -> Result<boring::pkey::PKey<boring::pkey::Public>, ErrorStack> {

View file

@ -5,7 +5,7 @@ use foreign_types::ForeignType;
use rustls::SignatureScheme;
use rustls_pki_types::{InvalidSignature, SignatureVerificationAlgorithm};
use crate::helper::cvt_p;
use crate::helper::{cvt_p, log_and_map};
pub struct BoringEdVerifier(SignatureScheme);
@ -21,9 +21,11 @@ impl SignatureVerificationAlgorithm for BoringEdVerifier {
message: &[u8],
signature: &[u8],
) -> Result<(), rustls_pki_types::InvalidSignature> {
let public_key =
ed_public_key_for_scheme(public_key, self.0).map_err(|_| InvalidSignature)?;
let mut verifier = ed_verifier_from_params(public_key.as_ref());
let public_key = ed_public_key_for_scheme(public_key, self.0)
.map_err(|e| log_and_map("ed_public_key_for_scheme", e, InvalidSignature))?;
let mut verifier = ed_verifier_from_params(public_key.as_ref())
.map_err(|e| log_and_map("ed_verifier_from_params", e, InvalidSignature))?;
verifier.verify_oneshot(signature, message).map_or_else(
|_| Err(InvalidSignature),
@ -50,11 +52,8 @@ impl SignatureVerificationAlgorithm for BoringEdVerifier {
fn ed_verifier_from_params(
key: &boring::pkey::PKeyRef<boring::pkey::Public>,
) -> boring::sign::Verifier {
let verifier =
boring::sign::Verifier::new_without_digest(key).expect("failed getting verifier");
verifier
) -> Result<boring::sign::Verifier, ErrorStack> {
boring::sign::Verifier::new_without_digest(key)
}
fn ed_public_key_for_scheme(
@ -66,10 +65,10 @@ fn ed_public_key_for_scheme(
SignatureScheme::ED448 => boring_sys::EVP_PKEY_ED448,
_ => unimplemented!(),
});
ed_public_key(spki_spk, nid)
public_key(spki_spk, nid)
}
pub fn ed_public_key(
pub fn public_key(
spki_spk: &[u8],
nid: boring::nid::Nid,
) -> Result<boring::pkey::PKey<boring::pkey::Public>, ErrorStack> {

View file

@ -1,8 +1,16 @@
use boring::{hash::MessageDigest, rsa::Padding, sign::RsaPssSaltlen};
use boring::{
bn::BigNum,
hash::MessageDigest,
pkey::PKey,
rsa::{Padding, Rsa},
sign::RsaPssSaltlen,
};
use rustls::SignatureScheme;
use rustls_pki_types::{InvalidSignature, SignatureVerificationAlgorithm};
use spki::der::Reader;
use crate::helper::log_and_map;
pub struct BoringRsaVerifier(SignatureScheme);
impl BoringRsaVerifier {
@ -105,18 +113,24 @@ fn rsa_verifier_from_params(
pub(crate) fn decode_spki_spk(
spki_spk: &[u8],
) -> Result<boring::pkey::PKey<boring::pkey::Public>, InvalidSignature> {
) -> Result<PKey<boring::pkey::Public>, InvalidSignature> {
// public_key: unfortunately this is not a whole SPKI, but just the key material.
// decode the two integers manually.
let mut reader = spki::der::SliceReader::new(spki_spk).map_err(|_| InvalidSignature)?;
let ne: [spki::der::asn1::UintRef; 2] = reader.decode().map_err(|_| InvalidSignature)?;
let mut reader = spki::der::SliceReader::new(spki_spk)
.map_err(|e| log_and_map("SliceReader::new", e, InvalidSignature))?;
let ne: [spki::der::asn1::UintRef; 2] = reader
.decode()
.map_err(|e| log_and_map("SliceReader::decode", e, InvalidSignature))?;
let n = boring::bn::BigNum::from_slice(ne[0].as_bytes()).map_err(|_| InvalidSignature)?;
let e = boring::bn::BigNum::from_slice(ne[1].as_bytes()).map_err(|_| InvalidSignature)?;
let n = BigNum::from_slice(ne[0].as_bytes())
.map_err(|e| log_and_map("BigNum::from_slice", e, InvalidSignature))?;
let e = BigNum::from_slice(ne[1].as_bytes())
.map_err(|e| log_and_map("BigNum::from_slice", e, InvalidSignature))?;
boring::pkey::PKey::from_rsa(
boring::rsa::Rsa::from_public_components(n, e).map_err(|_| InvalidSignature)?,
PKey::from_rsa(
Rsa::from_public_components(n, e)
.map_err(|e| log_and_map("Rsa::from_public_components", e, InvalidSignature))?,
)
.map_err(|_| InvalidSignature)
.map_err(|e| log_and_map("Pkey::from_rsa", e, InvalidSignature))
}