Initial dump for a boring-rustls-provider

This is just a dump of me figuring out how to interface with boring and rustls.
It works to establish a connection and exchange data but I haven't written real tests yet, nor did I cleanup the code or made the effort to make it look nice.
There is probably some code in here that should rather live in the `boring` crate.
This commit is contained in:
Jan Rüth 2023-11-19 17:41:54 +01:00
commit 5c45b9426b
24 changed files with 2355 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.DS_Store
/target
/.vscode
/Cargo.lock

16
Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[workspace]
members = [
# the main library and tests
"boring-rustls-provider",
# tests and example code
"examples",
]
default-members = [
"examples",
"boring-rustls-provider",
]
resolver = "2"
[workspace.dependencies]
rustls = { version = "=0.22.0-alpha.4", default-features = false }

55
Readme.md Normal file
View file

@ -0,0 +1,55 @@
# boring-rustls-provider
This is supposed to be the start to a [boringssl](https://github.com/cloudflare/boring)-based [rustls](https://github.com/rustls/rustls) crypto provider.
## Status
This is just a dump of me figuring out how to interface with boring and rustls.
It works to establish a connection and exchange data but I haven't written real tests yet, nor did I cleanup the code or made the effort to make it look nice.
There is probably some code in here that should rather live in the `boring` crate.
Further, the rustls crypto provider API is still not stable it seems. This works currently with `rustls = 0.22.0-alpha.4`.
### Supported ciphers
Currently, supports only TLS 1.3:
```
AES_128_GCM_SHA256
AES_256_GCM_SHA256
CHACHA20_POLY1305_SHA256
```
### Key Exchange Algorithms
`ECDHE` with curves:
```
X25519
X448
secp256r1
secp384r1
secp521r1
```
`FFDHE` with:
```
ffdhe2048
```
### Signature Generation / Verification
```
RSA_PKCS1_SHA256,
RSA_PKCS1_SHA384,
RSA_PKCS1_SHA512,
RSA_PSS_SHA256,
RSA_PSS_SHA384
RSA_PSS_SHA512
ECDSA_NISTP256_SHA256
ECDSA_NISTP384_SHA384
ECDSA_NISTP521_SHA512
ED25519
ED448
```
## License
MIT

View file

@ -0,0 +1,34 @@
[package]
name = "boring-rustls-provider"
version = "0.0.1"
authors = ["Jan Rüth <boring-rustls-provider@djiehmail.com>"]
edition = "2021"
license = "MIT"
description = "Boringssl rustls provider"
publish = false
[features]
default = []
# Use a FIPS-validated version of boringssl.
#fips = ["boring/fips", "boring-sys/fips"]
logging = ["log"]
[dependencies]
aead = {version = "0.5", default_features = false, features = ["alloc"] }
boring = { version = "4.0", default-features = false }
boring-sys = { version = "4.0", default-features = false }
foreign-types = "0.5"
lazy_static = "1.4"
log = { version = "0.4.4", optional = true }
once_cell = "1"
rustls = { workspace = true }
rustls-pki-types = "0.2"
spki = "0.7"
webpki = { package = "rustls-webpki", version = "0.102.0-alpha.1", default-features = false, features = ["alloc", "std"] }
[dev-dependencies]
hex-literal = "0.4"

View file

@ -0,0 +1,177 @@
use std::marker::PhantomData;
use aead::{AeadCore, AeadInPlace, Nonce, Tag};
use boring::error::ErrorStack;
use rustls::crypto::cipher::{self, make_tls13_aad, Iv};
use rustls::{ConnectionTrafficSecrets, ContentType, ProtocolVersion};
use crate::helper::error_stack_to_aead_error;
use self::aead2::Algorithm;
pub(crate) mod aead2;
pub(crate) mod aes;
pub(crate) mod chacha20;
pub(crate) trait BoringCipher {
fn new() -> Algorithm;
fn key_size() -> usize;
fn extract_keys(key: cipher::AeadKey, iv: cipher::Iv) -> ConnectionTrafficSecrets;
}
pub(crate) trait BoringAead: BoringCipher + AeadCore + Send + Sync {}
pub(crate) struct BoringAeadCrypter<T: BoringAead> {
crypter: aead2::Crypter,
iv: Iv,
phantom: PhantomData<T>,
}
unsafe impl<T: BoringAead> Sync for BoringAeadCrypter<T> {}
unsafe impl<T: BoringAead> Send for BoringAeadCrypter<T> {}
impl<T: BoringAead> AeadCore for BoringAeadCrypter<T> {
// inherit all properties from the Algorithm
type NonceSize = T::NonceSize;
type TagSize = T::TagSize;
type CiphertextOverhead = T::CiphertextOverhead;
}
impl<T: BoringAead> BoringAeadCrypter<T> {
pub fn new(iv: Iv, key: &[u8]) -> Result<Self, ErrorStack> {
let cipher = <T as BoringCipher>::new();
assert_eq!(
cipher.nonce_len(),
rustls::crypto::cipher::Nonce::new(&iv, 0).0.len()
);
let crypter = BoringAeadCrypter {
crypter: aead2::Crypter::new(cipher, key)?,
iv,
phantom: PhantomData,
};
Ok(crypter)
}
}
impl<T: BoringAead> aead::AeadInPlace for BoringAeadCrypter<T> {
fn encrypt_in_place_detached(
&self,
nonce: &Nonce<Self>,
associated_data: &[u8],
buffer: &mut [u8],
) -> aead::Result<Tag<Self>> {
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))?;
Ok(tag)
}
fn decrypt_in_place_detached(
&self,
nonce: &Nonce<Self>,
associated_data: &[u8],
buffer: &mut [u8],
tag: &Tag<Self>,
) -> aead::Result<()> {
self.crypter
.open_in_place(&nonce, &associated_data, buffer, tag)
.map_err(|e| error_stack_to_aead_error("open_in_place", e))?;
Ok(())
}
}
impl<T> cipher::MessageEncrypter for BoringAeadCrypter<T>
where
T: BoringAead,
{
fn encrypt(
&self,
msg: cipher::BorrowedPlainMessage,
seq: u64,
) -> Result<cipher::OpaqueMessage, rustls::Error> {
let total_len = msg.payload.len() + 1 + self.crypter.max_overhead();
let mut payload = Vec::with_capacity(total_len);
payload.extend_from_slice(msg.payload);
payload.push(msg.typ.get_u8());
let nonce = cipher::Nonce::new(&self.iv, seq);
let aad = cipher::make_tls13_aad(total_len);
self.encrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, &mut payload)
.map_err(|_| rustls::Error::EncryptError)
.map(|_| {
cipher::OpaqueMessage::new(
ContentType::ApplicationData,
ProtocolVersion::TLSv1_2,
payload,
)
})
}
}
impl<T> cipher::MessageDecrypter for BoringAeadCrypter<T>
where
T: BoringAead,
{
fn decrypt(
&self,
mut m: cipher::OpaqueMessage,
seq: u64,
) -> Result<cipher::PlainMessage, rustls::Error> {
let payload = m.payload_mut();
// construct nonce
let nonce = cipher::Nonce::new(&self.iv, seq);
// construct the aad
let aad = make_tls13_aad(payload.len());
// decrypt on clone to ensure this can be done in parallel
self.decrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, payload)
.map_err(|_| rustls::Error::DecryptError)
.and_then(|_| m.into_tls13_unpadded_message())
}
}
pub(crate) struct Aead<T: BoringCipher>(PhantomData<T>);
unsafe impl<T: BoringCipher> Sync for Aead<T> {}
unsafe impl<T: BoringCipher> Send for Aead<T> {}
impl<T: BoringCipher> Aead<T> {
pub const DEFAULT: Self = Self(PhantomData);
}
impl<T: BoringAead + 'static> cipher::Tls13AeadAlgorithm for Aead<T> {
fn encrypter(&self, key: cipher::AeadKey, iv: cipher::Iv) -> Box<dyn cipher::MessageEncrypter> {
Box::new(
BoringAeadCrypter::<T>::new(iv, key.as_ref()).expect("failed to create AEAD crypter"),
)
}
fn decrypter(&self, key: cipher::AeadKey, iv: cipher::Iv) -> Box<dyn cipher::MessageDecrypter> {
Box::new(
BoringAeadCrypter::<T>::new(iv, key.as_ref()).expect("failed to create AEAD crypter"),
)
}
fn key_len(&self) -> usize {
<T as BoringCipher>::key_size()
}
fn extract_keys(
&self,
key: cipher::AeadKey,
iv: cipher::Iv,
) -> Result<ConnectionTrafficSecrets, cipher::UnsupportedOperationError> {
Ok(<T as BoringCipher>::extract_keys(key, iv))
}
}

View file

