noq_proto/
crypto.rs

1//! Traits and implementations for the QUIC cryptography protocol
2//!
3//! The protocol logic in noq is contained in types that abstract over the actual
4//! cryptographic protocol used. This module contains the traits used for this
5//! abstraction layer as well as a single implementation of these traits that uses
6//! *ring* and rustls to implement the TLS protocol support.
7//!
8//! Note that usage of any protocol (version) other than TLS 1.3 does not conform to any
9//! published versions of the specification, and will not be supported in QUIC v1.
10
11use std::{any::Any, str, sync::Arc};
12
13use bytes::BytesMut;
14
15use crate::{
16    ConnectError, PathId, Side, TransportError, shared::ConnectionId,
17    transport_parameters::TransportParameters,
18};
19
20/// Cryptography interface based on ring or aws-lc-rs.
21///
22/// Prefers ring, if both are enabled.
23#[cfg(any(feature = "aws-lc-rs", feature = "ring"))]
24pub(crate) mod ring_like;
25/// TLS interface based on rustls
26#[cfg(feature = "rustls")]
27pub mod rustls;
28
29/// A cryptographic session (commonly TLS)
30pub trait Session: Send + Sync + 'static {
31    /// Create the initial set of keys given the client's initial destination ConnectionId
32    fn initial_keys(&self, dst_cid: ConnectionId, side: Side) -> Keys;
33
34    /// Get data negotiated during the handshake, if available
35    ///
36    /// Returns `None` until the connection emits `HandshakeDataReady`.
37    fn handshake_data(&self) -> Option<Box<dyn Any>>;
38
39    /// Get the peer's identity, if available
40    fn peer_identity(&self) -> Option<Box<dyn Any>>;
41
42    /// Get the 0-RTT keys if available (clients only)
43    ///
44    /// On the client side, this method can be used to see if 0-RTT key material is available
45    /// to start sending data before the protocol handshake has completed.
46    ///
47    /// Returns `None` if the key material is not available. This might happen if you have
48    /// not connected to this server before.
49    fn early_crypto(&self) -> Option<(Box<dyn HeaderKey>, Box<dyn PacketKey>)>;
50
51    /// If the 0-RTT-encrypted data has been accepted by the peer
52    fn early_data_accepted(&self) -> Option<bool>;
53
54    /// Returns `true` until the connection is fully established.
55    fn is_handshaking(&self) -> bool;
56
57    /// Read bytes of handshake data
58    ///
59    /// This should be called with the contents of `CRYPTO` frames. If it returns `Ok`, the
60    /// caller should call `write_handshake()` to check if the crypto protocol has anything
61    /// to send to the peer. This method will only return `true` the first time that
62    /// handshake data is available. Future calls will always return false.
63    ///
64    /// On success, returns `true` iff `self.handshake_data()` has been populated.
65    fn read_handshake(&mut self, buf: &[u8]) -> Result<bool, TransportError>;
66
67    /// The peer's QUIC transport parameters
68    ///
69    /// These are only available after the first flight from the peer has been received.
70    fn transport_parameters(&self) -> Result<Option<TransportParameters>, TransportError>;
71
72    /// Writes handshake bytes into the given buffer and optionally returns the negotiated keys
73    ///
74    /// When the handshake proceeds to the next phase, this method will return a new set of
75    /// keys to encrypt data with.
76    fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<Keys>;
77
78    /// Compute keys for the next key update
79    fn next_1rtt_keys(&mut self) -> Option<KeyPair<Box<dyn PacketKey>>>;
80
81    /// Verify the integrity of a retry packet
82    ///
83    /// See the specification: <https://www.rfc-editor.org/rfc/rfc9001#name-retry-packet-integrity>
84    fn is_valid_retry(&self, orig_dst_cid: ConnectionId, header: &[u8], payload: &[u8]) -> bool;
85
86    /// Fill `output` with `output.len()` bytes of keying material derived
87    /// from the [Session]'s secrets, using `label` and `context` for domain
88    /// separation.
89    ///
90    /// This function will fail, returning [ExportKeyingMaterialError],
91    /// if the requested output length is too large.
92    fn export_keying_material(
93        &self,
94        output: &mut [u8],
95        label: &[u8],
96        context: &[u8],
97    ) -> Result<(), ExportKeyingMaterialError>;
98}
99
100/// A pair of keys for bidirectional communication
101pub struct KeyPair<T> {
102    /// Key for encrypting data
103    pub local: T,
104    /// Key for decrypting data
105    pub remote: T,
106}
107
108/// A complete set of keys for a certain packet space
109pub struct Keys {
110    /// Header protection keys
111    pub header: KeyPair<Box<dyn HeaderKey>>,
112    /// Packet protection keys
113    pub packet: KeyPair<Box<dyn PacketKey>>,
114}
115
116impl Keys {
117    pub(crate) fn local(&self) -> (&dyn HeaderKey, &dyn PacketKey) {
118        (self.header.local.as_ref(), self.packet.local.as_ref())
119    }
120
121    pub(crate) fn remote(&self) -> (&dyn HeaderKey, &dyn PacketKey) {
122        (self.header.remote.as_ref(), self.packet.remote.as_ref())
123    }
124}
125
126/// Client-side configuration for the crypto protocol
127pub trait ClientConfig: Send + Sync {
128    /// Start a client session with this configuration
129    fn start_session(
130        self: Arc<Self>,
131        version: u32,
132        server_name: &str,
133        params: &TransportParameters,
134    ) -> Result<Box<dyn Session>, ConnectError>;
135}
136
137/// Server-side configuration for the crypto protocol
138pub trait ServerConfig: Send + Sync {
139    /// Create the initial set of keys given the client's initial destination ConnectionId
140    fn initial_keys(&self, version: u32, dst_cid: ConnectionId)
141    -> Result<Keys, UnsupportedVersion>;
142
143    /// Generate the integrity tag for a retry packet
144    ///
145    /// Never called if `initial_keys` rejected `version`.
146    ///
147    /// See the specification: <https://www.rfc-editor.org/rfc/rfc9001#name-retry-packet-integrity>
148    fn retry_tag(&self, version: u32, orig_dst_cid: ConnectionId, packet: &[u8]) -> [u8; 16];
149
150    /// Start a server session with this configuration
151    ///
152    /// Never called if `initial_keys` rejected `version`.
153    fn start_session(
154        self: Arc<Self>,
155        version: u32,
156        params: &TransportParameters,
157    ) -> Box<dyn Session>;
158}
159
160/// Keys used to protect packet payloads
161pub trait PacketKey: Send + Sync {
162    /// Encrypt the packet payload with the given packet number
163    fn encrypt(&self, path_id: PathId, packet: u64, buf: &mut [u8], header_len: usize);
164    /// Decrypt the packet payload with the given packet number
165    fn decrypt(
166        &self,
167        path_id: PathId,
168        packet: u64,
169        header: &[u8],
170        payload: &mut BytesMut,
171    ) -> Result<(), CryptoError>;
172    /// The length of the AEAD tag appended to packets on encryption
173    fn tag_len(&self) -> usize;
174    /// Maximum number of packets that may be sent using a single key
175    fn confidentiality_limit(&self) -> u64;
176    /// Maximum number of incoming packets that may fail decryption before the connection must be
177    /// abandoned
178    fn integrity_limit(&self) -> u64;
179}
180
181/// Keys used to protect packet headers
182pub trait HeaderKey: Send + Sync {
183    /// Decrypt the given packet's header
184    fn decrypt(&self, pn_offset: usize, packet: &mut [u8]);
185    /// Encrypt the given packet's header
186    fn encrypt(&self, pn_offset: usize, packet: &mut [u8]);
187    /// The sample size used for this key's algorithm
188    fn sample_size(&self) -> usize;
189}
190
191/// A key for signing with HMAC-based algorithms
192pub trait HmacKey: Send + Sync {
193    /// Method for signing a message
194    fn sign(&self, data: &[u8], signature_out: &mut [u8]);
195    /// Length of `sign`'s output
196    fn signature_len(&self) -> usize;
197    /// Method for verifying a message
198    fn verify(&self, data: &[u8], signature: &[u8]) -> Result<(), CryptoError>;
199}
200
201/// Error returned by [Session::export_keying_material].
202///
203/// This error occurs if the requested output length is too large.
204#[derive(Debug, PartialEq, Eq)]
205pub struct ExportKeyingMaterialError;
206
207/// The trait for encrypting tokens that power retry packets and NEW_TOKEN frames.
208pub trait HandshakeTokenKey: Send + Sync {
209    /// Method for sealing a token in-place.
210    ///
211    /// The nonce doesn't need to be attached to the ciphertext,
212    /// but the authentication tag is expected to be appended to `data`.
213    fn seal(&self, token_nonce: u128, data: &mut Vec<u8>) -> Result<(), CryptoError>;
214    /// Method for opening a sealed message `data` in-place.
215    ///
216    /// Returns the portion of `data` that contains the decrypted plaintext.
217    fn open<'a>(&self, token_nonce: u128, data: &'a mut [u8]) -> Result<&'a [u8], CryptoError>;
218}
219
220/// Generic crypto errors
221#[derive(Debug)]
222pub struct CryptoError;
223
224/// Error indicating that the specified QUIC version is not supported
225#[derive(Debug)]
226pub struct UnsupportedVersion;
227
228impl From<UnsupportedVersion> for ConnectError {
229    fn from(_: UnsupportedVersion) -> Self {
230        Self::UnsupportedVersion
231    }
232}