Fix TLS 1.2 crypto and add end-to-end tests
This commit is contained in:
parent
b597423237
commit
624eda8168
4 changed files with 163 additions and 25 deletions
|
|
@ -17,7 +17,7 @@ AES_256_GCM_SHA384
|
||||||
CHACHA20_POLY1305_SHA256
|
CHACHA20_POLY1305_SHA256
|
||||||
```
|
```
|
||||||
|
|
||||||
TLS 1.2 prepared for (doesn't work yet):
|
TLS 1.2 (only ECDSA is tested):
|
||||||
```
|
```
|
||||||
ECDHE_ECDSA_AES128_GCM_SHA256
|
ECDHE_ECDSA_AES128_GCM_SHA256
|
||||||
ECDHE_RSA_AES128_GCM_SHA256
|
ECDHE_RSA_AES128_GCM_SHA256
|
||||||
|
|
|
||||||
|
|
@ -111,23 +111,38 @@ where
|
||||||
msg: cipher::BorrowedPlainMessage,
|
msg: cipher::BorrowedPlainMessage,
|
||||||
seq: u64,
|
seq: u64,
|
||||||
) -> Result<cipher::OpaqueMessage, rustls::Error> {
|
) -> 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 nonce = cipher::Nonce::new(&self.iv, seq);
|
||||||
|
|
||||||
match self.tls_version {
|
match self.tls_version {
|
||||||
#[cfg(feature = "tls12")]
|
#[cfg(feature = "tls12")]
|
||||||
ProtocolVersion::TLSv1_2 => {
|
ProtocolVersion::TLSv1_2 => {
|
||||||
let aad = cipher::make_tls12_aad(seq, msg.typ, msg.version, total_len);
|
let fixed_iv_len = <T as BoringCipher>::fixed_iv_len();
|
||||||
self.encrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, &mut payload)
|
let explicit_nonce_len = <T as BoringCipher>::explicit_nonce_len();
|
||||||
|
|
||||||
|
let total_len =
|
||||||
|
msg.payload.len() + self.crypter.max_overhead() + explicit_nonce_len;
|
||||||
|
|
||||||
|
let mut full_payload = Vec::with_capacity(total_len);
|
||||||
|
full_payload.extend_from_slice(&nonce.0.as_ref()[fixed_iv_len..]);
|
||||||
|
full_payload.extend_from_slice(msg.payload);
|
||||||
|
full_payload.extend_from_slice(&vec![0u8; self.crypter.max_overhead()]);
|
||||||
|
|
||||||
|
let (_, payload) = full_payload.split_at_mut(explicit_nonce_len);
|
||||||
|
let (payload, tag) = payload.split_at_mut(msg.payload.len());
|
||||||
|
let aad = cipher::make_tls12_aad(seq, msg.typ, msg.version, msg.payload.len());
|
||||||
|
self.crypter
|
||||||
|
.seal_in_place(&nonce.0, &aad, payload, tag)
|
||||||
.map_err(|_| rustls::Error::EncryptError)
|
.map_err(|_| rustls::Error::EncryptError)
|
||||||
.map(|_| cipher::OpaqueMessage::new(msg.typ, msg.version, payload))
|
.map(|_| cipher::OpaqueMessage::new(msg.typ, msg.version, full_payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
ProtocolVersion::TLSv1_3 => {
|
ProtocolVersion::TLSv1_3 => {
|
||||||
|
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 aad = cipher::make_tls13_aad(total_len);
|
let aad = cipher::make_tls13_aad(total_len);
|
||||||
self.encrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, &mut payload)
|
self.encrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, &mut payload)
|
||||||
.map_err(|_| rustls::Error::EncryptError)
|
.map_err(|_| rustls::Error::EncryptError)
|
||||||
|
|
@ -159,18 +174,56 @@ where
|
||||||
seq: u64,
|
seq: u64,
|
||||||
) -> Result<cipher::PlainMessage, rustls::Error> {
|
) -> Result<cipher::PlainMessage, rustls::Error> {
|
||||||
// construct nonce
|
// construct nonce
|
||||||
let nonce = cipher::Nonce::new(&self.iv, seq);
|
|
||||||
|
|
||||||
// construct the aad and decrypt
|
// construct the aad and decrypt
|
||||||
match self.tls_version {
|
match self.tls_version {
|
||||||
#[cfg(feature = "tls12")]
|
#[cfg(feature = "tls12")]
|
||||||
ProtocolVersion::TLSv1_2 => {
|
ProtocolVersion::TLSv1_2 => {
|
||||||
let aad = make_tls12_aad(seq, m.typ, m.version, m.payload().len());
|
let explicit_nonce_len = <T as BoringCipher>::explicit_nonce_len();
|
||||||
self.decrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, m.payload_mut())
|
|
||||||
|
// payload is: [nonce] | [ciphertext] | [auth tag]
|
||||||
|
let actual_payload_length =
|
||||||
|
m.payload().len() - self.crypter.max_overhead() - explicit_nonce_len;
|
||||||
|
|
||||||
|
let aad = make_tls12_aad(seq, m.typ, m.version, actual_payload_length);
|
||||||
|
|
||||||
|
let payload = m.payload_mut();
|
||||||
|
|
||||||
|
// get the nonce
|
||||||
|
let (explicit_nonce, payload) = payload.split_at_mut(explicit_nonce_len);
|
||||||
|
|
||||||
|
let nonce = {
|
||||||
|
let fixed_iv_len = <T as BoringCipher>::fixed_iv_len();
|
||||||
|
|
||||||
|
assert_eq!(explicit_nonce_len + fixed_iv_len, 12);
|
||||||
|
|
||||||
|
// grab the IV by constructing a nonce, this is just an xor
|
||||||
|
let iv = cipher::Nonce::new(&self.iv, 0).0;
|
||||||
|
let mut nonce = [0u8; 12];
|
||||||
|
nonce[..fixed_iv_len].copy_from_slice(&iv[..fixed_iv_len]);
|
||||||
|
nonce[fixed_iv_len..].copy_from_slice(explicit_nonce);
|
||||||
|
nonce
|
||||||
|
};
|
||||||
|
|
||||||
|
// split off the authentication tag
|
||||||
|
let (payload, tag) =
|
||||||
|
payload.split_at_mut(payload.len() - self.crypter.max_overhead());
|
||||||
|
|
||||||
|
self.crypter
|
||||||
|
.open_in_place(&nonce, &aad, payload, tag)
|
||||||
.map_err(|_| rustls::Error::DecryptError)
|
.map_err(|_| rustls::Error::DecryptError)
|
||||||
.map(|_| m.into_plain_message())
|
.map(|_| {
|
||||||
|
// rotate the nonce to the end
|
||||||
|
m.payload_mut().rotate_left(explicit_nonce_len);
|
||||||
|
|
||||||
|
// truncate buffer to the actual payload
|
||||||
|
m.payload_mut().truncate(actual_payload_length);
|
||||||
|
|
||||||
|
m.into_plain_message()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
ProtocolVersion::TLSv1_3 => {
|
ProtocolVersion::TLSv1_3 => {
|
||||||
|
let nonce = cipher::Nonce::new(&self.iv, seq);
|
||||||
let aad = make_tls13_aad(m.payload().len());
|
let aad = make_tls13_aad(m.payload().len());
|
||||||
self.decrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, m.payload_mut())
|
self.decrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, m.payload_mut())
|
||||||
.map_err(|_| rustls::Error::DecryptError)
|
.map_err(|_| rustls::Error::DecryptError)
|
||||||
|
|
@ -221,18 +274,29 @@ impl<T: BoringAead + 'static> cipher::Tls12AeadAlgorithm for Aead<T> {
|
||||||
&self,
|
&self,
|
||||||
key: cipher::AeadKey,
|
key: cipher::AeadKey,
|
||||||
iv: &[u8],
|
iv: &[u8],
|
||||||
_extra: &[u8],
|
extra: &[u8],
|
||||||
) -> Box<dyn cipher::MessageEncrypter> {
|
) -> Box<dyn cipher::MessageEncrypter> {
|
||||||
|
let mut full_iv = Vec::with_capacity(iv.len() + extra.len());
|
||||||
|
full_iv.extend_from_slice(iv);
|
||||||
|
full_iv.extend_from_slice(extra);
|
||||||
Box::new(
|
Box::new(
|
||||||
BoringAeadCrypter::<T>::new(Iv::copy(iv), key.as_ref(), ProtocolVersion::TLSv1_2)
|
BoringAeadCrypter::<T>::new(Iv::copy(&full_iv), key.as_ref(), ProtocolVersion::TLSv1_2)
|
||||||
.expect("failed to create AEAD crypter"),
|
.expect("failed to create AEAD crypter"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypter(&self, key: cipher::AeadKey, iv: &[u8]) -> Box<dyn cipher::MessageDecrypter> {
|
fn decrypter(&self, key: cipher::AeadKey, iv: &[u8]) -> Box<dyn cipher::MessageDecrypter> {
|
||||||
|
let mut pseudo_iv =
|
||||||
|
Vec::with_capacity(iv.len() + <T as BoringCipher>::explicit_nonce_len());
|
||||||
|
pseudo_iv.extend_from_slice(iv);
|
||||||
|
pseudo_iv.extend_from_slice(&vec![0u8; <T as BoringCipher>::explicit_nonce_len()]);
|
||||||
Box::new(
|
Box::new(
|
||||||
BoringAeadCrypter::<T>::new(Iv::copy(iv), key.as_ref(), ProtocolVersion::TLSv1_2)
|
BoringAeadCrypter::<T>::new(
|
||||||
.expect("failed to create AEAD crypter"),
|
Iv::copy(&pseudo_iv),
|
||||||
|
key.as_ref(),
|
||||||
|
ProtocolVersion::TLSv1_2,
|
||||||
|
)
|
||||||
|
.expect("failed to create AEAD crypter"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,9 +305,8 @@ impl<T: BoringAead + 'static> cipher::Tls12AeadAlgorithm for Aead<T> {
|
||||||
enc_key_len: <T as BoringCipher>::key_size(),
|
enc_key_len: <T as BoringCipher>::key_size(),
|
||||||
// there is no benefit of splitting these up here, we'd need to stich them anyways
|
// there is no benefit of splitting these up here, we'd need to stich them anyways
|
||||||
// by only setting fixed_iv_len we get the full lengths
|
// by only setting fixed_iv_len we get the full lengths
|
||||||
fixed_iv_len: <T as BoringCipher>::fixed_iv_len()
|
fixed_iv_len: <T as BoringCipher>::fixed_iv_len(),
|
||||||
+ <T as BoringCipher>::explicit_nonce_len(),
|
explicit_nonce_len: <T as BoringCipher>::explicit_nonce_len(),
|
||||||
explicit_nonce_len: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,8 +314,20 @@ impl<T: BoringAead + 'static> cipher::Tls12AeadAlgorithm for Aead<T> {
|
||||||
&self,
|
&self,
|
||||||
key: cipher::AeadKey,
|
key: cipher::AeadKey,
|
||||||
iv: &[u8],
|
iv: &[u8],
|
||||||
_explicit: &[u8],
|
explicit: &[u8],
|
||||||
) -> Result<ConnectionTrafficSecrets, cipher::UnsupportedOperationError> {
|
) -> Result<ConnectionTrafficSecrets, cipher::UnsupportedOperationError> {
|
||||||
Ok(<T as BoringCipher>::extract_keys(key, Iv::copy(iv)))
|
let nonce = {
|
||||||
|
let fixed_iv_len = <T as BoringCipher>::fixed_iv_len();
|
||||||
|
let explicit_nonce_len = <T as BoringCipher>::explicit_nonce_len();
|
||||||
|
assert_eq!(explicit_nonce_len + fixed_iv_len, 12);
|
||||||
|
|
||||||
|
// grab the IV by constructing a nonce, this is just an xor
|
||||||
|
|
||||||
|
let mut nonce = [0u8; 12];
|
||||||
|
nonce[..fixed_iv_len].copy_from_slice(&iv[..fixed_iv_len]);
|
||||||
|
nonce[fixed_iv_len..].copy_from_slice(explicit);
|
||||||
|
nonce
|
||||||
|
};
|
||||||
|
Ok(<T as BoringCipher>::extract_keys(key, Iv::copy(&nonce)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,24 @@ mod tests {
|
||||||
use super::SHA256;
|
use super::SHA256;
|
||||||
use hex_literal::hex;
|
use hex_literal::hex;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_context() {
|
||||||
|
let mut hash = SHA256.start();
|
||||||
|
hash.update(b"ABCDE");
|
||||||
|
let abcde = hash.fork_finish();
|
||||||
|
hash.update(b"FGHIJ");
|
||||||
|
let abcdefghij = hash.finish();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
abcde.as_ref(),
|
||||||
|
hex!("f0393febe8baaa55e32f7be2a7cc180bf34e52137d99e056c817a9c07b8f239a")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
abcdefghij.as_ref(),
|
||||||
|
hex!("261305762671a58cae5b74990bcfc236c2336fb04a0fbac626166d9491d2884c")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sha256() {
|
fn test_sha256() {
|
||||||
let hash = SHA256.hash("test".as_bytes());
|
let hash = SHA256.hash("test".as_bytes());
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,11 @@ use tokio::{
|
||||||
net::TcpStream,
|
net::TcpStream,
|
||||||
};
|
};
|
||||||
|
|
||||||
use boring_rustls_provider::{tls13, PROVIDER};
|
use boring_rustls_provider::{tls12, tls13, PROVIDER};
|
||||||
use rustls::{version::TLS13, ServerConfig, SupportedCipherSuite};
|
use rustls::{
|
||||||
|
version::{TLS12, TLS13},
|
||||||
|
ServerConfig, SupportedCipherSuite,
|
||||||
|
};
|
||||||
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio_rustls::{TlsAcceptor, TlsConnector};
|
use tokio_rustls::{TlsAcceptor, TlsConnector};
|
||||||
|
|
@ -20,6 +23,7 @@ async fn test_tls13_crypto() {
|
||||||
let ciphers = [
|
let ciphers = [
|
||||||
SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256),
|
SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256),
|
||||||
SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384),
|
SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384),
|
||||||
|
SupportedCipherSuite::Tls13(&tls13::CHACHA20_POLY1305_SHA256),
|
||||||
];
|
];
|
||||||
|
|
||||||
for cipher in ciphers {
|
for cipher in ciphers {
|
||||||
|
|
@ -50,6 +54,47 @@ async fn test_tls13_crypto() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_tls12_ec_crypto() {
|
||||||
|
let pki = TestPki::new(&rcgen::PKCS_ECDSA_P256_SHA256);
|
||||||
|
|
||||||
|
let root_store = pki.client_root_store();
|
||||||
|
let server_config = pki.server_config();
|
||||||
|
|
||||||
|
let ciphers = [
|
||||||
|
SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES128_GCM_SHA256),
|
||||||
|
SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES256_GCM_SHA384),
|
||||||
|
SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256),
|
||||||
|
];
|
||||||
|
|
||||||
|
for cipher in ciphers {
|
||||||
|
let config = rustls::ClientConfig::builder_with_provider(PROVIDER)
|
||||||
|
.with_cipher_suites(&[cipher])
|
||||||
|
.with_safe_default_kx_groups()
|
||||||
|
.with_protocol_versions(&[&TLS12])
|
||||||
|
.unwrap()
|
||||||
|
.with_root_certificates(root_store.clone())
|
||||||
|
.with_no_client_auth();
|
||||||
|
|
||||||
|
let listener = new_listener().await;
|
||||||
|
let addr = listener.local_addr().unwrap();
|
||||||
|
tokio::spawn(spawn_echo_server(listener, server_config.clone()));
|
||||||
|
|
||||||
|
let connector = TlsConnector::from(Arc::new(config));
|
||||||
|
let stream = TcpStream::connect(&addr).await.unwrap();
|
||||||
|
|
||||||
|
let mut stream = connector
|
||||||
|
.connect(rustls::ServerName::try_from("localhost").unwrap(), stream)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
stream.write_all(b"HELLO").await.unwrap();
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
let bytes = stream.read_to_end(&mut buf).await.unwrap();
|
||||||
|
assert_eq!(&buf[..bytes], b"HELLO");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn new_listener() -> TcpListener {
|
async fn new_listener() -> TcpListener {
|
||||||
TcpListener::bind("localhost:0").await.unwrap()
|
TcpListener::bind("localhost:0").await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue