// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rustls::{cipher_suite as ciphers, quic, CipherSuite, SupportedCipherSuite}; use s2n_quic_core::crypto::{self, tls, CryptoError, HeaderProtectionMask, Key}; pub struct PacketKey { key: quic::PacketKey, cipher_suite: tls::CipherSuite, } impl PacketKey { pub(crate) fn new( keys: quic::DirectionalKeys, cipher_suite: tls::CipherSuite, ) -> (Self, HeaderProtectionKey) { ( Self { key: keys.packet, cipher_suite, }, HeaderProtectionKey(keys.header), ) } } impl crypto::Key for PacketKey { fn decrypt( &self, packet_number: u64, header: &[u8], payload: &mut [u8], ) -> Result<(), CryptoError> { match self.key.decrypt_in_place(packet_number, header, payload) { Ok(_tag) => Ok(()), Err(_) => Err(CryptoError::DECRYPT_ERROR), } } fn encrypt( &self, packet_number: u64, header: &[u8], payload: &mut [u8], ) -> Result<(), CryptoError> { let (payload, tag_storage) = payload.split_at_mut(payload.len() - self.tag_len()); match self.key.encrypt_in_place(packet_number, header, payload) { Ok(tag) => { tag_storage.copy_from_slice(tag.as_ref()); Ok(()) } Err(_) => Err(CryptoError::INTERNAL_ERROR), } } fn tag_len(&self) -> usize { self.key.tag_len() } fn aead_confidentiality_limit(&self) -> u64 { self.key.confidentiality_limit() } fn aead_integrity_limit(&self) -> u64 { self.key.integrity_limit() } fn cipher_suite(&self) -> tls::CipherSuite { self.cipher_suite } } impl crypto::ZeroRttKey for PacketKey {} pub struct PacketKeys { sealer: PacketKey, opener: PacketKey, } impl PacketKeys { pub(crate) fn new(keys: quic::Keys, cipher_suite: CipherSuite) -> (Self, HeaderProtectionKeys) { let quic::Keys { local, remote } = keys; let cipher_suite = match cipher_suite { CipherSuite::TLS13_AES_128_GCM_SHA256 => tls::CipherSuite::TLS_AES_128_GCM_SHA256, CipherSuite::TLS13_AES_256_GCM_SHA384 => tls::CipherSuite::TLS_AES_256_GCM_SHA384, CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => { tls::CipherSuite::TLS_CHACHA20_POLY1305_SHA256 } _ => tls::CipherSuite::Unknown, }; let (sealer_packet, sealer_header) = PacketKey::new(local, cipher_suite); let (opener_packet, opener_header) = PacketKey::new(remote, cipher_suite); let key = Self { sealer: sealer_packet, opener: opener_packet, }; let header_key = HeaderProtectionKeys { sealer: sealer_header, opener: opener_header, }; (key, header_key) } } impl crypto::Key for PacketKeys { fn decrypt( &self, packet_number: u64, header: &[u8], payload: &mut [u8], ) -> Result<(), CryptoError> { self.opener.decrypt(packet_number, header, payload) } fn encrypt( &self, packet_number: u64, header: &[u8], payload: &mut [u8], ) -> Result<(), CryptoError> { self.sealer.encrypt(packet_number, header, payload) } fn tag_len(&self) -> usize { self.sealer.tag_len() } fn aead_confidentiality_limit(&self) -> u64 { self.sealer.aead_confidentiality_limit() } fn aead_integrity_limit(&self) -> u64 { self.sealer.aead_integrity_limit() } fn cipher_suite(&self) -> tls::CipherSuite { self.sealer.cipher_suite() } } impl crypto::HandshakeKey for PacketKeys {} pub struct HeaderProtectionKey(quic::HeaderProtectionKey); impl HeaderProtectionKey { /// Returns the header protection mask for the given ciphertext sample /// /// Rustls API applies the header protection rather than returning /// the mask. This method exists for extracting the mask from rustls by calling /// the `encrypt_in_place` api and reversing the operation it applies. /// /// The primary motivation for extracting the mask from rustls is to maintain API /// compatibility with other tls providers (s2n-tls) and have one common code /// for applying and removing header protection. fn get_mask(&self, ciphertext_sample: &[u8]) -> HeaderProtectionMask { let mut mask = HeaderProtectionMask::default(); // tell rustls we need all of the packet number mask by setting the pn length to 4 let tag = 0b0000_0011; mask[0] = tag; let (packet_tag, packet_number) = mask.split_first_mut().unwrap(); self.0 .encrypt_in_place(ciphertext_sample, packet_tag, packet_number) .unwrap(); // rustls XORs the mask with the original tag so undo that operation mask[0] ^= tag; mask } } impl crypto::HeaderKey for HeaderProtectionKey { fn opening_header_protection_mask(&self, ciphertext_sample: &[u8]) -> HeaderProtectionMask { self.get_mask(ciphertext_sample) } fn opening_sample_len(&self) -> usize { self.0.sample_len() } fn sealing_header_protection_mask(&self, ciphertext_sample: &[u8]) -> HeaderProtectionMask { self.get_mask(ciphertext_sample) } fn sealing_sample_len(&self) -> usize { self.0.sample_len() } } impl crypto::ZeroRttHeaderKey for HeaderProtectionKey {} pub struct HeaderProtectionKeys { sealer: HeaderProtectionKey, opener: HeaderProtectionKey, } impl crypto::HeaderKey for HeaderProtectionKeys { fn opening_header_protection_mask(&self, ciphertext_sample: &[u8]) -> HeaderProtectionMask { self.opener .opening_header_protection_mask(ciphertext_sample) } fn opening_sample_len(&self) -> usize { self.opener.opening_sample_len() } fn sealing_header_protection_mask(&self, ciphertext_sample: &[u8]) -> HeaderProtectionMask { self.sealer .sealing_header_protection_mask(ciphertext_sample) } fn sealing_sample_len(&self) -> usize { self.sealer.sealing_sample_len() } } impl crypto::HandshakeHeaderKey for HeaderProtectionKeys {} impl crypto::OneRttHeaderKey for HeaderProtectionKeys {} pub struct OneRttKey { key: PacketKeys, secrets: quic::Secrets, } impl OneRttKey { pub(crate) fn new( keys: quic::Keys, secrets: quic::Secrets, cipher_suite: CipherSuite, ) -> (Self, HeaderProtectionKeys) { let (key, header_key) = PacketKeys::new(keys, cipher_suite); let key = Self { key, secrets }; (key, header_key) } } impl crypto::Key for OneRttKey { fn decrypt( &self, packet_number: u64, header: &[u8], payload: &mut [u8], ) -> Result<(), CryptoError> { self.key.decrypt(packet_number, header, payload) } fn encrypt( &self, packet_number: u64, header: &[u8], payload: &mut [u8], ) -> Result<(), CryptoError> { self.key.encrypt(packet_number, header, payload) } fn tag_len(&self) -> usize { self.key.tag_len() } fn aead_confidentiality_limit(&self) -> u64 { self.key.aead_confidentiality_limit() } fn aead_integrity_limit(&self) -> u64 { self.key.aead_integrity_limit() } fn cipher_suite(&self) -> tls::CipherSuite { self.key.cipher_suite() } } impl crypto::OneRttKey for OneRttKey { fn derive_next_key(&self) -> Self { let cipher_suite = self.cipher_suite(); let mut secrets = self.secrets.clone(); let quic::PacketKeySet { local, remote } = secrets.next_packet_keys(); Self { key: PacketKeys { sealer: PacketKey { key: local, cipher_suite, }, opener: PacketKey { key: remote, cipher_suite, }, }, secrets, } } fn update_sealer_pmtu(&mut self, _pmtu: u16) { // rustls doesn't have any ptmu specialization } fn update_opener_pmtu(&mut self, _pmtu: u16) { // rustls doesn't have any ptmu specialization } } //= https://www.rfc-editor.org/rfc/rfc9001#section-5.3 //# A cipher suite MUST NOT be //# negotiated unless a header protection scheme is defined for the //# cipher suite. // All of the cipher_suites from the current exported list have HP schemes for QUIC pub static DEFAULT_CIPHERSUITES: &[SupportedCipherSuite] = &[ ciphers::TLS13_AES_128_GCM_SHA256, ciphers::TLS13_AES_256_GCM_SHA384, ciphers::TLS13_CHACHA20_POLY1305_SHA256, ]; #[test] fn test_default_cipher_suites() { insta::assert_debug_snapshot!("default_cipher_suites", DEFAULT_CIPHERSUITES); }