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
|
||||
```
|
||||
|
||||
TLS 1.2 prepared for (doesn't work yet):
|
||||
TLS 1.2 (only ECDSA is tested):
|
||||
```
|
||||
ECDHE_ECDSA_AES128_GCM_SHA256
|
||||
ECDHE_RSA_AES128_GCM_SHA256
|
||||
|
|
|
|||
|
|
@ -111,23 +111,38 @@ where
|
|||
msg: cipher::BorrowedPlainMessage,
|
||||
seq: u64,
|
||||
) -> Result<cipher::OpaqueMessage, rustls::Error> {
|
||||
let nonce = cipher::Nonce::new(&self.iv, seq);
|
||||
|
||||
match self.tls_version {
|
||||
#[cfg(feature = "tls12")]
|
||||
ProtocolVersion::TLSv1_2 => {
|
||||
let fixed_iv_len = <T as BoringCipher>::fixed_iv_len();
|
||||
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(|_| cipher::OpaqueMessage::new(msg.typ, msg.version, full_payload))
|
||||
}
|
||||
|
||||
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 nonce = cipher::Nonce::new(&self.iv, seq);
|
||||
|
||||
match self.tls_version {
|
||||
#[cfg(feature = "tls12")]
|
||||
ProtocolVersion::TLSv1_2 => {
|
||||
let aad = cipher::make_tls12_aad(seq, msg.typ, msg.version, total_len);
|
||||
self.encrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, &mut payload)
|
||||
.map_err(|_| rustls::Error::EncryptError)
|
||||
.map(|_| cipher::OpaqueMessage::new(msg.typ, msg.version, payload))
|
||||
}
|
||||
ProtocolVersion::TLSv1_3 => {
|
||||
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)
|
||||
|
|
@ -159,18 +174,56 @@ where
|
|||
seq: u64,
|
||||
) -> Result<cipher::PlainMessage, rustls::Error> {
|
||||
// construct nonce
|
||||
let nonce = cipher::Nonce::new(&self.iv, seq);
|
||||
|
||||
// construct the aad and decrypt
|
||||
match self.tls_version {
|
||||
#[cfg(feature = "tls12")]
|
||||
ProtocolVersion::TLSv1_2 => {
|
||||
let aad = make_tls12_aad(seq, m.typ, m.version, m.payload().len());
|
||||
self.decrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, m.payload_mut())
|
||||
let explicit_nonce_len = <T as BoringCipher>::explicit_nonce_len();
|
||||
|
||||
// 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(|_| 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 => {
|
||||
let nonce = cipher::Nonce::new(&self.iv, seq);
|
||||
let aad = make_tls13_aad(m.payload().len());
|
||||
self.decrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, m.payload_mut())
|
||||
.map_err(|_| rustls::Error::DecryptError)
|
||||
|
|
@ -221,17 +274,28 @@ impl<T: BoringAead + 'static> cipher::Tls12AeadAlgorithm for Aead<T> {
|
|||
&self,
|
||||
key: cipher::AeadKey,
|
||||
iv: &[u8],
|
||||
_extra: &[u8],
|
||||
extra: &[u8],
|
||||
) -> 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(
|
||||
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"),
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
BoringAeadCrypter::<T>::new(Iv::copy(iv), key.as_ref(), ProtocolVersion::TLSv1_2)
|
||||
BoringAeadCrypter::<T>::new(
|
||||
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(),
|
||||
// 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
|
||||
fixed_iv_len: <T as BoringCipher>::fixed_iv_len()
|
||||
+ <T as BoringCipher>::explicit_nonce_len(),
|
||||
explicit_nonce_len: 0,
|
||||
fixed_iv_len: <T as BoringCipher>::fixed_iv_len(),
|
||||
explicit_nonce_len: <T as BoringCipher>::explicit_nonce_len(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -251,8 +314,20 @@ impl<T: BoringAead + 'static> cipher::Tls12AeadAlgorithm for Aead<T> {
|
|||
&self,
|
||||
key: cipher::AeadKey,
|
||||
iv: &[u8],
|
||||
_explicit: &[u8],
|
||||
explicit: &[u8],
|
||||
) -> 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 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]
|
||||
fn test_sha256() {
|
||||
let hash = SHA256.hash("test".as_bytes());
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ use tokio::{
|
|||
net::TcpStream,
|
||||
};
|
||||
|
||||
use boring_rustls_provider::{tls13, PROVIDER};
|
||||
use rustls::{version::TLS13, ServerConfig, SupportedCipherSuite};
|
||||
use boring_rustls_provider::{tls12, tls13, PROVIDER};
|
||||
use rustls::{
|
||||
version::{TLS12, TLS13},
|
||||
ServerConfig, SupportedCipherSuite,
|
||||
};
|
||||
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_rustls::{TlsAcceptor, TlsConnector};
|
||||
|
|
@ -20,6 +23,7 @@ async fn test_tls13_crypto() {
|
|||
let ciphers = [
|
||||
SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256),
|
||||
SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384),
|
||||
SupportedCipherSuite::Tls13(&tls13::CHACHA20_POLY1305_SHA256),
|
||||
];
|
||||
|
||||
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 {
|
||||
TcpListener::bind("localhost:0").await.unwrap()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue