quinn-boring 0.1.0

BoringSSL crypto provider for quinn
Documentation
mod aead;
mod alert;
mod alpn;
mod bffi_ext;
mod client;
mod error;
mod handshake_token;
mod hkdf;
mod hmac;
mod key;
mod key_log;
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 key_log::*;
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::TokioRuntime;
    use quinn_proto::crypto;
    use std::io;
    use std::net::SocketAddr;
    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.
    pub fn client_endpoint(addr: SocketAddr) -> io::Result<quinn::Endpoint> {
        let socket = std::net::UdpSocket::bind(addr)?;
        quinn::Endpoint::new(default_endpoint_config(), None, socket, 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.
    pub fn server_endpoint(
        config: quinn::ServerConfig,
        addr: SocketAddr,
    ) -> io::Result<quinn::Endpoint> {
        let socket = std::net::UdpSocket::bind(addr)?;
        quinn::Endpoint::new(
            default_endpoint_config(),
            Some(config),
            socket,
            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]);
    }
}