quinn-boring 0.1.0

BoringSSL crypto provider for quinn
Documentation
use crate::alpn::AlpnProtocols;
use crate::bffi_ext::QuicSsl;
use crate::error::{map_result, Result};
use crate::secret::Secrets;
use crate::session_state::{SessionState, QUIC_METHOD};
use crate::version::QuicVersion;
use crate::{retry, KeyLog, NoKeyLog, QuicSslContext};
use boring::ssl::{Ssl, SslContext, SslContextBuilder, SslMethod, SslVersion};
use boring_sys as bffi;
use bytes::{Bytes, BytesMut};
use foreign_types_shared::ForeignType;
use once_cell::sync::Lazy;
use quinn_proto::{
    crypto, transport_parameters::TransportParameters, ConnectionId, Side, TransportError,
};
use std::any::Any;
use std::ffi::{c_int, c_uint, c_void};
use std::result::Result as StdResult;
use std::slice;
use std::sync::Arc;

/// Configuration for a server-side QUIC. Wraps around a BoringSSL [SslContext].
pub struct Config {
    ctx: SslContext,
    alpn_protocols: AlpnProtocols,
    key_log: Option<Arc<dyn KeyLog>>,
}

impl Config {
    pub fn new() -> Result<Self> {
        let mut builder = SslContextBuilder::new(SslMethod::tls())?;

        // QUIC requires TLS 1.3.
        builder.set_min_proto_version(Some(SslVersion::TLS1_3))?;
        builder.set_max_proto_version(Some(SslVersion::TLS1_3))?;

        builder.set_default_verify_paths()?;

        // We build the context early, since we are not allowed to further mutate the context
        // in start_session.
        let mut ctx = builder.build();

        // Disable verification of the client by default.
        ctx.verify_peer(false);

        // By default, enable early data (used for 0-RTT).
        ctx.enable_early_data(true);

        // Configure default ALPN protocols accepted by the server.QUIC requires ALPN be
        // configured (see https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1).
        ctx.set_alpn_select_cb(Some(Session::alpn_select_callback));

        // Set the callback for receipt of the Server Name Indication (SNI) extension.
        ctx.set_server_name_cb(Some(Session::server_name_callback));

        // Set callbacks for the SessionState.
        ctx.set_quic_method(&QUIC_METHOD)?;
        ctx.set_info_callback(Some(SessionState::info_callback));
        ctx.set_keylog_callback(Some(SessionState::keylog_callback));

        ctx.set_options(bffi::SSL_OP_CIPHER_SERVER_PREFERENCE as u32);

        Ok(Self {
            ctx,
            alpn_protocols: AlpnProtocols::default(),
            key_log: None,
        })
    }

    /// Returns the underlying [SslContext] backing all created sessions.
    pub fn ctx(&self) -> &SslContext {
        &self.ctx
    }

    /// Returns the underlying [SslContext] backing all created sessions. Wherever possible use
    /// the provided methods to modify settings rather than accessing this directly.
    ///
    /// Care should be taken to avoid overriding required behavior. In particular, this
    /// configuration will set callbacks for QUIC events, alpn selection, server name,
    /// as well as info and key logging.
    pub fn ctx_mut(&mut self) -> &mut SslContext {
        &mut self.ctx
    }

    /// Sets whether or not the peer certificate should be verified. If `true`, any error
    /// during verification will be fatal. If not called, verification of the client is
    /// disabled by default.
    pub fn verify_peer(&mut self, verify: bool) {
        self.ctx.verify_peer(verify)
    }

    /// Sets the ALPN protocols that will be accepted by the server. QUIC requires that
    /// ALPN be used (see <https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1>).
    ///
    /// If this method is not called, the server will default to accepting "h3".
    pub fn set_alpn(&mut self, alpn_protocols: &[Vec<u8>]) -> Result<()> {
        self.alpn_protocols = alpn_protocols.into();
        Ok(())
    }

    /// Sets the key logger.
    pub fn set_key_log(&mut self, key_log: Option<Arc<dyn KeyLog>>) {
        self.key_log = key_log;
    }
}

impl crypto::ServerConfig for Config {
    fn initial_keys(
        &self,
        version: u32,
        dcid: &ConnectionId,
        side: Side,
    ) -> StdResult<crypto::Keys, crypto::UnsupportedVersion> {
        let version = QuicVersion::parse(version)?;
        let secrets = Secrets::initial(version, dcid, side).unwrap();
        Ok(secrets.keys().unwrap().as_crypto().unwrap())
    }

    fn retry_tag(&self, version: u32, orig_dst_cid: &ConnectionId, packet: &[u8]) -> [u8; 16] {
        let version = QuicVersion::parse(version).unwrap();
        retry::retry_tag(&version, orig_dst_cid, packet)
    }

    fn start_session(
        self: Arc<Self>,
        version: u32,
        params: &TransportParameters,
    ) -> Box<dyn crypto::Session> {
        let version = QuicVersion::parse(version).unwrap();
        Session::new(self, version, params).unwrap()
    }
}

static SESSION_INDEX: Lazy<c_int> = Lazy::new(|| unsafe {
    bffi::SSL_get_ex_new_index(0, std::ptr::null_mut(), std::ptr::null_mut(), None, None)
});

/// The [crypto::Session] implementation for BoringSSL.
struct Session {
    state: Box<SessionState>,
    alpn: AlpnProtocols,
    handshake_data_available: bool,
    handshake_data_sent: bool,
}

impl Session {
    fn new(
        cfg: Arc<Config>,
        version: QuicVersion,
        params: &TransportParameters,
    ) -> Result<Box<Self>> {
        let mut ssl = Ssl::new(&cfg.ctx).unwrap();

        // Configure the TLS extension based on the QUIC version used.
        ssl.set_quic_use_legacy_codepoint(version.uses_legacy_extension());

        // Configure the SSL to be a server.
        ssl.set_accept_state();

        // Set the transport parameters.
        ssl.set_quic_transport_params(&encode_params(params))
            .unwrap();

        // Need to se
        ssl.set_quic_early_data_context(b"quinn-boring").unwrap();

        let mut session = Box::new(Self {
            state: SessionState::new(
                ssl,
                Side::Server,
                version,
                cfg.key_log
                    .as_ref()
                    .map_or(Arc::new(NoKeyLog), |key_log| key_log.clone()),
            )?,
            alpn: cfg.alpn_protocols.clone(),
            handshake_data_available: false,
            handshake_data_sent: false,
        });

        // Register the instance in SSL ex_data. This allows the static callbacks to
        // reference the instance.
        unsafe {
            map_result(bffi::SSL_set_ex_data(
                session.state.ssl.as_ptr(),
                *SESSION_INDEX,
                &mut *session as *mut Self as *mut _,
            ))?;
        }

        Ok(session)
    }

    /// Server-side only callback from BoringSSL to select the ALPN protocol.
    #[inline]
    fn on_alpn_select<'a>(&mut self, offered: &'a [u8]) -> Result<&'a [u8]> {
        // Indicate that we now have handshake data available.
        self.handshake_data_available = true;

        self.alpn.select(offered)
    }

    /// Server-side only callback from BoringSSL indicating that the Server Name Indication (SNI)
    /// extension in the client hello was successfully parsed.
    #[inline]
    fn on_server_name(&mut self, _: *mut c_int) -> c_int {
        // Indicate that we now have handshake data available.
        self.handshake_data_available = true;

        // SSL_TLSEXT_ERR_OK causes the server_name extension to be acked in
        // ServerHello.
        bffi::SSL_TLSEXT_ERR_OK
    }
}

// Raw callbacks from BoringSSL
impl Session {
    #[inline]
    fn get_instance(ssl: *const bffi::SSL) -> &'static mut Session {
        unsafe {
            let data = bffi::SSL_get_ex_data(ssl, *SESSION_INDEX);
            if data.is_null() {
                panic!("BUG: Session instance missing")
            }
            &mut *(data as *mut Session)
        }
    }

    extern "C" fn alpn_select_callback(
        ssl: *mut bffi::SSL,
        out: *mut *const u8,
        out_len: *mut u8,
        in_: *const u8,
        in_len: c_uint,
        _: *mut c_void,
    ) -> c_int {
        let inst = Self::get_instance(ssl);

        unsafe {
            let protos = slice::from_raw_parts(in_, in_len as _);
            match inst.on_alpn_select(protos) {
                Ok(proto) => {
                    *out = proto.as_ptr() as _;
                    *out_len = proto.len() as _;
                    bffi::SSL_TLSEXT_ERR_OK
                }
                Err(_) => bffi::SSL_TLSEXT_ERR_ALERT_FATAL,
            }
        }
    }

    extern "C" fn server_name_callback(
        ssl: *mut bffi::SSL,
        out_alert: *mut c_int,
        _: *mut c_void,
    ) -> c_int {
        let inst = Self::get_instance(ssl);
        inst.on_server_name(out_alert)
    }
}

impl crypto::Session for Session {
    fn initial_keys(&self, dcid: &ConnectionId, side: Side) -> crypto::Keys {
        self.state.initial_keys(dcid, side)
    }

    fn handshake_data(&self) -> Option<Box<dyn Any>> {
        self.state.handshake_data()
    }

    fn peer_identity(&self) -> Option<Box<dyn Any>> {
        self.state.peer_identity()
    }

    fn early_crypto(&self) -> Option<(Box<dyn crypto::HeaderKey>, Box<dyn crypto::PacketKey>)> {
        self.state.early_crypto()
    }

    fn early_data_accepted(&self) -> Option<bool> {
        None
    }

    fn is_handshaking(&self) -> bool {
        self.state.is_handshaking()
    }

    fn read_handshake(&mut self, plaintext: &[u8]) -> StdResult<bool, TransportError> {
        self.state.read_handshake(plaintext)?;

        // Only indicate that handshake data is available once.
        if !self.handshake_data_sent && self.handshake_data_available {
            self.handshake_data_sent = true;
            return Ok(true);
        }

        Ok(false)
    }

    fn transport_parameters(&self) -> StdResult<Option<TransportParameters>, TransportError> {
        self.state.transport_parameters()
    }

    fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<crypto::Keys> {
        self.state.write_handshake(buf)
    }

    fn next_1rtt_keys(&mut self) -> Option<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
        self.state.next_1rtt_keys()
    }

    fn is_valid_retry(&self, orig_dst_cid: &ConnectionId, header: &[u8], payload: &[u8]) -> bool {
        self.state.is_valid_retry(orig_dst_cid, header, payload)
    }

    fn export_keying_material(
        &self,
        output: &mut [u8],
        label: &[u8],
        context: &[u8],
    ) -> StdResult<(), crypto::ExportKeyingMaterialError> {
        self.state.export_keying_material(output, label, context)
    }
}

fn encode_params(params: &TransportParameters) -> Bytes {
    let mut out = BytesMut::with_capacity(128);
    params.write(&mut out);
    out.freeze()
}