@ -0,0 +1,187 @@
use std::ptr;
use boring::error::ErrorStack;
use crate::helper::{cvt, cvt_p};
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.
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.
pub fn aes_256_gcm() -> Self {
Self(unsafe { boring_sys::EVP_aead_aes_256_gcm() })
}
/// ChaCha20 and Poly1305 as described in RFC 8439.
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)]
pub fn xchacha20_poly1305() -> Self {
Self(unsafe { boring_sys::EVP_aead_xchacha20_poly1305() })
}
/// Returns the length, in bytes, of the keys used by aead
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.
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)]
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.
pub fn nonce_len(&self) -> usize {
unsafe { boring_sys::EVP_AEAD_nonce_length(self.0) }
}
}
pub struct Crypter {
ctx: *mut boring_sys::EVP_AEAD_CTX,
max_overhead: usize,
nonce_len: usize,
}
unsafe impl Send for Crypter {}
unsafe impl Sync for Crypter {}
impl Crypter {
pub fn new(aead_alg: Algorithm, key: &[u8]) -> Result<Self, ErrorStack> {
assert_eq!(aead_alg.key_length(), key.len());
boring_sys::init();
let this = unsafe {
Self {
ctx: 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)
}
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
pub fn seal_in_place(
&self,
nonce: &[u8],
associated_data: &[u8],
buffer: &mut [u8],
tag: &mut [u8],
) -> Result<usize, ErrorStack> {
assert!(tag.len() >= self.max_overhead);
assert_eq!(nonce.len(), self.nonce_len);
let mut tag_len = tag.len();
unsafe {
cvt(boring_sys::EVP_AEAD_CTX_seal_scatter(
self.ctx,
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)
}
pub fn open_in_place(
&self,
nonce: &[u8],
associated_data: &[u8],
buffer: &mut [u8],
tag: &[u8],
) -> Result<(), ErrorStack> {
assert_eq!(nonce.len(), self.nonce_len);
unsafe {
cvt(boring_sys::EVP_AEAD_CTX_open_gather(
self.ctx,
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(())
}
}
impl Drop for Crypter {
fn drop(&mut self) {
unsafe {
boring_sys::EVP_AEAD_CTX_free(self.ctx);
}
}
}
#[cfg(test)]
mod tests {
use super::Crypter;
#[test]
fn in_out() {
let key = Crypter::new(super::Algorithm::aes_128_gcm(), &[0u8; 16]).unwrap();
let nonce = [0u8; 12];
let associated_data = "this is signed".as_bytes();
let mut buffer = Vec::with_capacity(26);
buffer.push('A' as u8);
buffer.push('B' as u8);
buffer.push('C' as u8);
buffer.push('D' as u8);
buffer.push('E' as u8);
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());
}
}

View file

@ -0,0 +1,90 @@
use aead::consts::{U12, U16};
use rustls::{crypto::cipher, ConnectionTrafficSecrets};
use super::{aead2::Algorithm, BoringAead, BoringCipher};
pub struct Aes128 {}
impl BoringAead for Aes128 {}
unsafe impl Send for Aes128 {}
unsafe impl Sync for Aes128 {}
impl BoringCipher for Aes128 {
fn new() -> Algorithm {
Algorithm::aes_128_gcm()
}
fn key_size() -> usize {
16
}
fn extract_keys(key: cipher::AeadKey, iv: cipher::Iv) -> ConnectionTrafficSecrets {
ConnectionTrafficSecrets::Aes128Gcm { key, iv }
}
}
impl aead::AeadCore for Aes128 {
type NonceSize = U12;
type TagSize = U16;
type CiphertextOverhead = U16;
}
pub struct Aes256 {}
impl BoringAead for Aes256 {}
unsafe impl Send for Aes256 {}
unsafe impl Sync for Aes256 {}
impl BoringCipher for Aes256 {
fn new() -> Algorithm {
Algorithm::aes_256_gcm()
}
fn key_size() -> usize {
32
}
fn extract_keys(key: cipher::AeadKey, iv: cipher::Iv) -> ConnectionTrafficSecrets {
ConnectionTrafficSecrets::Aes256Gcm { key, iv }
}
}
impl aead::AeadCore for Aes256 {
type NonceSize = U12;
type TagSize = U16;
type CiphertextOverhead = U16;
}
#[cfg(test)]
mod tests {
use aead::{generic_array::GenericArray, AeadCore, Nonce, Tag};
use crate::aead::{
aes::{Aes128, Aes256},
BoringCipher,
};
#[test]
fn ensure_aes128_aead_core() {
let alg = Aes128::new();
let nonce = Nonce::<Aes128>::default();
assert_eq!(nonce.len(), alg.nonce_len());
let tag = Tag::<Aes128>::default();
assert_eq!(alg.max_tag_len(), tag.len());
let overhead = GenericArray::<u8, <Aes128 as AeadCore>::CiphertextOverhead>::default();
assert_eq!(alg.max_overhead(), overhead.len());
}
#[test]
fn ensure_aes256_aead_core() {
let alg = Aes256::new();
let nonce = Nonce::<Aes256>::default();
assert_eq!(nonce.len(), alg.nonce_len());
let tag = Tag::<Aes256>::default();
assert_eq!(alg.max_tag_len(), tag.len());
let overhead = GenericArray::<u8, <Aes256 as AeadCore>::CiphertextOverhead>::default();
assert_eq!(alg.max_overhead(), overhead.len());
}
}

View file

@ -0,0 +1,56 @@
use aead::{
consts::{U12, U16},
AeadCore,
};
use rustls::{crypto::cipher, ConnectionTrafficSecrets};
use super::{aead2::Algorithm, BoringAead, BoringCipher};
pub struct ChaCha20Poly1305 {}
impl BoringAead for ChaCha20Poly1305 {}
unsafe impl Send for ChaCha20Poly1305 {}
unsafe impl Sync for ChaCha20Poly1305 {}
impl BoringCipher for ChaCha20Poly1305 {
fn new() -> Algorithm {
Algorithm::chacha20_poly1305()
}
fn key_size() -> usize {
32
}
fn extract_keys(key: cipher::AeadKey, iv: cipher::Iv) -> ConnectionTrafficSecrets {
ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }
}
}
impl AeadCore for ChaCha20Poly1305 {
type NonceSize = U12;
type TagSize = U16;
type CiphertextOverhead = U16;
}
#[cfg(test)]
mod tests {
use aead::{generic_array::GenericArray, AeadCore, Nonce, Tag};
use super::ChaCha20Poly1305;
use crate::aead::BoringCipher;
#[test]
fn ensure_aead_core() {
let alg = ChaCha20Poly1305::new();
let nonce = Nonce::<ChaCha20Poly1305>::default();
assert_eq!(nonce.len(), alg.nonce_len());
let tag = Tag::<ChaCha20Poly1305>::default();
assert_eq!(alg.max_tag_len(), tag.len());
let overhead =
GenericArray::<u8, <ChaCha20Poly1305 as AeadCore>::CiphertextOverhead>::default();
assert_eq!(alg.max_overhead(), overhead.len());
}
}

View file

@ -0,0 +1,70 @@
use rustls::crypto::hash;
pub const SHA256: &dyn hash::Hash = &Hash(boring::nid::Nid::SHA256);
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();
Box::new(HasherContext(hasher))
}
fn hash(&self, data: &[u8]) -> hash::Output {
let mut hasher = self.start();
hasher.update(data);
hasher.finish()
}
fn algorithm(&self) -> hash::HashAlgorithm {
match self.0 {
boring::nid::Nid::SHA256 => hash::HashAlgorithm::SHA256,
_ => unimplemented!(),
}
}
fn output_len(&self) -> usize {
boring::hash::MessageDigest::from_nid(self.0)
.unwrap()
.size()
}
}
struct HasherContext(boring::hash::Hasher);
impl hash::Context for HasherContext {
fn fork_finish(&self) -> hash::Output {
let mut cloned = self.0.clone();
hash::Output::new(&cloned.finish().unwrap()[..])
}
fn fork(&self) -> Box<dyn hash::Context> {
Box::new(HasherContext(self.0.clone()))
}
fn finish(mut self: Box<Self>) -> hash::Output {
hash::Output::new(&self.0.finish().unwrap()[..])
}
fn update(&mut self, data: &[u8]) {
self.0.update(data).unwrap();
}
}
#[cfg(test)]
mod tests {
use super::SHA256;
use hex_literal::hex;
#[test]
fn test_sha256() {
let hash = SHA256.hash("test".as_bytes());
assert_eq!(
hash.as_ref(),
hex!("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")
);
}
}

View file

@ -0,0 +1,42 @@
use std::os::raw::c_int;
use boring::error::ErrorStack;
#[cfg(feature = "log")]
use log::trace;
/// 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.
pub(crate) fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
if r.is_null() {
Err(ErrorStack::get())
} else {
Ok(r)
}
}
/// Check the value returned from a BoringSSL ffi call that
/// returns a integer.
///
/// Returns the BoringSSL 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 {
Err(ErrorStack::get())
} else {
Ok(r)
}
}
#[cfg(feature = "log")]
pub(crate) fn error_stack_to_aead_error(func: &'static str, e: ErrorStack) -> aead::Error {
trace!("failed {}, error: {}", func, e);
aead::Error
}
#[cfg(not(feature = "log"))]
pub(crate) fn error_stack_to_aead_error(_: &'static str, _: ErrorStack) -> aead::Error {
aead::Error
}

View file

@ -0,0 +1,184 @@
use std::marker::PhantomData;
use rustls::crypto::tls13::{self, Hkdf as RustlsHkdf};
use crate::helper::{cvt, cvt_p};
pub trait BoringHash: Send + Sync {
fn new() -> boring::hash::MessageDigest;
}
pub struct Sha256();
impl BoringHash for Sha256 {
fn new() -> boring::hash::MessageDigest {
boring::hash::MessageDigest::sha256()
}
}
pub struct Hkdf<T: BoringHash>(PhantomData<T>);
impl<T: BoringHash> Hkdf<T> {
pub const DEFAULT: Self = Self(PhantomData);
}
impl<T: BoringHash> RustlsHkdf for Hkdf<T> {
/// `HKDF-Extract(salt, 0_HashLen)`
///
/// `0_HashLen` is a string of `HashLen` zero bytes.
///
/// A `salt` of `None` should be treated as a sequence of `HashLen` zero bytes.
fn extract_from_zero_ikm(
&self,
salt: Option<&[u8]>,
) -> Box<dyn rustls::crypto::tls13::HkdfExpander> {
let hash_size = T::new().size();
let secret = [0u8; boring_sys::EVP_MAX_MD_SIZE as usize];
let secret_len = hash_size;
self.extract_from_secret(salt, &secret[..secret_len])
}
/// `HKDF-Extract(salt, secret)`
///
/// A `salt` of `None` should be treated as a sequence of `HashLen` zero bytes.
fn extract_from_secret(
&self,
salt: Option<&[u8]>,
secret: &[u8],
) -> Box<dyn rustls::crypto::tls13::HkdfExpander> {
let digest = T::new();
let hash_size = digest.size();
let mut prk = [0u8; boring_sys::EVP_MAX_MD_SIZE as usize];
let mut prk_len = 0;
// if salt isn't set we usen these bytes here as salt
let salt_bytes = [0u8; boring_sys::EVP_MAX_MD_SIZE as usize];
let salt = if let Some(salt) = salt {
salt
} else {
&salt_bytes[..hash_size]
};
unsafe {
cvt(boring_sys::HKDF_extract(
prk.as_mut_ptr(),
&mut prk_len,
digest.as_ptr(),
secret.as_ptr(),
secret.len(),
salt.as_ptr(),
salt.len(),
))
.expect("HKDF_extract failed");
}
Box::new(HkdfExpander {
prk,
prk_len,
digest,
})
}
fn expander_for_okm(
&self,
okm: &rustls::crypto::tls13::OkmBlock,
) -> Box<dyn rustls::crypto::tls13::HkdfExpander> {
let okm = okm.as_ref();
let mut prk = [0u8; boring_sys::EVP_MAX_MD_SIZE as usize];
let prk_len = okm.len();
prk[..prk_len].copy_from_slice(okm);
Box::new(HkdfExpander {
prk,
prk_len,
digest: T::new(),
})
}
fn hmac_sign(
&self,
key: &rustls::crypto::tls13::OkmBlock,
message: &[u8],
) -> rustls::crypto::hmac::Tag {
let digest = T::new();
let mut hash = [0u8; boring_sys::EVP_MAX_MD_SIZE as usize];
let mut hash_len = 0u32;
unsafe {
cvt_p(boring_sys::HMAC(
digest.as_ptr(),
key.as_ref().as_ptr() as _,
key.as_ref().len(),
message.as_ptr(),
message.len(),
hash.as_mut_ptr(),
&mut hash_len,
))
.expect("HMAC failed");
}
rustls::crypto::hmac::Tag::new(&hash[..hash_len as usize])
}
}
pub struct HkdfExpander {
prk: [u8; boring_sys::EVP_MAX_MD_SIZE as usize],
prk_len: usize,
digest: boring::hash::MessageDigest,
}
impl tls13::HkdfExpander for HkdfExpander {
/// `HKDF-Expand(PRK, info, L)` into a slice.
///
/// Where:
///
/// - `PRK` is the implicit key material represented by this instance.
/// - `L` is `output.len()`.
/// - `info` is a slice of byte slices, which should be processed sequentially
/// (or concatenated if that is not possible).
///
/// Returns `Err(OutputLengthError)` if `L` is larger than `255 * HashLen`.
/// Otherwise, writes to `output`.
fn expand_slice(
&self,
info: &[&[u8]],
output: &mut [u8],
) -> Result<(), tls13::OutputLengthError> {
let info_concat = info.concat();
unsafe {
boring_sys::HKDF_expand(
output.as_mut_ptr(),
output.len(),
self.digest.as_ptr(),
self.prk.as_ptr(),
self.prk_len,
info_concat.as_ptr(),
info_concat.len(),
);
};
Ok(())
}
/// `HKDF-Expand(PRK, info, L=HashLen)` returned as a value.
///
/// - `PRK` is the implicit key material represented by this instance.
/// - `L := HashLen`.
/// - `info` is a slice of byte slices, which should be processed sequentially
/// (or concatenated if that is not possible).
///
/// This is infallible, because by definition `OkmBlock` is always exactly
/// `HashLen` bytes long.
fn expand_block(&self, info: &[&[u8]]) -> tls13::OkmBlock {
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();
tls13::OkmBlock::new(&output[..output_len])
}
fn hash_len(&self) -> usize {
self.digest.size()
}
}

View file

@ -0,0 +1,144 @@
use std::{os::raw::c_void, ptr};
use boring::hash::MessageDigest;
use rustls::crypto;
use crate::helper::{cvt, cvt_p};
#[allow(unused)]
pub const SHA256: &dyn crypto::hmac::Hmac = &BoringHmac(boring::nid::Nid::SHA256);
pub 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 = cvt_p(boring_sys::HMAC_CTX_new()).unwrap();
let md = boring::hash::MessageDigest::from_nid(self.0).unwrap();
BoringHmacKey {
ctx,
md,
key: key.to_vec(),
}
})
}
fn hash_output_len(&self) -> usize {
boring::hash::MessageDigest::from_nid(self.0)
.unwrap()
.size()
}
}
struct BoringHmacKey {
ctx: *mut boring_sys::HMAC_CTX,
md: MessageDigest,
key: Vec<u8>,
}
impl Clone for BoringHmacKey {
fn clone(&self) -> Self {
let ctx = unsafe {
let ctx = cvt_p(boring_sys::HMAC_CTX_new()).unwrap();
cvt(boring_sys::HMAC_CTX_copy(ctx, self.ctx)).unwrap();
ctx
};
Self {
ctx,
md: self.md.clone(),
key: self.key.clone(),
}
}
}
unsafe impl Sync for BoringHmacKey {}
unsafe impl Send for BoringHmacKey {}
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 {
// initialize a new hmac
cvt(boring_sys::HMAC_Init_ex(
self.ctx,
self.key.as_ptr() as *const c_void,
self.key.len(),
self.md.as_ptr(),
ptr::null_mut(),
))
.unwrap();
cvt(boring_sys::HMAC_Update(
self.ctx,
first.as_ptr(),
first.len(),
))
.unwrap();
for m in middle {
cvt(boring_sys::HMAC_Update(self.ctx, m.as_ptr(), m.len())).unwrap();
}
cvt(boring_sys::HMAC_Update(self.ctx, last.as_ptr(), last.len())).unwrap();
let mut out_len = 0;
cvt(boring_sys::HMAC_Final(
self.ctx,
out.as_mut_ptr(),
&mut out_len,
))
.unwrap();
&out[..out_len as usize]
})
}
fn tag_len(&self) -> usize {
self.md.size()
}
}
impl Drop for BoringHmacKey {
fn drop(&mut self) {
unsafe {
boring_sys::HMAC_CTX_free(self.ctx);
}
}
}
#[cfg(test)]
mod tests {
use super::SHA256;
use hex_literal::hex;
#[test]
fn test_sha256_hmac() {
let hasher = SHA256.with_key("Very Secret".as_bytes());
let _tag = hasher.sign_concat(
"yay".as_bytes(),
&["this".as_bytes(), "works".as_bytes()],
"well".as_bytes(),
);
let tag = hasher.sign_concat(
&[],
&[
"yay".as_bytes(),
"this".as_bytes(),
"works".as_bytes(),
"well".as_bytes(),
],
&[],
);
assert_eq!(
tag.as_ref(),
hex!("11fa4a6ee97bebfad9e1087145c556fec9a786cad0659aa10702d21bd2968305")
);
}
}

