mirror of https://github.com/ctz/rustls
quic: Expose keys rather than secrets
Strengthens protection of sensitive internal state and makes life easier for QUIC implementations.
This commit is contained in:
parent
b2fa83cada
commit
96acad1d55
|
@ -5,29 +5,40 @@ use crate::msgs::handshake::{ClientExtension, ServerExtension};
|
|||
use crate::msgs::message::{Message, MessagePayload};
|
||||
use crate::server::{ServerConfig, ServerSession, ServerSessionImpl};
|
||||
use crate::error::TLSError;
|
||||
use crate::key_schedule;
|
||||
use crate::key_schedule::hkdf_expand;
|
||||
use crate::session::{SessionCommon, Protocol};
|
||||
use crate::suites::{BulkAlgorithm, SupportedCipherSuite, TLS13_AES_128_GCM_SHA256};
|
||||
|
||||
use std::sync::Arc;
|
||||
use ring::hkdf;
|
||||
use ring::{aead, hkdf};
|
||||
use webpki;
|
||||
|
||||
/// Secrets used to encrypt/decrypt traffic
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Secrets {
|
||||
pub(crate) struct Secrets {
|
||||
/// Secret used to encrypt packets transmitted by the client
|
||||
pub client: hkdf::Prk,
|
||||
/// Secret used to encrypt packets transmitted by the server
|
||||
pub server: hkdf::Prk,
|
||||
}
|
||||
|
||||
impl Secrets {
|
||||
fn local_remote(&self, is_client: bool) -> (&hkdf::Prk, &hkdf::Prk) {
|
||||
if is_client {
|
||||
(&self.client, &self.server)
|
||||
} else {
|
||||
(&self.server, &self.client)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic methods for QUIC sessions
|
||||
pub trait QuicExt {
|
||||
/// Return the TLS-encoded transport parameters for the session's peer.
|
||||
fn get_quic_transport_parameters(&self) -> Option<&[u8]>;
|
||||
|
||||
/// Return the early traffic secret, used to encrypt 0-RTT data.
|
||||
fn get_early_secret(&self) -> Option<&hkdf::Prk>;
|
||||
/// Compute the keys for encrypting/decrypting 0-RTT packets, if available
|
||||
fn get_0rtt_keys(&self) -> Option<DirectionalKeys>;
|
||||
|
||||
/// Consume unencrypted TLS handshake data.
|
||||
///
|
||||
|
@ -36,17 +47,18 @@ pub trait QuicExt {
|
|||
|
||||
/// Emit unencrypted TLS handshake data.
|
||||
///
|
||||
/// When this returns `Some(_)`, the keys used for future handshake data must be derived from
|
||||
/// the new secrets.
|
||||
fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Secrets>;
|
||||
/// When this returns `Some(_)`, the new keys must be used for future handshake data.
|
||||
fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Keys>;
|
||||
|
||||
/// Emit the TLS description code of a fatal alert, if one has arisen.
|
||||
///
|
||||
/// Check after `read_hs` returns `Err(_)`.
|
||||
fn get_alert(&self) -> Option<AlertDescription>;
|
||||
|
||||
/// Compute the secrets to use following a 1-RTT key update from their previous values.
|
||||
fn update_secrets(&self, client: &hkdf::Prk, server: &hkdf::Prk) -> Secrets;
|
||||
/// Compute the keys to use following a 1-RTT key update
|
||||
///
|
||||
/// Must not be called until the handshake is complete
|
||||
fn next_1rtt_keys(&mut self) -> PacketKeySet;
|
||||
}
|
||||
|
||||
impl QuicExt for ClientSession {
|
||||
|
@ -54,19 +66,24 @@ impl QuicExt for ClientSession {
|
|||
self.imp.common.quic.params.as_ref().map(|v| v.as_ref())
|
||||
}
|
||||
|
||||
fn get_early_secret(&self) -> Option<&hkdf::Prk> {
|
||||
self.imp.common.quic.early_secret.as_ref()
|
||||
fn get_0rtt_keys(&self) -> Option<DirectionalKeys> {
|
||||
Some(DirectionalKeys::new(
|
||||
self.imp.resumption_ciphersuite?,
|
||||
self.imp.common.quic.early_secret.as_ref()?,
|
||||
))
|
||||
}
|
||||
|
||||
fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), TLSError> {
|
||||
read_hs(&mut self.imp.common, plaintext)?;
|
||||
self.imp.process_new_handshake_messages()
|
||||
}
|
||||
fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Secrets> { write_hs(&mut self.imp.common, buf) }
|
||||
fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Keys> { write_hs(&mut self.imp.common, buf) }
|
||||
|
||||
fn get_alert(&self) -> Option<AlertDescription> { self.imp.common.quic.alert }
|
||||
|
||||
fn update_secrets(&self, client: &hkdf::Prk, server: &hkdf::Prk) -> Secrets { update_secrets(&self.imp.common, client, server) }
|
||||
fn next_1rtt_keys(&mut self) -> PacketKeySet {
|
||||
next_1rtt_keys(&mut self.imp.common)
|
||||
}
|
||||
}
|
||||
|
||||
impl QuicExt for ServerSession {
|
||||
|
@ -74,19 +91,143 @@ impl QuicExt for ServerSession {
|
|||
self.imp.common.quic.params.as_ref().map(|v| v.as_ref())
|
||||
}
|
||||
|
||||
fn get_early_secret(&self) -> Option<&hkdf::Prk> {
|
||||
self.imp.common.quic.early_secret.as_ref()
|
||||
fn get_0rtt_keys(&self) -> Option<DirectionalKeys> {
|
||||
Some(DirectionalKeys::new(
|
||||
self.imp.common.get_suite()?,
|
||||
self.imp.common.quic.early_secret.as_ref()?,
|
||||
))
|
||||
}
|
||||
|
||||
fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), TLSError> {
|
||||
read_hs(&mut self.imp.common, plaintext)?;
|
||||
self.imp.process_new_handshake_messages()
|
||||
}
|
||||
fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Secrets> { write_hs(&mut self.imp.common, buf) }
|
||||
fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Keys> { write_hs(&mut self.imp.common, buf) }
|
||||
|
||||
fn get_alert(&self) -> Option<AlertDescription> { self.imp.common.quic.alert }
|
||||
|
||||
fn update_secrets(&self, client: &hkdf::Prk, server: &hkdf::Prk) -> Secrets { update_secrets(&self.imp.common, client, server) }
|
||||
fn next_1rtt_keys(&mut self) -> PacketKeySet {
|
||||
next_1rtt_keys(&mut self.imp.common)
|
||||
}
|
||||
}
|
||||
|
||||
/// Keys used to communicate in a single direction
|
||||
pub struct DirectionalKeys {
|
||||
/// Encrypts or decrypts a packet's headers
|
||||
pub header: aead::quic::HeaderProtectionKey,
|
||||
/// Encrypts or decrypts the payload of a packet
|
||||
pub packet: PacketKey,
|
||||
}
|
||||
|
||||
impl DirectionalKeys {
|
||||
fn new(suite: &'static SupportedCipherSuite, secret: &hkdf::Prk) -> Self {
|
||||
let hp_alg = match suite.bulk {
|
||||
BulkAlgorithm::AES_128_GCM => &aead::quic::AES_128,
|
||||
BulkAlgorithm::AES_256_GCM => &aead::quic::AES_256,
|
||||
BulkAlgorithm::CHACHA20_POLY1305 => &aead::quic::CHACHA20,
|
||||
};
|
||||
|
||||
Self {
|
||||
header: hkdf_expand(secret, hp_alg, b"quic hp", &[]),
|
||||
packet: PacketKey::new(suite, secret),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Keys to encrypt or decrypt the payload of a packet
|
||||
pub struct PacketKey {
|
||||
/// Encrypts or decrypts a packet's payload
|
||||
pub key: aead::LessSafeKey,
|
||||
/// Computes unique nonces for each packet
|
||||
pub iv: Iv,
|
||||
}
|
||||
|
||||
impl PacketKey {
|
||||
fn new(suite: &'static SupportedCipherSuite, secret: &hkdf::Prk) -> Self {
|
||||
Self {
|
||||
key: aead::LessSafeKey::new(hkdf_expand(
|
||||
secret,
|
||||
suite.get_aead_alg(),
|
||||
b"quic key",
|
||||
&[],
|
||||
)),
|
||||
iv: hkdf_expand(secret, IvLen, b"quic iv", &[]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Packet protection keys for bidirectional 1-RTT communication
|
||||
pub struct PacketKeySet {
|
||||
/// Encrypts outgoing packets
|
||||
pub local: PacketKey,
|
||||
/// Decrypts incoming packets
|
||||
pub remote: PacketKey,
|
||||
}
|
||||
|
||||
/// Computes unique nonces for each packet
|
||||
pub struct Iv([u8; aead::NONCE_LEN]);
|
||||
|
||||
impl Iv {
|
||||
/// Compute the nonce to use for encrypting or decrypting `packet_number`
|
||||
pub fn nonce_for(&self, packet_number: u64) -> ring::aead::Nonce {
|
||||
let mut out = [0; aead::NONCE_LEN];
|
||||
out[4..].copy_from_slice(&packet_number.to_be_bytes());
|
||||
for (out, inp) in out.iter_mut().zip(self.0.iter()) {
|
||||
*out ^= inp;
|
||||
}
|
||||
aead::Nonce::assume_unique_for_key(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hkdf::Okm<'_, IvLen>> for Iv {
|
||||
fn from(okm: hkdf::Okm<IvLen>) -> Self {
|
||||
let mut iv = [0; aead::NONCE_LEN];
|
||||
okm.fill(&mut iv[..]).unwrap();
|
||||
Iv(iv)
|
||||
}
|
||||
}
|
||||
|
||||
struct IvLen;
|
||||
|
||||
impl hkdf::KeyType for IvLen {
|
||||
fn len(&self) -> usize {
|
||||
aead::NONCE_LEN
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete set of keys used to communicate with the peer
|
||||
pub struct Keys {
|
||||
/// Encrypts outgoing packets
|
||||
pub local: DirectionalKeys,
|
||||
/// Decrypts incoming packets
|
||||
pub remote: DirectionalKeys,
|
||||
}
|
||||
|
||||
impl Keys {
|
||||
/// Construct keys for use with initial packets
|
||||
pub fn initial(
|
||||
initial_salt: &hkdf::Salt,
|
||||
client_dst_connection_id: &[u8],
|
||||
is_client: bool,
|
||||
) -> Self {
|
||||
const CLIENT_LABEL: &[u8] = b"client in";
|
||||
const SERVER_LABEL: &[u8] = b"server in";
|
||||
let hs_secret = initial_salt.extract(client_dst_connection_id);
|
||||
|
||||
let secrets = Secrets {
|
||||
client: hkdf_expand(&hs_secret, hkdf::HKDF_SHA256, CLIENT_LABEL, &[]),
|
||||
server: hkdf_expand(&hs_secret, hkdf::HKDF_SHA256, SERVER_LABEL, &[]),
|
||||
};
|
||||
Self::new(&TLS13_AES_128_GCM_SHA256, is_client, &secrets)
|
||||
}
|
||||
|
||||
fn new(suite: &'static SupportedCipherSuite, is_client: bool, secrets: &Secrets) -> Self {
|
||||
let (local, remote) = secrets.local_remote(is_client);
|
||||
Keys {
|
||||
local: DirectionalKeys::new(suite, local),
|
||||
remote: DirectionalKeys::new(suite, remote),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_hs(this: &mut SessionCommon, plaintext: &[u8]) -> Result<(), TLSError> {
|
||||
|
@ -104,7 +245,7 @@ fn read_hs(this: &mut SessionCommon, plaintext: &[u8]) -> Result<(), TLSError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn write_hs(this: &mut SessionCommon, buf: &mut Vec<u8>) -> Option<Secrets> {
|
||||
fn write_hs(this: &mut SessionCommon, buf: &mut Vec<u8>) -> Option<Keys> {
|
||||
while let Some((_, msg)) = this.quic.hs_queue.pop_front() {
|
||||
buf.extend_from_slice(&msg);
|
||||
if let Some(&(true, _)) = this.quic.hs_queue.front() {
|
||||
|
@ -115,30 +256,41 @@ fn write_hs(this: &mut SessionCommon, buf: &mut Vec<u8>) -> Option<Secrets> {
|
|||
}
|
||||
}
|
||||
if let Some(secrets) = this.quic.hs_secrets.take() {
|
||||
return Some(secrets);
|
||||
return Some(Keys::new(this.get_suite_assert(), this.is_client, &secrets));
|
||||
}
|
||||
if let Some(secrets) = this.quic.traffic_secrets.take() {
|
||||
return Some(secrets);
|
||||
if let Some(secrets) = this.quic.traffic_secrets.as_ref() {
|
||||
if !this.quic.returned_traffic_keys {
|
||||
this.quic.returned_traffic_keys = true;
|
||||
return Some(Keys::new(this.get_suite_assert(), this.is_client, &secrets));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn update_secrets(this: &SessionCommon, client: &hkdf::Prk, server: &hkdf::Prk) -> Secrets {
|
||||
let hkdf_alg= this.get_suite_assert().hkdf_algorithm;
|
||||
let client = key_schedule::hkdf_expand(
|
||||
client,
|
||||
hkdf_alg,
|
||||
b"quic ku",
|
||||
&[]);
|
||||
let server = key_schedule::hkdf_expand(
|
||||
server,
|
||||
hkdf_alg,
|
||||
b"quic ku",
|
||||
&[]);
|
||||
fn next_1rtt_keys(this: &mut SessionCommon) -> PacketKeySet {
|
||||
let hkdf_alg = this.get_suite_assert().hkdf_algorithm;
|
||||
let secrets = this
|
||||
.quic
|
||||
.traffic_secrets
|
||||
.as_ref()
|
||||
.expect("traffic keys not yet available");
|
||||
|
||||
let next = next_1rtt_secrets(hkdf_alg, secrets);
|
||||
|
||||
let (local, remote) = secrets.local_remote(this.is_client);
|
||||
let keys = PacketKeySet {
|
||||
local: PacketKey::new(this.get_suite_assert(), local),
|
||||
remote: PacketKey::new(this.get_suite_assert(), remote),
|
||||
};
|
||||
|
||||
this.quic.traffic_secrets = Some(next);
|
||||
keys
|
||||
}
|
||||
|
||||
fn next_1rtt_secrets(hkdf_alg: hkdf::Algorithm, prev: &Secrets) -> Secrets {
|
||||
Secrets {
|
||||
client,
|
||||
server,
|
||||
client: hkdf_expand(&prev.client, hkdf_alg, b"quic ku", &[]),
|
||||
server: hkdf_expand(&prev.server, hkdf_alg, b"quic ku", &[]),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,3 +330,199 @@ pub trait ServerQuicExt {
|
|||
}
|
||||
|
||||
impl ServerQuicExt for ServerSession {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn initial_keys_test_vectors() {
|
||||
// Test vectors based on draft 27
|
||||
const INITIAL_SALT: [u8; 20] = [
|
||||
0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4,
|
||||
0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02,
|
||||
];
|
||||
|
||||
const CONNECTION_ID: &[u8] = &[0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];
|
||||
const PACKET_NUMBER: u64 = 42;
|
||||
|
||||
let initial_salt = hkdf::Salt::new(hkdf::HKDF_SHA256, &INITIAL_SALT);
|
||||
let server_keys = Keys::initial(&initial_salt, &CONNECTION_ID, false);
|
||||
let client_keys = Keys::initial(&initial_salt, &CONNECTION_ID, true);
|
||||
|
||||
// Nonces
|
||||
const SERVER_NONCE: [u8; 12] = [
|
||||
0x5e, 0x5a, 0xe6, 0x51, 0xfd, 0x1e, 0x84, 0x95, 0xaf, 0x13, 0x50, 0xa1,
|
||||
];
|
||||
assert_eq!(
|
||||
server_keys
|
||||
.local
|
||||
.packet
|
||||
.iv
|
||||
.nonce_for(PACKET_NUMBER)
|
||||
.as_ref(),
|
||||
&SERVER_NONCE
|
||||
);
|
||||
assert_eq!(
|
||||
client_keys
|
||||
.remote
|
||||
.packet
|
||||
.iv
|
||||
.nonce_for(PACKET_NUMBER)
|
||||
.as_ref(),
|
||||
&SERVER_NONCE
|
||||
);
|
||||
const CLIENT_NONCE: [u8; 12] = [
|
||||
0x86, 0x81, 0x35, 0x94, 0x10, 0xa7, 0x0b, 0xb9, 0xc9, 0x2f, 0x04, 0x0a,
|
||||
];
|
||||
assert_eq!(
|
||||
server_keys
|
||||
.remote
|
||||
.packet
|
||||
.iv
|
||||
.nonce_for(PACKET_NUMBER)
|
||||
.as_ref(),
|
||||
&CLIENT_NONCE
|
||||
);
|
||||
assert_eq!(
|
||||
client_keys
|
||||
.local
|
||||
.packet
|
||||
.iv
|
||||
.nonce_for(PACKET_NUMBER)
|
||||
.as_ref(),
|
||||
&CLIENT_NONCE
|
||||
);
|
||||
|
||||
// Header encryption mask
|
||||
const SAMPLE: &[u8] = &[
|
||||
0x70, 0x02, 0x59, 0x6f, 0x99, 0xae, 0x67, 0xab, 0xf6, 0x5a, 0x58, 0x52, 0xf5, 0x4f,
|
||||
0x58, 0xc3,
|
||||
];
|
||||
|
||||
const SERVER_MASK: [u8; 5] = [0x38, 0x16, 0x8a, 0x0c, 0x25];
|
||||
assert_eq!(
|
||||
server_keys.local.header.new_mask(SAMPLE).unwrap(),
|
||||
SERVER_MASK
|
||||
);
|
||||
assert_eq!(
|
||||
client_keys.remote.header.new_mask(SAMPLE).unwrap(),
|
||||
SERVER_MASK
|
||||
);
|
||||
const CLIENT_MASK: [u8; 5] = [0xae, 0x96, 0x2e, 0x67, 0xec];
|
||||
assert_eq!(
|
||||
server_keys.remote.header.new_mask(SAMPLE).unwrap(),
|
||||
CLIENT_MASK
|
||||
);
|
||||
assert_eq!(
|
||||
client_keys.local.header.new_mask(SAMPLE).unwrap(),
|
||||
CLIENT_MASK
|
||||
);
|
||||
|
||||
const AAD: &[u8] = &[
|
||||
0xc9, 0xff, 0x00, 0x00, 0x1b, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62,
|
||||
0xb5, 0x00, 0x40, 0x74, 0x16, 0x8b,
|
||||
];
|
||||
let aad = aead::Aad::from(AAD);
|
||||
const PLAINTEXT: [u8; 12] = [
|
||||
0x0d, 0x00, 0x00, 0x00, 0x00, 0x18, 0x41, 0x0a, 0x02, 0x00, 0x00, 0x56,
|
||||
];
|
||||
let mut payload = PLAINTEXT;
|
||||
let server_nonce = server_keys.local.packet.iv.nonce_for(PACKET_NUMBER);
|
||||
let tag = server_keys
|
||||
.local
|
||||
.packet
|
||||
.key
|
||||
.seal_in_place_separate_tag(server_nonce, aad, &mut payload)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
payload,
|
||||
[0x0d, 0x91, 0x96, 0x31, 0xc0, 0xeb, 0x84, 0xf2, 0x88, 0x59, 0xfe, 0xc0]
|
||||
);
|
||||
assert_eq!(
|
||||
tag.as_ref(),
|
||||
&[
|
||||
0xdf, 0xee, 0x06, 0x81, 0x9e, 0x7a, 0x08, 0x34, 0xe4, 0x94, 0x19, 0x79, 0x5f, 0xe0,
|
||||
0xd7, 0x3f
|
||||
]
|
||||
);
|
||||
|
||||
let aad = aead::Aad::from(AAD);
|
||||
let mut payload = PLAINTEXT;
|
||||
let client_nonce = client_keys.local.packet.iv.nonce_for(PACKET_NUMBER);
|
||||
let tag = client_keys
|
||||
.local
|
||||
.packet
|
||||
.key
|
||||
.seal_in_place_separate_tag(client_nonce, aad, &mut payload)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
payload,
|
||||
[0x89, 0x6c, 0x66, 0x91, 0xe0, 0x9f, 0x47, 0x7a, 0x91, 0x42, 0xa4, 0x46]
|
||||
);
|
||||
assert_eq!(
|
||||
tag.as_ref(),
|
||||
&[
|
||||
0xb6, 0xff, 0xef, 0x89, 0xd5, 0xcb, 0x53, 0xd0, 0x98, 0xf7, 0x40, 0xa, 0x8d, 0x97,
|
||||
0x72, 0x6e
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_update_test_vector() {
|
||||
fn equal_prk(x: &hkdf::Prk, y: &hkdf::Prk) -> bool {
|
||||
let mut x_data = [0; 16];
|
||||
let mut y_data = [0; 16];
|
||||
let x_okm = x.expand(&[b"info"], &aead::quic::AES_128).unwrap();
|
||||
x_okm.fill(&mut x_data[..]).unwrap();
|
||||
let y_okm = y.expand(&[b"info"], &aead::quic::AES_128).unwrap();
|
||||
y_okm.fill(&mut y_data[..]).unwrap();
|
||||
x_data == y_data
|
||||
}
|
||||
|
||||
let initial = Secrets {
|
||||
// Constant dummy values for reproducibility
|
||||
client: hkdf::Prk::new_less_safe(
|
||||
hkdf::HKDF_SHA256,
|
||||
&[
|
||||
0xb8, 0x76, 0x77, 0x08, 0xf8, 0x77, 0x23, 0x58, 0xa6, 0xea, 0x9f, 0xc4, 0x3e,
|
||||
0x4a, 0xdd, 0x2c, 0x96, 0x1b, 0x3f, 0x52, 0x87, 0xa6, 0xd1, 0x46, 0x7e, 0xe0,
|
||||
0xae, 0xab, 0x33, 0x72, 0x4d, 0xbf,
|
||||
],
|
||||
),
|
||||
server: hkdf::Prk::new_less_safe(
|
||||
hkdf::HKDF_SHA256,
|
||||
&[
|
||||
0x42, 0xdc, 0x97, 0x21, 0x40, 0xe0, 0xf2, 0xe3, 0x98, 0x45, 0xb7, 0x67, 0x61,
|
||||
0x34, 0x39, 0xdc, 0x67, 0x58, 0xca, 0x43, 0x25, 0x9b, 0x87, 0x85, 0x06, 0x82,
|
||||
0x4e, 0xb1, 0xe4, 0x38, 0xd8, 0x55,
|
||||
],
|
||||
),
|
||||
};
|
||||
let updated = next_1rtt_secrets(hkdf::HKDF_SHA256, &initial);
|
||||
|
||||
assert!(equal_prk(
|
||||
&updated.client,
|
||||
&hkdf::Prk::new_less_safe(
|
||||
hkdf::HKDF_SHA256,
|
||||
&[
|
||||
0x42, 0xca, 0xc8, 0xc9, 0x1c, 0xd5, 0xeb, 0x40, 0x68, 0x2e, 0x43, 0x2e, 0xdf,
|
||||
0x2d, 0x2b, 0xe9, 0xf4, 0x1a, 0x52, 0xca, 0x6b, 0x22, 0xd8, 0xe6, 0xcd, 0xb1,
|
||||
0xe8, 0xac, 0xa9, 0x6, 0x1f, 0xce
|
||||
]
|
||||
)
|
||||
));
|
||||
assert!(equal_prk(
|
||||
&updated.server,
|
||||
&hkdf::Prk::new_less_safe(
|
||||
hkdf::HKDF_SHA256,
|
||||
&[
|
||||
0xeb, 0x7f, 0x5e, 0x2a, 0x12, 0x3f, 0x40, 0x7d, 0xb4, 0x99, 0xe3, 0x61, 0xca,
|
||||
0xe5, 0x90, 0xd4, 0xd9, 0x92, 0xe1, 0x4b, 0x7a, 0xce, 0x3, 0xc2, 0x44, 0xe0,
|
||||
0x42, 0x21, 0x15, 0xb6, 0xd3, 0x8a
|
||||
]
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -742,6 +742,8 @@ pub(crate) struct Quic {
|
|||
pub early_secret: Option<ring::hkdf::Prk>,
|
||||
pub hs_secrets: Option<quic::Secrets>,
|
||||
pub traffic_secrets: Option<quic::Secrets>,
|
||||
/// Whether keys derived from traffic_secrets have been passed to the QUIC implementation
|
||||
pub returned_traffic_keys: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "quic")]
|
||||
|
@ -754,6 +756,7 @@ impl Quic {
|
|||
early_secret: None,
|
||||
hs_secrets: None,
|
||||
traffic_secrets: None,
|
||||
returned_traffic_keys: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@ use rustls::KeyLog;
|
|||
use rustls::ClientHello;
|
||||
#[cfg(feature = "quic")]
|
||||
use rustls::quic::{self, QuicExt, ClientQuicExt, ServerQuicExt};
|
||||
#[cfg(feature = "quic")]
|
||||
use ring::hkdf;
|
||||
|
||||
#[cfg(feature = "dangerous_configuration")]
|
||||
use rustls::ClientCertVerified;
|
||||
|
@ -2015,7 +2013,7 @@ mod test_quic {
|
|||
use super::*;
|
||||
|
||||
// Returns the sender's next secrets to use, or the receiver's error.
|
||||
fn step(send: &mut dyn Session, recv: &mut dyn Session) -> Result<Option<quic::Secrets>, TLSError> {
|
||||
fn step(send: &mut dyn Session, recv: &mut dyn Session) -> Result<Option<quic::Keys>, TLSError> {
|
||||
let mut buf = Vec::new();
|
||||
let secrets = loop {
|
||||
let prev = buf.len();
|
||||
|
@ -2036,18 +2034,13 @@ mod test_quic {
|
|||
|
||||
#[test]
|
||||
fn test_quic_handshake() {
|
||||
fn equal_prk(x: &ring::hkdf::Prk, y: &ring::hkdf::Prk) -> bool {
|
||||
let mut x_data = [0; 16];
|
||||
let mut y_data = [0; 16];
|
||||
let x_okm = x.expand(&[b"info"], &ring::aead::quic::AES_128).unwrap();
|
||||
x_okm.fill(&mut x_data[..]).unwrap();
|
||||
let y_okm = y.expand(&[b"info"], &ring::aead::quic::AES_128).unwrap();
|
||||
y_okm.fill(&mut y_data[..]).unwrap();
|
||||
x_data == y_data
|
||||
fn equal_dir_keys(x: &quic::DirectionalKeys, y: &quic::DirectionalKeys) -> bool {
|
||||
// Check that these two sets of keys are equal. The quic module's unit tests validate
|
||||
// that the IV and the keys are consistent, so we can just check the IV here.
|
||||
x.packet.iv.nonce_for(42).as_ref() == y.packet.iv.nonce_for(42).as_ref()
|
||||
}
|
||||
|
||||
fn equal_secrets(x: &quic::Secrets, y: &quic::Secrets) -> bool {
|
||||
equal_prk(&x.client, &y.client) && equal_prk(&x.server, &y.server)
|
||||
fn compatible_keys(x: &quic::Keys, y: &quic::Keys) -> bool {
|
||||
equal_dir_keys(&x.local, &y.remote) && equal_dir_keys(&x.remote, &y.local)
|
||||
}
|
||||
|
||||
let kt = KeyType::RSA;
|
||||
|
@ -2069,12 +2062,12 @@ mod test_quic {
|
|||
let mut server = ServerSession::new_quic(&server_config, server_params.into());
|
||||
let client_initial = step(&mut client, &mut server).unwrap();
|
||||
assert!(client_initial.is_none());
|
||||
assert!(client.get_early_secret().is_none());
|
||||
assert!(client.get_0rtt_keys().is_none());
|
||||
assert_eq!(server.get_quic_transport_parameters(), Some(client_params));
|
||||
let server_hs = step(&mut server, &mut client).unwrap().unwrap();
|
||||
assert!(server.get_early_secret().is_none());
|
||||
assert!(server.get_0rtt_keys().is_none());
|
||||
let client_hs = step(&mut client, &mut server).unwrap().unwrap();
|
||||
assert!(equal_secrets(&server_hs, &client_hs));
|
||||
assert!(compatible_keys(&server_hs, &client_hs));
|
||||
assert!(client.is_handshaking());
|
||||
let server_1rtt = step(&mut server, &mut client).unwrap().unwrap();
|
||||
assert!(!client.is_handshaking());
|
||||
|
@ -2082,45 +2075,11 @@ mod test_quic {
|
|||
assert!(server.is_handshaking());
|
||||
let client_1rtt = step(&mut client, &mut server).unwrap().unwrap();
|
||||
assert!(!server.is_handshaking());
|
||||
assert!(equal_secrets(&server_1rtt, &client_1rtt));
|
||||
assert!(!equal_secrets(&server_hs, &server_1rtt));
|
||||
assert!(compatible_keys(&server_1rtt, &client_1rtt));
|
||||
assert!(!compatible_keys(&server_hs, &server_1rtt));
|
||||
assert!(step(&mut client, &mut server).unwrap().is_none());
|
||||
assert!(step(&mut server, &mut client).unwrap().is_none());
|
||||
|
||||
// key update
|
||||
let initial = quic::Secrets {
|
||||
// Constant dummy values for reproducibility
|
||||
client: hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, &[
|
||||
0xb8, 0x76, 0x77, 0x08, 0xf8, 0x77, 0x23, 0x58, 0xa6, 0xea, 0x9f, 0xc4, 0x3e, 0x4a,
|
||||
0xdd, 0x2c, 0x96, 0x1b, 0x3f, 0x52, 0x87, 0xa6, 0xd1, 0x46, 0x7e, 0xe0, 0xae, 0xab,
|
||||
0x33, 0x72, 0x4d, 0xbf,
|
||||
]),
|
||||
server: hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, &[
|
||||
0x42, 0xdc, 0x97, 0x21, 0x40, 0xe0, 0xf2, 0xe3, 0x98, 0x45, 0xb7, 0x67, 0x61, 0x34,
|
||||
0x39, 0xdc, 0x67, 0x58, 0xca, 0x43, 0x25, 0x9b, 0x87, 0x85, 0x06, 0x82, 0x4e, 0xb1,
|
||||
0xe4, 0x38, 0xd8, 0x55,
|
||||
]),
|
||||
};
|
||||
let updated = client.update_secrets(&initial.client, &initial.server);
|
||||
// The expected values will need to be updated if the negotiated hash function changes. Pull the
|
||||
// values from ring's `hmac::Key::construct` with a debugger.
|
||||
assert!(equal_prk(
|
||||
&updated.client,
|
||||
&hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, &[
|
||||
0x42, 0xca, 0xc8, 0xc9, 0x1c, 0xd5, 0xeb, 0x40, 0x68, 0x2e, 0x43,
|
||||
0x2e, 0xdf, 0x2d, 0x2b, 0xe9, 0xf4, 0x1a, 0x52, 0xca, 0x6b, 0x22, 0xd8, 0xe6, 0xcd, 0xb1,
|
||||
0xe8, 0xac, 0xa9, 0x6, 0x1f, 0xce
|
||||
]))
|
||||
);
|
||||
assert!(equal_prk(
|
||||
&updated.server,
|
||||
&hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, &[
|
||||
0xeb, 0x7f, 0x5e, 0x2a, 0x12, 0x3f, 0x40, 0x7d, 0xb4, 0x99, 0xe3,
|
||||
0x61, 0xca, 0xe5, 0x90, 0xd4, 0xd9, 0x92, 0xe1, 0x4b, 0x7a, 0xce, 0x3, 0xc2, 0x44, 0xe0,
|
||||
0x42, 0x21, 0x15, 0xb6, 0xd3, 0x8a
|
||||
]))
|
||||
);
|
||||
|
||||
// 0-RTT handshake
|
||||
let mut client =
|
||||
ClientSession::new_quic(&client_config, dns_name("localhost"), client_params.into());
|
||||
|
@ -2129,9 +2088,9 @@ mod test_quic {
|
|||
step(&mut client, &mut server).unwrap();
|
||||
assert_eq!(client.get_quic_transport_parameters(), Some(server_params));
|
||||
{
|
||||
let client_early = client.get_early_secret().unwrap();
|
||||
let server_early = server.get_early_secret().unwrap();
|
||||
assert!(equal_prk(client_early, server_early));
|
||||
let client_early = client.get_0rtt_keys().unwrap();
|
||||
let server_early = server.get_0rtt_keys().unwrap();
|
||||
assert!(equal_dir_keys(&client_early, &server_early));
|
||||
}
|
||||
step(&mut server, &mut client).unwrap().unwrap();
|
||||
step(&mut client, &mut server).unwrap().unwrap();
|
||||
|
@ -2142,13 +2101,16 @@ mod test_quic {
|
|||
{
|
||||
let mut client_config = (*client_config).clone();
|
||||
client_config.alpn_protocols = vec!["foo".into()];
|
||||
let mut client =
|
||||
ClientSession::new_quic(&Arc::new(client_config), dns_name("localhost"), client_params.into());
|
||||
let mut client = ClientSession::new_quic(
|
||||
&Arc::new(client_config),
|
||||
dns_name("localhost"),
|
||||
client_params.into(),
|
||||
);
|
||||
let mut server = ServerSession::new_quic(&server_config, server_params.into());
|
||||
step(&mut client, &mut server).unwrap();
|
||||
assert_eq!(client.get_quic_transport_parameters(), Some(server_params));
|
||||
assert!(client.get_early_secret().is_some());
|
||||
assert!(server.get_early_secret().is_none());
|
||||
assert!(client.get_0rtt_keys().is_some());
|
||||
assert!(server.get_0rtt_keys().is_none());
|
||||
step(&mut server, &mut client).unwrap().unwrap();
|
||||
step(&mut client, &mut server).unwrap().unwrap();
|
||||
step(&mut server, &mut client).unwrap().unwrap();
|
||||
|
@ -2164,7 +2126,7 @@ mod test_quic {
|
|||
let mut server = ServerSession::new_quic(&server_config, server_params.into());
|
||||
step(&mut client, &mut server).unwrap();
|
||||
step(&mut server, &mut client).unwrap().unwrap();
|
||||
step(&mut server, &mut client).unwrap_err();
|
||||
assert!(step(&mut server, &mut client).is_err());
|
||||
assert_eq!(
|
||||
client.get_alert(),
|
||||
Some(rustls::internal::msgs::enums::AlertDescription::BadCertificate)
|
||||
|
@ -2193,7 +2155,7 @@ mod test_quic {
|
|||
let mut server = ServerSession::new_quic(&server_config,
|
||||
server_params.into());
|
||||
|
||||
assert_eq!(step(&mut client, &mut server).unwrap_err(),
|
||||
assert_eq!(step(&mut client, &mut server).err().unwrap(),
|
||||
TLSError::NoApplicationProtocol);
|
||||
|
||||
assert_eq!(server.get_alert(),
|
||||
|
|
Loading…
Reference in New Issue