302 lines
10 KiB
Rust
302 lines
10 KiB
Rust
#![allow(unused)]
|
|
mod aead;
|
|
mod alert;
|
|
mod alpn;
|
|
mod bffi_ext;
|
|
mod client;
|
|
mod error;
|
|
mod handshake_token;
|
|
mod hkdf;
|
|
mod hmac;
|
|
mod key;
|
|
mod macros;
|
|
mod retry;
|
|
mod secret;
|
|
mod server;
|
|
mod session_cache;
|
|
mod session_state;
|
|
mod suite;
|
|
mod version;
|
|
|
|
// Export the public interface.
|
|
pub use bffi_ext::*;
|
|
pub use client::Config as ClientConfig;
|
|
pub use error::{Error, Result};
|
|
pub use handshake_token::HandshakeTokenKey;
|
|
pub use hmac::HmacKey;
|
|
pub use server::Config as ServerConfig;
|
|
pub use session_cache::*;
|
|
pub use version::QuicVersion;
|
|
|
|
/// Information available from [quinn_proto::crypto::Session::handshake_data] once the handshake has completed.
|
|
#[derive(Clone, Debug)]
|
|
pub struct HandshakeData {
|
|
/// The negotiated application protocol, if ALPN is in use
|
|
///
|
|
/// Guaranteed to be set if a nonempty list of protocols was specified for this connection.
|
|
pub protocol: Option<Vec<u8>>,
|
|
|
|
/// The server name specified by the client, if any
|
|
///
|
|
/// Always `None` for outgoing connections
|
|
pub server_name: Option<String>,
|
|
}
|
|
|
|
pub mod helpers {
|
|
use super::*;
|
|
use quinn_proto::crypto;
|
|
use std::sync::Arc;
|
|
|
|
/// Create a server config with the given [`crypto::ServerConfig`]
|
|
///
|
|
/// Uses a randomized handshake token key.
|
|
pub fn server_config(crypto: Arc<dyn crypto::ServerConfig>) -> Result<quinn::ServerConfig> {
|
|
Ok(quinn::ServerConfig::new(
|
|
crypto,
|
|
Arc::new(HandshakeTokenKey::new()?),
|
|
))
|
|
}
|
|
|
|
/// Returns a default endpoint configuration for BoringSSL.
|
|
pub fn default_endpoint_config() -> quinn::EndpointConfig {
|
|
let mut cfg = quinn::EndpointConfig::new(Arc::new(HmacKey::sha256()));
|
|
cfg.supported_versions(QuicVersion::default_supported_versions());
|
|
cfg
|
|
}
|
|
|
|
/// Helper to construct an endpoint for use with outgoing connections only
|
|
///
|
|
/// Note that `addr` is the *local* address to bind to, which should usually be a wildcard
|
|
/// address like `0.0.0.0:0` or `[::]:0`, which allow communication with any reachable IPv4 or
|
|
/// IPv6 address respectively from an OS-assigned port.
|
|
///
|
|
/// Platform defaults for dual-stack sockets vary. For example, any socket bound to a wildcard
|
|
/// IPv6 address on Windows will not by default be able to communicate with IPv4
|
|
/// addresses. Portable applications should bind an address that matches the family they wish to
|
|
/// communicate within.
|
|
#[cfg(feature = "runtime-tokio")]
|
|
pub fn client_endpoint(addr: std::net::SocketAddr) -> std::io::Result<quinn::Endpoint> {
|
|
let socket = std::net::UdpSocket::bind(addr)?;
|
|
quinn::Endpoint::new(
|
|
default_endpoint_config(),
|
|
None,
|
|
socket,
|
|
Arc::new(quinn::TokioRuntime),
|
|
)
|
|
}
|
|
|
|
/// Helper to construct an endpoint for use with both incoming and outgoing connections
|
|
///
|
|
/// Platform defaults for dual-stack sockets vary. For example, any socket bound to a wildcard
|
|
/// IPv6 address on Windows will not by default be able to communicate with IPv4
|
|
/// addresses. Portable applications should bind an address that matches the family they wish to
|
|
/// communicate within.
|
|
#[cfg(feature = "runtime-tokio")]
|
|
pub fn server_endpoint(
|
|
config: quinn::ServerConfig,
|
|
addr: std::net::SocketAddr,
|
|
) -> std::io::Result<quinn::Endpoint> {
|
|
let socket = std::net::UdpSocket::bind(addr)?;
|
|
quinn::Endpoint::new(
|
|
default_endpoint_config(),
|
|
Some(config),
|
|
socket,
|
|
Arc::new(quinn::TokioRuntime),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
use crate::error::Result;
|
|
use crate::secret::{Secret, Secrets};
|
|
use crate::suite::CipherSuite;
|
|
use bytes::BytesMut;
|
|
use hex_literal::hex;
|
|
use quinn_proto::crypto::PacketKey;
|
|
use quinn_proto::{ConnectionId, Side};
|
|
|
|
/// Copied from quiche.
|
|
#[test]
|
|
fn test_initial_keys_v1() -> Result<()> {
|
|
let dcid: &[u8] = &hex!("8394c8f03e515708");
|
|
let version = QuicVersion::V1;
|
|
let suite = CipherSuite::aes128_gcm_sha256();
|
|
|
|
let s = Secrets::initial(version, &ConnectionId::new(dcid), Side::Client)?;
|
|
|
|
let expected_enc_key: &[u8] = &hex!("1f369613dd76d5467730efcbe3b1a22d");
|
|
assert_eq!(
|
|
s.local.packet_key(version, suite)?.key().slice(),
|
|
expected_enc_key
|
|
);
|
|
let expected_enc_iv: &[u8] = &hex!("fa044b2f42a3fd3b46fb255c");
|
|
assert_eq!(
|
|
s.local.packet_key(version, suite)?.iv().slice(),
|
|
expected_enc_iv
|
|
);
|
|
let expected_enc_hdr_key: &[u8] = &hex!("9f50449e04a0e810283a1e9933adedd2");
|
|
assert_eq!(
|
|
s.local.header_key(version, suite)?.key().slice(),
|
|
expected_enc_hdr_key
|
|
);
|
|
let expected_dec_key: &[u8] = &hex!("cf3a5331653c364c88f0f379b6067e37");
|
|
assert_eq!(
|
|
s.remote.packet_key(version, suite)?.key().slice(),
|
|
expected_dec_key
|
|
);
|
|
let expected_dec_iv: &[u8] = &hex!("0ac1493ca1905853b0bba03e");
|
|
assert_eq!(
|
|
s.remote.packet_key(version, suite)?.iv().slice(),
|
|
expected_dec_iv
|
|
);
|
|
let expected_dec_hdr_key: &[u8] = &hex!("c206b8d9b9f0f37644430b490eeaa314");
|
|
assert_eq!(
|
|
s.remote.header_key(version, suite)?.key().slice(),
|
|
expected_dec_hdr_key
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Copied from rustls.
|
|
#[test]
|
|
fn short_packet_header_protection() {
|
|
// https://www.rfc-editor.org/rfc/rfc9001.html#name-chacha20-poly1305-short-hea
|
|
|
|
const PN: u64 = 654360564;
|
|
const SECRET: &[u8] =
|
|
&hex!("9ac312a7f877468ebe69422748ad00a15443f18203a07d6060f688f30f21632b");
|
|
|
|
let version = QuicVersion::V1;
|
|
let suite = CipherSuite::chacha20_poly1305_sha256();
|
|
|
|
let secret = Secret::from(SECRET);
|
|
let hpk = secret
|
|
.header_key(version, suite)
|
|
.unwrap()
|
|
.as_crypto()
|
|
.unwrap();
|
|
let packet = secret.packet_key(version, suite).unwrap();
|
|
|
|
const PLAIN: &[u8] = &[0x42, 0x00, 0xbf, 0xf4, b'h', b'e', b'l', b'l', b'o'];
|
|
|
|
let mut buf = PLAIN.to_vec();
|
|
// Make space for the output tag.
|
|
buf.extend_from_slice(&[0u8; 16]);
|
|
packet.encrypt(PN, &mut buf, 4);
|
|
|
|
let pn_offset = 1;
|
|
hpk.encrypt(pn_offset, &mut buf);
|
|
|
|
const PROTECTED: &[u8] = &hex!("593b46220c4d504a9f1857793356400fc4a784ee309dff98b2");
|
|
|
|
assert_eq!(&buf, PROTECTED);
|
|
|
|
hpk.decrypt(pn_offset, &mut buf);
|
|
|
|
let (header, payload_tag) = buf.split_at(4);
|
|
let mut payload_tag = BytesMut::from(payload_tag);
|
|
packet.decrypt(PN, header, &mut payload_tag).unwrap();
|
|
let plain = payload_tag.as_ref();
|
|
assert_eq!(plain, &PLAIN[4..]);
|
|
}
|
|
|
|
/// Copied from rustls.
|
|
#[test]
|
|
fn key_update_test_vector() {
|
|
let version = QuicVersion::V1;
|
|
let suite = CipherSuite::aes128_gcm_sha256();
|
|
let mut secrets = Secrets {
|
|
version,
|
|
suite,
|
|
local: Secret::from(&hex!(
|
|
"b8767708f8772358a6ea9fc43e4add2c961b3f5287a6d1467ee0aeab33724dbf"
|
|
)),
|
|
remote: Secret::from(&hex!(
|
|
"42dc972140e0f2e39845b767613439dc6758ca43259b878506824eb1e438d855"
|
|
)),
|
|
};
|
|
secrets.update().unwrap();
|
|
|
|
let expected = Secrets {
|
|
version,
|
|
suite,
|
|
local: Secret::from(&hex!(
|
|
"42cac8c91cd5eb40682e432edf2d2be9f41a52ca6b22d8e6cdb1e8aca9061fce"
|
|
)),
|
|
remote: Secret::from(&hex!(
|
|
"eb7f5e2a123f407db499e361cae590d4d992e14b7ace03c244e0422115b6d38a"
|
|
)),
|
|
};
|
|
|
|
assert_eq!(expected, secrets);
|
|
}
|
|
|
|
#[test]
|
|
fn client_encrypt_header() {
|
|
let dcid = ConnectionId::new(&hex!("06b858ec6f80452b"));
|
|
|
|
let secrets = Secrets::initial(QuicVersion::V1, &dcid, Side::Client).unwrap();
|
|
let client = secrets.keys().unwrap().as_crypto().unwrap();
|
|
|
|
// Client (encrypt)
|
|
let mut packet: [u8; 51] = hex!(
|
|
"c0000000010806b858ec6f80452b0000402100c8fb7ffd97230e38b70d86e7ff148afdf88fc21c4426c7d1cec79914c8785757"
|
|
);
|
|
let packet_number = 0;
|
|
let packet_number_pos = 18;
|
|
let header_len = 19;
|
|
|
|
// Encrypt the payload.
|
|
client
|
|
.packet
|
|
.local
|
|
.encrypt(packet_number, &mut packet, header_len);
|
|
let expected_after_packet_encrypt: [u8; 51] = hex!(
|
|
"c0000000010806b858ec6f80452b0000402100f60e77fa2f629f9921fae64125c5632cf769d801a4693af6b949af37c2c45399"
|
|
);
|
|
assert_eq!(packet, expected_after_packet_encrypt);
|
|
|
|
// Encrypt the header.
|
|
client.header.local.encrypt(packet_number_pos, &mut packet);
|
|
let expected_after_header_encrypt: [u8; 51] = hex!(
|
|
"cd000000010806b858ec6f80452b000040210bf60e77fa2f629f9921fae64125c5632cf769d801a4693af6b949af37c2c45399"
|
|
);
|
|
assert_eq!(packet, expected_after_header_encrypt);
|
|
}
|
|
|
|
#[test]
|
|
fn server_decrypt_header() {
|
|
let dcid = ConnectionId::new(&hex!("06b858ec6f80452b"));
|
|
let secrets = Secrets::initial(QuicVersion::V1, &dcid, Side::Server).unwrap();
|
|
let server = secrets.keys().unwrap().as_crypto().unwrap();
|
|
|
|
let mut packet = BytesMut::from(
|
|
&hex!(
|
|
"c8000000010806b858ec6f80452b00004021be3ef50807b84191a196f760a6dad1e9d1c430c48952cba0148250c21c0a6a70e1"
|
|
)[..],
|
|
);
|
|
let packet_number = 0;
|
|
let packet_number_pos = 18;
|
|
let header_len = 19;
|
|
|
|
// Decrypt the header.
|
|
server.header.remote.decrypt(packet_number_pos, &mut packet);
|
|
let expected_header: [u8; 19] = hex!("c0000000010806b858ec6f80452b0000402100");
|
|
assert_eq!(packet[..header_len], expected_header);
|
|
|
|
// Decrypt the payload.
|
|
let mut header = packet;
|
|
let mut packet = header.split_off(header_len);
|
|
server
|
|
.packet
|
|
.remote
|
|
.decrypt(packet_number, &header, &mut packet)
|
|
.unwrap();
|
|
assert_eq!(packet[..], [0; 16]);
|
|
}
|
|
}
|