View file

@ -0,0 +1,139 @@
use rustls::crypto::{self, ActiveKeyExchange};
mod dh;
mod evp;
#[derive(Debug)]
enum DhKeyType {
EC(i32),
ED(i32),
FFDHE2048,
}
#[derive(Debug)]
pub struct X25519;
impl crypto::SupportedKxGroup for X25519 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(
evp::BoringEvpKey::generate_x25519().map_err(|_| crypto::GetRandomFailed)?,
))
}
fn name(&self) -> rustls::NamedGroup {
rustls::NamedGroup::X25519
}
}
#[derive(Debug)]
pub struct X448;
impl crypto::SupportedKxGroup for X448 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(
evp::BoringEvpKey::generate_x448().map_err(|_| crypto::GetRandomFailed)?,
))
}
fn name(&self) -> rustls::NamedGroup {
rustls::NamedGroup::X448
}
}
#[derive(Debug)]
pub struct Secp256r1;
impl crypto::SupportedKxGroup for Secp256r1 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(
evp::BoringEvpKey::generate_secp256r1().map_err(|_| crypto::GetRandomFailed)?,
))
}
fn name(&self) -> rustls::NamedGroup {
rustls::NamedGroup::secp256r1
}
}
#[derive(Debug)]
pub struct Secp384r1;
impl crypto::SupportedKxGroup for Secp384r1 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(
evp::BoringEvpKey::generate_secp384r1().map_err(|_| crypto::GetRandomFailed)?,
))
}
fn name(&self) -> rustls::NamedGroup {
rustls::NamedGroup::secp384r1
}
}
#[derive(Debug)]
pub struct Secp521r1;
impl crypto::SupportedKxGroup for Secp521r1 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(
evp::BoringEvpKey::generate_secp521r1().map_err(|_| crypto::GetRandomFailed)?,
))
}
fn name(&self) -> rustls::NamedGroup {
rustls::NamedGroup::secp521r1
}
}
#[derive(Debug)]
pub struct FfDHe2048;
impl crypto::SupportedKxGroup for FfDHe2048 {
fn start(&self) -> Result<Box<(dyn ActiveKeyExchange + 'static)>, rustls::Error> {
Ok(Box::new(
dh::BoringDhKey::generate_ffdhe_2048().map_err(|_| crypto::GetRandomFailed)?,
))
}
fn name(&self) -> rustls::NamedGroup {
rustls::NamedGroup::FFDHE2048
}
}
#[cfg(test)]
mod tests {
use rustls::crypto::ActiveKeyExchange;
#[test]
fn test_derive_ed() {
let alice = super::evp::BoringEvpKey::generate_x25519().unwrap();
let bob = super::evp::BoringEvpKey::generate_x25519().unwrap();
let shared_secret1 = alice.diffie_hellman(&bob.pub_key()).unwrap();
let shared_secret2 = bob.diffie_hellman(&alice.pub_key()).unwrap();
assert_eq!(shared_secret1, shared_secret2)
}
#[test]
fn test_derive_ec() {
let alice = super::evp::BoringEvpKey::generate_secp256r1().unwrap();
let bob = super::evp::BoringEvpKey::generate_secp256r1().unwrap();
let shared_secret1 = alice.diffie_hellman(&bob.pub_key()).unwrap();
let shared_secret2 = bob.diffie_hellman(&alice.pub_key()).unwrap();
assert_eq!(shared_secret1, shared_secret2)
}
#[test]
fn test_derive_dh() {
let alice = super::dh::BoringDhKey::generate_ffdhe_2048().unwrap();
let bob = super::dh::BoringDhKey::generate_ffdhe_2048().unwrap();
let shared_secret1 = alice.diffie_hellman(&bob.pub_key()).unwrap();
let shared_secret2 = bob.diffie_hellman(&alice.pub_key()).unwrap();
assert_eq!(shared_secret1, shared_secret2)
}
}

View file

@ -0,0 +1,98 @@
use boring::error::ErrorStack;
use foreign_types::ForeignType;
use rustls::crypto;
use crate::helper::{cvt, cvt_p};
use super::DhKeyType;
pub struct BoringDhKey {
dh: boring::dh::Dh<boring::pkey::Private>,
pub_bytes: Vec<u8>,
key_type: DhKeyType,
}
impl BoringDhKey {
pub fn generate_ffdhe_2048() -> Result<Self, ErrorStack> {
let mut me = Self {
dh: unsafe { boring::dh::Dh::from_ptr(cvt_p(boring_sys::DH_get_rfc7919_2048())?) },
pub_bytes: Vec::new(),
key_type: DhKeyType::FFDHE2048,
};
me.pub_bytes = unsafe {
// generate a new key pair
cvt(boring_sys::DH_generate_key(me.dh.as_ptr()))?;
// get a reference to the pub key
let pubkey = boring_sys::DH_get0_pub_key(me.dh.as_ptr());
// figure out how many bytes we need, round up to the next full byte
let size = (boring_sys::BN_num_bits(pubkey) as usize + 7) / 8;
// alloc a vector with enough capacity
let mut v = Vec::with_capacity(size);
// convert to binary representation
let after_size = boring_sys::BN_bn2bin(pubkey, v.as_mut_ptr());
// size should be what we calculated before
assert_eq!(size, after_size);
// ensure those bytes are accessible in the vec
v.set_len(size);
v
};
Ok(me)
}
pub fn diffie_hellman(&self, raw_public_key: &[u8]) -> Result<Vec<u8>, ErrorStack> {
let peer = boring::bn::BigNum::from_slice(raw_public_key).unwrap();
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(),
peer.as_ptr(),
self.dh.as_ptr(),
))?
} as usize;
secret.truncate(secret_len);
Ok(secret)
}
#[allow(unused)]
fn pub_key(&self) -> &[u8] {
self.pub_bytes.as_ref()
}
}
impl crypto::ActiveKeyExchange for BoringDhKey {
fn complete(
self: Box<Self>,
peer_pub_key: &[u8],
) -> Result<crypto::SharedSecret, rustls::Error> {
let expected_len = self.pub_bytes.len();
if peer_pub_key.len() != expected_len {
return Err(rustls::Error::from(rustls::PeerMisbehaved::InvalidKeyShare));
}
Ok(crypto::SharedSecret::from(
self.diffie_hellman(peer_pub_key)
.map_err(|x| rustls::Error::General(x.to_string()))?
.as_ref(),
))
}
fn pub_key(&self) -> &[u8] {
self.pub_bytes.as_ref()
}
fn group(&self) -> rustls::NamedGroup {
match self.key_type {
DhKeyType::FFDHE2048 => rustls::NamedGroup::FFDHE2048,
_ => unimplemented!(),
}
}
}

View file

@ -0,0 +1,315 @@
use std::{
mem::MaybeUninit,
ops::{Deref, DerefMut},
ptr::{self, NonNull},
};
use boring::error::ErrorStack;
use foreign_types::{ForeignType, ForeignTypeRef, Opaque};
use rustls::crypto;
use spki::der::Decode;
use crate::helper::{cvt, cvt_p};
use super::DhKeyType;
pub struct BoringEvpPkeyCtxRef(Opaque);
unsafe impl ForeignTypeRef for BoringEvpPkeyCtxRef {
type CType = boring_sys::EVP_PKEY_CTX;
}
unsafe impl Sync for BoringEvpPkeyCtxRef {}
unsafe impl Send for BoringEvpPkeyCtxRef {}
unsafe impl Sync for BoringEvpPkeyCtx {}
unsafe impl Send for BoringEvpPkeyCtx {}
pub struct BoringEvpPkeyCtx(NonNull<boring_sys::EVP_PKEY_CTX>);
unsafe impl ForeignType for BoringEvpPkeyCtx {
type CType = boring_sys::EVP_PKEY_CTX;
type Ref = BoringEvpPkeyCtxRef;
unsafe fn from_ptr(ptr: *mut Self::CType) -> Self {
Self(NonNull::new_unchecked(ptr))
}
fn as_ptr(&self) -> *mut Self::CType {
self.0.as_ptr()
}
}
impl Drop for BoringEvpPkeyCtx {
fn drop(&mut self) {
unsafe {
boring_sys::EVP_PKEY_CTX_free(self.0.as_ptr());
}
}
}
impl core::fmt::Debug for BoringEvpPkeyCtx {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("BoringEvpPkeyCtx").field(&self.0).finish()
}
}
impl Deref for BoringEvpPkeyCtx {
type Target = BoringEvpPkeyCtxRef;
fn deref(&self) -> &BoringEvpPkeyCtxRef {
unsafe { BoringEvpPkeyCtxRef::from_ptr(self.as_ptr()) }
}
}
impl DerefMut for BoringEvpPkeyCtx {
fn deref_mut(&mut self) -> &mut BoringEvpPkeyCtxRef {
unsafe { BoringEvpPkeyCtxRef::from_ptr_mut(self.as_ptr()) }
}
}
#[derive(Debug)]
pub struct BoringEvpKey {
/// the private key context for deriving shared secrets
dctx: BoringEvpPkeyCtx,
key_type: DhKeyType,
pub_bytes: Vec<u8>,
}
unsafe impl Sync for BoringEvpKey {}
unsafe impl Send for BoringEvpKey {}
impl BoringEvpKey {
pub fn generate_x25519() -> Result<Self, ErrorStack> {
unsafe {
Self::generate_with_ctx(
DhKeyType::ED(boring_sys::NID_X25519),
Self::generate_ctx_from_nid(boring_sys::NID_X25519)?,
)
}
}
pub fn generate_x448() -> Result<Self, ErrorStack> {
unsafe {
Self::generate_with_ctx(
DhKeyType::ED(boring_sys::NID_X448),
Self::generate_ctx_from_nid(boring_sys::NID_X448)?,
)
}
}
pub fn generate_secp256r1() -> Result<Self, ErrorStack> {
unsafe {
let pctx = Self::generate_ctx_with_ec_curve(boring_sys::NID_X9_62_prime256v1)?;
Self::generate_with_ctx(DhKeyType::EC(boring_sys::NID_X9_62_prime256v1), pctx)
}
}
pub fn generate_secp384r1() -> Result<Self, ErrorStack> {
unsafe {
let pctx = Self::generate_ctx_with_ec_curve(boring_sys::NID_secp384r1)?;
Self::generate_with_ctx(DhKeyType::EC(boring_sys::NID_secp384r1), pctx)
}
}
pub fn generate_secp521r1() -> Result<Self, ErrorStack> {
unsafe {
let pctx = Self::generate_ctx_with_ec_curve(boring_sys::NID_secp521r1)?;
Self::generate_with_ctx(DhKeyType::EC(boring_sys::NID_secp521r1), pctx)
}
}
unsafe fn generate_ctx_with_ec_curve(curve_nid: i32) -> Result<BoringEvpPkeyCtx, ErrorStack> {
boring_sys::init();
let pctx = BoringEvpPkeyCtx::from_ptr(cvt_p(boring_sys::EVP_PKEY_CTX_new_id(
boring_sys::EVP_PKEY_EC,
ptr::null_mut(),
))?);
// The following function is for generating parameters
cvt(boring_sys::EVP_PKEY_paramgen_init(pctx.as_ptr()))?;
// Set the curve
cvt(boring_sys::EVP_PKEY_CTX_set_ec_paramgen_curve_nid(
pctx.as_ptr(),
curve_nid,
))?;
// Used a named curve which has max compatiblity according man page
cvt(boring_sys::EVP_PKEY_CTX_set_ec_param_enc(
pctx.as_ptr(),
boring_sys::OPENSSL_EC_NAMED_CURVE,
))?;
// generate parameters
let mut pkey = MaybeUninit::<*mut boring_sys::EVP_PKEY>::new(ptr::null_mut()).assume_init();
cvt(boring_sys::EVP_PKEY_paramgen(pctx.as_ptr(), &mut pkey))?;
let pkey: boring::pkey::PKey<boring::pkey::Params> = boring::pkey::PKey::from_ptr(pkey);
Ok(BoringEvpPkeyCtx::from_ptr(
// ctx will take ownership of pkey
cvt_p(boring_sys::EVP_PKEY_CTX_new(pkey.as_ptr(), ptr::null_mut())).expect("failed"),
))
}
fn generate_ctx_from_nid(nid: i32) -> Result<BoringEvpPkeyCtx, ErrorStack> {
boring_sys::init();
Ok(unsafe {
BoringEvpPkeyCtx::from_ptr(cvt_p(boring_sys::EVP_PKEY_CTX_new_id(
nid,
ptr::null_mut(),
))?)
})
}
unsafe fn generate_with_ctx(
key_type: DhKeyType,
pctx: BoringEvpPkeyCtx,
) -> Result<Self, ErrorStack> {
let mut pkey = MaybeUninit::<*mut boring_sys::EVP_PKEY>::new(ptr::null_mut()).assume_init();
cvt(boring_sys::EVP_PKEY_keygen_init(pctx.as_ptr()))?;
cvt(boring_sys::EVP_PKEY_keygen(pctx.as_ptr(), &mut pkey))?;
let pkey: boring::pkey::PKey<boring::pkey::Private> = boring::pkey::PKey::from_ptr(pkey);
let dctx = BoringEvpPkeyCtx::from_ptr(
// dctx will take ownership of pkey, we can safely drop it
cvt_p(boring_sys::EVP_PKEY_CTX_new(pkey.as_ptr(), ptr::null_mut()))?,
);
let pub_bytes = Self::raw_public_key(pkey.as_ref())?;
Ok(Self {
dctx,
key_type,
pub_bytes,
})
}
fn raw_public_key(
pkey: &boring::pkey::PKeyRef<boring::pkey::Private>,
) -> Result<Vec<u8>, ErrorStack> {
let key_len = unsafe {
// figure out how many bytes we need for the key
cvt(boring_sys::i2d_PUBKEY(pkey.as_ptr(), ptr::null_mut()))? as usize
};
let mut spki = vec![0u8; key_len];
unsafe {
// write the key to spki
cvt(boring_sys::i2d_PUBKEY(
pkey.as_ptr(),
&mut spki.as_mut_ptr(),
))?;
}
// parse the key
let key = spki::SubjectPublicKeyInfoRef::from_der(spki.as_ref()).unwrap();
// return the raw public key as a new vec
Ok(Vec::from(key.subject_public_key.as_bytes().unwrap()))
}
pub fn diffie_hellman(&self, raw_public_key: &[u8]) -> Result<Vec<u8>, ErrorStack> {
match self.key_type {
DhKeyType::EC(nid) => self.diffie_hellman_ec(nid, raw_public_key),
DhKeyType::ED(nid) => self.diffie_hellman_ed(nid, raw_public_key),
_ => unimplemented!(),
}
}
fn diffie_hellman_ec(&self, nid: i32, raw_public_key: &[u8]) -> Result<Vec<u8>, ErrorStack> {
// this is only the key data not the algo identifier etc
let group = boring::ec::EcGroup::from_curve_name(boring::nid::Nid::from_raw(nid))?;
let mut bn_ctx = boring::bn::BigNumContext::new()?;
let point =
crate::verify::ec::ec_point(group.as_ref(), &mut bn_ctx, raw_public_key).unwrap();
let peerkey = crate::verify::ec::ec_public_key(group.as_ref(), point.as_ref()).unwrap();
self.diffie_hellman_common(peerkey.as_ptr())
}
fn diffie_hellman_ed(&self, nid: i32, raw_public_key: &[u8]) -> Result<Vec<u8>, ErrorStack> {
let peerkey: boring::pkey::PKey<boring::pkey::Public> = unsafe {
boring::pkey::PKey::from_ptr(cvt_p(boring_sys::EVP_PKEY_new_raw_public_key(
nid,
ptr::null_mut(),
raw_public_key.as_ptr(),
raw_public_key.len(),
))?)
};
self.diffie_hellman_common(peerkey.as_ptr())
}
fn diffie_hellman_common(
&self,
peerkey: *mut boring_sys::EVP_PKEY,
) -> Result<Vec<u8>, ErrorStack> {
unsafe {
// Initialize
cvt(boring_sys::EVP_PKEY_derive_init(self.dctx.as_ptr()))?;
// Provide the peer public key
cvt(boring_sys::EVP_PKEY_derive_set_peer(
self.dctx.as_ptr(),
peerkey,
))?;
}
// Determine buffer length for shared secret
let mut secret_len = unsafe {
let mut secret_len = 0;
cvt(boring_sys::EVP_PKEY_derive(
self.dctx.as_ptr(),
ptr::null_mut(),
&mut secret_len,
))?;
secret_len
};
let mut secret = vec![0u8; secret_len];
unsafe {
cvt(boring_sys::EVP_PKEY_derive(
self.dctx.as_ptr(),
secret.as_mut_ptr(),
&mut secret_len,
))?;
}
Ok(secret)
}
}
impl crypto::ActiveKeyExchange for BoringEvpKey {
fn complete(
self: Box<Self>,
peer_pub_key: &[u8],
) -> Result<crypto::SharedSecret, rustls::Error> {
let expected_len = self.pub_bytes.len();
if peer_pub_key.len() != expected_len {
return Err(rustls::Error::from(rustls::PeerMisbehaved::InvalidKeyShare));
}
Ok(crypto::SharedSecret::from(
self.diffie_hellman(peer_pub_key)
.map_err(|x| rustls::Error::General(x.to_string()))?
.as_ref(),
))
}
fn pub_key(&self) -> &[u8] {
self.pub_bytes.as_ref()
}
fn group(&self) -> rustls::NamedGroup {
match self.key_type {
DhKeyType::ED(boring_sys::NID_X25519) => rustls::NamedGroup::X25519,
DhKeyType::ED(boring_sys::NID_X448) => rustls::NamedGroup::X448,
DhKeyType::EC(boring_sys::NID_X9_62_prime256v1) => rustls::NamedGroup::secp256r1,
DhKeyType::EC(boring_sys::NID_secp384r1) => rustls::NamedGroup::secp384r1,
DhKeyType::EC(boring_sys::NID_secp521r1) => rustls::NamedGroup::secp521r1,
_ => unimplemented!(),
}
}
}

View file

@ -0,0 +1,78 @@
use std::sync::Arc;
use rustls::{
crypto::{CryptoProvider, GetRandomFailed, SupportedKxGroup},
SupportedCipherSuite,
};
use rustls_pki_types::PrivateKeyDer;
mod aead;
mod hash;
mod helper;
mod hkdf;
mod hmac;
mod kx;
mod sign;
mod tls13;
mod verify;
pub static PROVIDER: &'static dyn CryptoProvider = &Provider;
#[derive(Debug)]
struct Provider;
impl CryptoProvider for Provider {
fn fill_random(&self, bytes: &mut [u8]) -> Result<(), GetRandomFailed> {
boring::rand::rand_bytes(bytes).map_err(|_| GetRandomFailed)
}
fn default_cipher_suites(&self) -> &'static [SupportedCipherSuite] {
if boring::fips::enabled() {
ALL_FIPS_SUITES
} else {
ALL_CIPHER_SUITES
}
}
fn default_kx_groups(&self) -> &'static [&'static dyn SupportedKxGroup] {
if boring::fips::enabled() {
ALL_FIPS_KX_GROUPS
} else {
ALL_KX_GROUPS
}
}
fn load_private_key(
&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()))
}
fn signature_verification_algorithms(&self) -> rustls::WebPkiSupportedAlgorithms {
verify::ALL_ALGORITHMS
}
}
static ALL_FIPS_SUITES: &[SupportedCipherSuite] = &[
SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256),
SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA256),
];
static ALL_CIPHER_SUITES: &[SupportedCipherSuite] = &[
SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256),
SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA256),
SupportedCipherSuite::Tls13(&tls13::CHACHA20_POLY1305_SHA256),
];
pub const ALL_FIPS_KX_GROUPS: &[&dyn SupportedKxGroup] = &[];
pub const ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[
&kx::X25519 as _,
&kx::X448 as _,
&kx::Secp256r1 as _,
&kx::Secp384r1 as _,
&kx::Secp521r1 as _,
&kx::FfDHe2048 as _,
];

View file

@ -0,0 +1,194 @@
use std::sync::Arc;
use boring::{hash::MessageDigest, pkey::Id, rsa::Padding, sign::RsaPssSaltlen};
use rustls::{
sign::{Signer, SigningKey},
SignatureScheme,
};
use rustls_pki_types::PrivateKeyDer;
const ALL_RSA_SCHEMES: &[SignatureScheme] = &[
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PKCS1_SHA256,
];
const ALL_EC_SCHEMES: &[SignatureScheme] = &[
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::ECDSA_NISTP521_SHA512,
];
#[derive(Debug)]
pub struct BoringPrivateKey(
Arc<boring::pkey::PKey<boring::pkey::Private>>,
rustls::SignatureAlgorithm,
);
impl TryFrom<PrivateKeyDer<'static>> for BoringPrivateKey {
type Error = rustls::Error;
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(|_| ())
}
PrivateKeyDer::Pkcs1(der) => {
boring::pkey::PKey::private_key_from_der(der.secret_pkcs1_der()).map_err(|_| ())
}
_ => Err(()),
}
.map_err(|_| rustls::Error::General("failed loading private key".into()))?;
let sig = match pkey.id() {
Id::RSA => rustls::SignatureAlgorithm::RSA,
Id::EC => rustls::SignatureAlgorithm::ECDSA,
Id::ED25519 => rustls::SignatureAlgorithm::ED25519,
Id::ED448 => rustls::SignatureAlgorithm::ED448,
_ => return Err(rustls::Error::General("unsupported key format".into())),
};
Ok(Self(Arc::new(pkey), sig))
}
}
fn rsa_signer_from_params(
key: &boring::pkey::PKeyRef<boring::pkey::Private>,
digest: MessageDigest,
padding: Padding,
) -> boring::sign::Signer {
let mut signer = boring::sign::Signer::new(digest.clone(), key).expect("failed getting signer");
signer
.set_rsa_padding(padding)
.expect("failed setting padding");
if padding == Padding::PKCS1_PSS {
signer
.set_rsa_pss_saltlen(RsaPssSaltlen::DIGEST_LENGTH)
.expect("failed setting rsa_pss salt lengths");
signer
.set_rsa_mgf1_md(digest)
.expect("failed setting mgf1 digest");
}
signer
}
fn ec_signer_from_params(
key: &boring::pkey::PKeyRef<boring::pkey::Private>,
digest: MessageDigest,
) -> boring::sign::Signer {
let signer = boring::sign::Signer::new(digest, key).expect("failed getting signer");
signer
}
impl BoringPrivateKey {}
impl SigningKey for BoringPrivateKey {
fn choose_scheme(
&self,
offered: &[rustls::SignatureScheme],
) -> Option<Box<dyn rustls::sign::Signer>> {
match self.1 {
rustls::SignatureAlgorithm::RSA => ALL_RSA_SCHEMES
.iter()
.find(|scheme| offered.contains(scheme))
.map(|&scheme| Box::new(BoringSigner(self.0.clone(), scheme)) as _),
rustls::SignatureAlgorithm::ECDSA => ALL_EC_SCHEMES
.iter()
.find(|scheme| offered.contains(scheme))
.map(|&scheme| Box::new(BoringSigner(self.0.clone(), scheme)) as _),
rustls::SignatureAlgorithm::ED25519
if offered.contains(&rustls::SignatureScheme::ED25519) =>
{
Some(Box::new(BoringSigner(
self.0.clone(),
rustls::SignatureScheme::ED25519,
)))
}
rustls::SignatureAlgorithm::ED448
if offered.contains(&rustls::SignatureScheme::ED448) =>
{
Some(Box::new(BoringSigner(
self.0.clone(),
rustls::SignatureScheme::ED448,
)))
}
_ => None,
}
}
fn algorithm(&self) -> rustls::SignatureAlgorithm {
self.1
}
}
#[derive(Debug)]
pub struct BoringSigner(
Arc<boring::pkey::PKey<boring::pkey::Private>>,
rustls::SignatureScheme,
);
impl BoringSigner {
fn get_signer(&self) -> boring::sign::Signer {
match self.1 {
SignatureScheme::RSA_PKCS1_SHA256 => {
rsa_signer_from_params(self.0.as_ref(), MessageDigest::sha256(), Padding::PKCS1)
}
SignatureScheme::RSA_PKCS1_SHA384 => {
rsa_signer_from_params(self.0.as_ref(), MessageDigest::sha384(), Padding::PKCS1)
}
SignatureScheme::RSA_PKCS1_SHA512 => {
rsa_signer_from_params(self.0.as_ref(), MessageDigest::sha512(), Padding::PKCS1)
}
SignatureScheme::RSA_PSS_SHA256 => {
rsa_signer_from_params(self.0.as_ref(), MessageDigest::sha256(), Padding::PKCS1_PSS)
}
SignatureScheme::RSA_PSS_SHA384 => {
rsa_signer_from_params(self.0.as_ref(), MessageDigest::sha384(), Padding::PKCS1_PSS)
}
SignatureScheme::RSA_PSS_SHA512 => {
rsa_signer_from_params(self.0.as_ref(), MessageDigest::sha512(), Padding::PKCS1_PSS)
}
SignatureScheme::ECDSA_NISTP256_SHA256 => {
ec_signer_from_params(self.0.as_ref(), MessageDigest::sha256())
}
SignatureScheme::ECDSA_NISTP384_SHA384 => {
ec_signer_from_params(self.0.as_ref(), MessageDigest::sha384())
}
SignatureScheme::ECDSA_NISTP521_SHA512 => {
ec_signer_from_params(self.0.as_ref(), MessageDigest::sha512())
}
SignatureScheme::ED25519 => boring::sign::Signer::new_without_digest(self.0.as_ref())
.expect("failed getting signer"),
SignatureScheme::ED448 => boring::sign::Signer::new_without_digest(self.0.as_ref())
.expect("failed getting signer"),
_ => unimplemented!(),
}
}
}
impl Signer for BoringSigner {
fn sign(&self, message: &[u8]) -> Result<Vec<u8>, rustls::Error> {
let signer = self.get_signer();
let mut msg_with_sig =
Vec::<u8>::with_capacity(message.len() + boring_sys::EVP_MAX_MD_SIZE as usize);
msg_with_sig.extend_from_slice(message);
msg_with_sig.extend_from_slice(&[0u8; boring_sys::EVP_MAX_MD_SIZE as usize]);
let toatl_len = signer
.sign(&mut msg_with_sig[..])
.map_err(|_| rustls::Error::General("failed signing".into()))?;
msg_with_sig.truncate(toatl_len);
Ok(msg_with_sig)
}
fn scheme(&self) -> rustls::SignatureScheme {
self.1
}
}

View file

@ -0,0 +1,31 @@
use rustls::Tls13CipherSuite;
use crate::{aead, hash, hkdf};
pub static AES_128_GCM_SHA256: Tls13CipherSuite = Tls13CipherSuite {
common: rustls::CipherSuiteCommon {
suite: rustls::CipherSuite::TLS13_AES_128_GCM_SHA256,
hash_provider: hash::SHA256,
},
hkdf_provider: &hkdf::Hkdf::<hkdf::Sha256>::DEFAULT,
aead_alg: &aead::Aead::<aead::aes::Aes128>::DEFAULT,
};
pub static AES_256_GCM_SHA256: Tls13CipherSuite = Tls13CipherSuite {
common: rustls::CipherSuiteCommon {
suite: rustls::CipherSuite::TLS13_AES_128_GCM_SHA256,
hash_provider: hash::SHA256,
},
hkdf_provider: &hkdf::Hkdf::<hkdf::Sha256>::DEFAULT,
aead_alg: &aead::Aead::<aead::aes::Aes256>::DEFAULT,
};
pub static CHACHA20_POLY1305_SHA256: Tls13CipherSuite = Tls13CipherSuite {
common: rustls::CipherSuiteCommon {
suite: rustls::CipherSuite::TLS13_CHACHA20_POLY1305_SHA256,
hash_provider: hash::SHA256,
},
hkdf_provider: &hkdf::Hkdf::<hkdf::Sha256>::DEFAULT,
aead_alg: &aead::Aead::<aead::chacha20::ChaCha20Poly1305>::DEFAULT,
};

View file

@ -0,0 +1,61 @@
use rustls::{SignatureScheme, WebPkiSupportedAlgorithms};
pub(crate) mod ec;
mod ed;
pub(crate) mod rsa;
pub static ALL_ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms {
all: &[
&rsa::BoringRsaVerifier::RSA_PKCS1_SHA256,
&rsa::BoringRsaVerifier::RSA_PKCS1_SHA384,
&rsa::BoringRsaVerifier::RSA_PKCS1_SHA512,
&rsa::BoringRsaVerifier::RSA_PSS_SHA256,
&rsa::BoringRsaVerifier::RSA_PSS_SHA384,
&rsa::BoringRsaVerifier::RSA_PSS_SHA512,
&ec::BoringEcVerifier::ECDSA_NISTP256_SHA256,
&ec::BoringEcVerifier::ECDSA_NISTP384_SHA384,
&ec::BoringEcVerifier::ECDSA_NISTP521_SHA512,
&ed::BoringEdVerifier::ED25519,
&ed::BoringEdVerifier::ED448,
],
mapping: &[
(
SignatureScheme::RSA_PKCS1_SHA256,
&[&rsa::BoringRsaVerifier::RSA_PKCS1_SHA256],
),
(
SignatureScheme::RSA_PKCS1_SHA384,
&[&rsa::BoringRsaVerifier::RSA_PKCS1_SHA384],
),
(
SignatureScheme::RSA_PKCS1_SHA512,
&[&rsa::BoringRsaVerifier::RSA_PKCS1_SHA512],
),
(
SignatureScheme::RSA_PSS_SHA256,
&[&rsa::BoringRsaVerifier::RSA_PSS_SHA256],
),
(
SignatureScheme::RSA_PSS_SHA384,
&[&rsa::BoringRsaVerifier::RSA_PSS_SHA384],
),
(
SignatureScheme::RSA_PSS_SHA512,
&[&rsa::BoringRsaVerifier::RSA_PSS_SHA512],
),
(
SignatureScheme::ECDSA_NISTP256_SHA256,
&[&ec::BoringEcVerifier::ECDSA_NISTP256_SHA256],
),
(
SignatureScheme::ECDSA_NISTP384_SHA384,
&[&ec::BoringEcVerifier::ECDSA_NISTP384_SHA384],
),
(
SignatureScheme::ECDSA_NISTP521_SHA512,
&[&ec::BoringEcVerifier::ECDSA_NISTP521_SHA512],
),
(SignatureScheme::ED25519, &[&ed::BoringEdVerifier::ED25519]),
(SignatureScheme::ED448, &[&ed::BoringEdVerifier::ED448]),
],
};

View file

@ -0,0 +1,115 @@
use boring::hash::MessageDigest;
use rustls::SignatureScheme;
use rustls_pki_types::{InvalidSignature, SignatureVerificationAlgorithm};
pub struct BoringEcVerifier(SignatureScheme);
impl BoringEcVerifier {
pub const ECDSA_NISTP256_SHA256: Self = Self(SignatureScheme::ECDSA_NISTP256_SHA256);
pub const ECDSA_NISTP384_SHA384: Self = Self(SignatureScheme::ECDSA_NISTP384_SHA384);
pub const ECDSA_NISTP521_SHA512: Self = Self(SignatureScheme::ECDSA_NISTP521_SHA512);
}
impl SignatureVerificationAlgorithm for BoringEcVerifier {
fn verify_signature(
&self,
public_key: &[u8],
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)?;
let public_key = ec_public_key(group.as_ref(), ec_point.as_ref())?;
let mut verifier = match self.0 {
SignatureScheme::ECDSA_NISTP256_SHA256 => {
ec_verifier_from_params(public_key.as_ref(), MessageDigest::sha256())
}
SignatureScheme::ECDSA_NISTP384_SHA384 => {
ec_verifier_from_params(public_key.as_ref(), MessageDigest::sha384())
}
SignatureScheme::ECDSA_NISTP521_SHA512 => {
ec_verifier_from_params(public_key.as_ref(), MessageDigest::sha512())
}
_ => unimplemented!(),
};
verifier.verify_oneshot(signature, message).map_or_else(
|_| Err(InvalidSignature),
|res| if res { Ok(()) } else { Err(InvalidSignature) },
)
}
fn public_key_alg_id(&self) -> rustls_pki_types::AlgorithmIdentifier {
match self.0 {
SignatureScheme::ECDSA_NISTP256_SHA256 => webpki::alg_id::ECDSA_P256,
SignatureScheme::ECDSA_NISTP384_SHA384 => webpki::alg_id::ECDSA_P384,
SignatureScheme::ECDSA_NISTP521_SHA512 => {
// See rfc5480 appendix-A (secp521r1): 1.3.132.0.35
rustls_pki_types::AlgorithmIdentifier::from_slice(&[
0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81,
0x04, 0x00, 0x23,
])
}
_ => unimplemented!(),
}
}
fn signature_alg_id(&self) -> rustls_pki_types::AlgorithmIdentifier {
match self.0 {
SignatureScheme::ECDSA_NISTP256_SHA256 => webpki::alg_id::ECDSA_SHA256,
SignatureScheme::ECDSA_NISTP384_SHA384 => webpki::alg_id::ECDSA_SHA384,
SignatureScheme::ECDSA_NISTP521_SHA512 => {
// See rfc5480 appendix-A (ecdsa-with-SHA512): 1.2.840.10045.4.3.4
rustls_pki_types::AlgorithmIdentifier::from_slice(&[
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04,
])
}
_ => unimplemented!(),
}
}
}
fn ec_verifier_from_params(
key: &boring::pkey::PKeyRef<boring::pkey::Public>,
digest: MessageDigest,
) -> boring::sign::Verifier {
let verifier =
boring::sign::Verifier::new(digest.clone(), key).expect("failed getting verifier");
verifier
}
fn group_for_scheme(scheme: SignatureScheme) -> boring::ec::EcGroup {
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")
}
fn setup_ec_key(scheme: SignatureScheme) -> (boring::ec::EcGroup, boring::bn::BigNumContext) {
(
group_for_scheme(scheme),
boring::bn::BigNumContext::new().unwrap(),
)
}
pub(crate) fn ec_point(
group: &boring::ec::EcGroupRef,
bignum_ctx: &mut boring::bn::BigNumContextRef,
spki_spk: &[u8],
) -> Result<boring::ec::EcPoint, InvalidSignature> {
boring::ec::EcPoint::from_bytes(group, spki_spk, bignum_ctx).map_err(|_| InvalidSignature)
}
pub(crate) fn ec_public_key(
group: &boring::ec::EcGroupRef,
ec_point: &boring::ec::EcPointRef,
) -> Result<boring::pkey::PKey<boring::pkey::Public>, InvalidSignature> {
boring::pkey::PKey::from_ec_key(
boring::ec::EcKey::from_public_key(group, ec_point).map_err(|_| InvalidSignature)?,
)
.map_err(|_| InvalidSignature)
}

