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:
parent
db339f7161
commit
5c45b9426b
24 changed files with 2355 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.DS_Store
|
||||
/target
|
||||
/.vscode
|
||||
/Cargo.lock
|
||||
16
Cargo.toml
Normal file
16
Cargo.toml
Normal 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
55
Readme.md
Normal 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
|
||||
34
boring-rustls-provider/Cargo.toml
Normal file
34
boring-rustls-provider/Cargo.toml
Normal 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"
|
||||
|
||||
|
||||
|
||||
177
boring-rustls-provider/src/aead.rs
Normal file
177
boring-rustls-provider/src/aead.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
187
boring-rustls-provider/src/aead/aead2.rs
Normal file
187
boring-rustls-provider/src/aead/aead2.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
90
boring-rustls-provider/src/aead/aes.rs
Normal file
90
boring-rustls-provider/src/aead/aes.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
56
boring-rustls-provider/src/aead/chacha20.rs
Normal file
56
boring-rustls-provider/src/aead/chacha20.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
70
boring-rustls-provider/src/hash.rs
Normal file
70
boring-rustls-provider/src/hash.rs
Normal 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")
|
||||
);
|
||||
}
|
||||
}
|
||||
42
boring-rustls-provider/src/helper.rs
Normal file
42
boring-rustls-provider/src/helper.rs
Normal 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
|
||||
}
|
||||
184
boring-rustls-provider/src/hkdf.rs
Normal file
184
boring-rustls-provider/src/hkdf.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
144
boring-rustls-provider/src/hmac.rs
Normal file
144
boring-rustls-provider/src/hmac.rs
Normal 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")
|
||||
);
|
||||
}
|
||||
}
|
||||
139
boring-rustls-provider/src/kx.rs
Normal file
139
boring-rustls-provider/src/kx.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
98
boring-rustls-provider/src/kx/dh.rs
Normal file
98
boring-rustls-provider/src/kx/dh.rs
Normal 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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
315
boring-rustls-provider/src/kx/evp.rs
Normal file
315
boring-rustls-provider/src/kx/evp.rs
Normal 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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
78
boring-rustls-provider/src/lib.rs
Normal file
78
boring-rustls-provider/src/lib.rs
Normal 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 _,
|
||||
];
|
||||
194
boring-rustls-provider/src/sign.rs
Normal file
194
boring-rustls-provider/src/sign.rs
Normal 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
|
||||
}
|
||||
}
|
||||
31
boring-rustls-provider/src/tls13.rs
Normal file
31
boring-rustls-provider/src/tls13.rs
Normal 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,
|
||||
};
|
||||
61
boring-rustls-provider/src/verify.rs
Normal file
61
boring-rustls-provider/src/verify.rs
Normal 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]),
|
||||
],
|
||||
};
|
||||
115
boring-rustls-provider/src/verify/ec.rs
Normal file
115
boring-rustls-provider/src/verify/ec.rs
Normal 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)
|
||||
}
|
||||
78
boring-rustls-provider/src/verify/ed.rs
Normal file
78
boring-rustls-provider/src/verify/ed.rs
Normal 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)
|
||||
})
|
||||
}
|
||||
123
boring-rustls-provider/src/verify/rsa.rs
Normal file
123
boring-rustls-provider/src/verify/rsa.rs
Normal 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
21
examples/Cargo.toml
Normal 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"
|
||||
43
examples/src/bin/client.rs
Normal file
43
examples/src/bin/client.rs
Normal 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();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue