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