View file

@ -0,0 +1,78 @@
use std::ptr;
use foreign_types::ForeignType;
use rustls::SignatureScheme;
use rustls_pki_types::{InvalidSignature, SignatureVerificationAlgorithm};
use crate::helper::cvt_p;
pub struct BoringEdVerifier(SignatureScheme);
impl BoringEdVerifier {
pub const ED25519: Self = Self(SignatureScheme::ED25519);
pub const ED448: Self = Self(SignatureScheme::ED448);
}
impl SignatureVerificationAlgorithm for BoringEdVerifier {
fn verify_signature(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), rustls_pki_types::InvalidSignature> {
let public_key = ed_public_key(public_key, self.0)?;
let mut verifier = ed_verifier_from_params(public_key.as_ref());
verifier.verify_oneshot(signature, message).map_or_else(
|_| Err(InvalidSignature),
|res| if res { Ok(()) } else { Err(InvalidSignature) },
)
}
fn public_key_alg_id(&self) -> rustls_pki_types::AlgorithmIdentifier {
// for ed those are the same
self.signature_alg_id()
}
fn signature_alg_id(&self) -> rustls_pki_types::AlgorithmIdentifier {
match self.0 {
SignatureScheme::ED25519 => webpki::alg_id::ED25519,
SignatureScheme::ED448 => {
// rfc8410#section-3: 1.3.101.113: -> DER: 06 03 2B 65 71
rustls_pki_types::AlgorithmIdentifier::from_slice(&[0x06, 0x03, 0x2B, 0x65, 0x71])
}
_ => unimplemented!(),
}
}
}
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
}
fn ed_public_key(
spki_spk: &[u8],
scheme: SignatureScheme,
) -> Result<boring::pkey::PKey<boring::pkey::Public>, InvalidSignature> {
let typ = match scheme {
SignatureScheme::ED25519 => boring_sys::EVP_PKEY_ED25519,
SignatureScheme::ED448 => boring_sys::EVP_PKEY_ED448,
_ => unimplemented!(),
};
Ok(unsafe {
let pkey = cvt_p(boring_sys::EVP_PKEY_new_raw_public_key(
typ,
ptr::null_mut(),
spki_spk.as_ptr(),
spki_spk.len(),
))
.map_err(|_| InvalidSignature)?;
boring::pkey::PKey::from_ptr(pkey)
})
}

View file

@ -0,0 +1,123 @@
use boring::{hash::MessageDigest, rsa::Padding, sign::RsaPssSaltlen};
use rustls::SignatureScheme;
use rustls_pki_types::{InvalidSignature, SignatureVerificationAlgorithm};
use spki::der::Reader;
pub struct BoringRsaVerifier(SignatureScheme);
impl BoringRsaVerifier {
pub const RSA_PKCS1_SHA256: Self = Self(SignatureScheme::RSA_PKCS1_SHA256);
pub const RSA_PKCS1_SHA384: Self = Self(SignatureScheme::RSA_PKCS1_SHA384);
pub const RSA_PKCS1_SHA512: Self = Self(SignatureScheme::RSA_PKCS1_SHA512);
pub const RSA_PSS_SHA256: Self = Self(SignatureScheme::RSA_PSS_SHA256);
pub const RSA_PSS_SHA384: Self = Self(SignatureScheme::RSA_PSS_SHA384);
pub const RSA_PSS_SHA512: Self = Self(SignatureScheme::RSA_PSS_SHA512);
}
impl SignatureVerificationAlgorithm for BoringRsaVerifier {
fn verify_signature(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), rustls_pki_types::InvalidSignature> {
let public_key = decode_spki_spk(public_key)?;
let mut verifier = match self.0 {
SignatureScheme::RSA_PKCS1_SHA256 => rsa_verifier_from_params(
public_key.as_ref(),
MessageDigest::sha256(),
Padding::PKCS1,
),
SignatureScheme::RSA_PKCS1_SHA384 => rsa_verifier_from_params(
public_key.as_ref(),
MessageDigest::sha384(),
Padding::PKCS1,
),
SignatureScheme::RSA_PKCS1_SHA512 => rsa_verifier_from_params(
public_key.as_ref(),
MessageDigest::sha512(),
Padding::PKCS1,
),
SignatureScheme::RSA_PSS_SHA256 => rsa_verifier_from_params(
public_key.as_ref(),
MessageDigest::sha256(),
Padding::PKCS1_PSS,
),
SignatureScheme::RSA_PSS_SHA384 => rsa_verifier_from_params(
public_key.as_ref(),
MessageDigest::sha384(),
Padding::PKCS1_PSS,
),
SignatureScheme::RSA_PSS_SHA512 => rsa_verifier_from_params(
public_key.as_ref(),
MessageDigest::sha512(),
Padding::PKCS1_PSS,
),
_ => unimplemented!(),
};
verifier.verify_oneshot(signature, message).map_or_else(
|_| Err(InvalidSignature),
|res| if res { Ok(()) } else { Err(InvalidSignature) },
)
}
fn public_key_alg_id(&self) -> rustls_pki_types::AlgorithmIdentifier {
webpki::alg_id::RSA_ENCRYPTION
}
fn signature_alg_id(&self) -> rustls_pki_types::AlgorithmIdentifier {
match self.0 {
SignatureScheme::RSA_PKCS1_SHA256 => webpki::alg_id::RSA_PKCS1_SHA256,
SignatureScheme::RSA_PKCS1_SHA384 => webpki::alg_id::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PKCS1_SHA512 => webpki::alg_id::RSA_PKCS1_SHA512,
SignatureScheme::RSA_PSS_SHA256 => webpki::alg_id::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384 => webpki::alg_id::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512 => webpki::alg_id::RSA_PSS_SHA512,
_ => unimplemented!(),
}
}
}
fn rsa_verifier_from_params(
key: &boring::pkey::PKeyRef<boring::pkey::Public>,
digest: MessageDigest,
padding: Padding,
) -> boring::sign::Verifier {
let mut verifier =
boring::sign::Verifier::new(digest.clone(), key).expect("failed getting verifier");
verifier
.set_rsa_padding(padding)
.expect("failed setting padding");
if padding == Padding::PKCS1_PSS {
verifier
.set_rsa_pss_saltlen(RsaPssSaltlen::DIGEST_LENGTH)
.expect("failed setting rsa_pss salt lengths");
verifier
.set_rsa_mgf1_md(digest)
.expect("failed setting mgf1 digest");
}
verifier
}
pub(crate) fn decode_spki_spk(
spki_spk: &[u8],
) -> Result<boring::pkey::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 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)?;
boring::pkey::PKey::from_rsa(
boring::rsa::Rsa::from_public_components(n, e).map_err(|_| InvalidSignature)?,
)
.map_err(|_| InvalidSignature)
}

21
examples/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "boring-rustls-provider-examples"
version = "0.0.1"
edition = "2021"
license = "MIT"
description = "Boring Rustls provider example code and tests."
publish = false
[dependencies]
docopt = "~1.1"
env_logger = "0.10"
log = { version = "0.4.4" }
mio = { version = "0.8", features = ["net", "os-poll"] }
pki-types = { package = "rustls-pki-types", version = "0.2" }
rcgen = { version = "0.11.3", features = ["pem"], default-features = false }
rustls = { workspace = true, features = [ "logging" ]}
boring-rustls-provider = { path = "../boring-rustls-provider", features = ["logging"] }
rustls-pemfile = "=2.0.0-alpha.1"
serde = "1.0"
serde_derive = "1.0"
webpki-roots = "=0.26.0-alpha.1"

View file

@ -0,0 +1,43 @@
use std::io::{stdout, Read, Write};
use std::net::TcpStream;
use std::sync::Arc;
use boring_rustls_provider::PROVIDER;
fn main() {
env_logger::init();
let mut root_store = rustls::RootCertStore::empty();
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let config = rustls::ClientConfig::builder_with_provider(PROVIDER)
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth();
let server_name = "www.rust-lang.org".try_into().unwrap();
let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap();
let mut sock = TcpStream::connect("www.rust-lang.org:443").unwrap();
let mut tls = rustls::Stream::new(&mut conn, &mut sock);
tls.write_all(
concat!(
"GET / HTTP/1.1\r\n",
"Host: www.rust-lang.org\r\n",
"Connection: close\r\n",
"Accept-Encoding: identity\r\n",
"\r\n"
)
.as_bytes(),
)
.unwrap();
let ciphersuite = tls.conn.negotiated_cipher_suite().unwrap();
writeln!(
&mut std::io::stderr(),
"Current ciphersuite: {:?}",
ciphersuite.suite()
)
.unwrap();
let mut plaintext = Vec::new();
tls.read_to_end(&mut plaintext).unwrap();
stdout().write_all(&plaintext).unwrap();
}