From 9522b44600b0d3e5d4027736b2b93a78527e25bf Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Thu, 21 Nov 2019 09:26:54 -0800 Subject: [PATCH] Remove quinn-proto rustls config newtypes These were not successful at removing rustls from our public API, and made specifying custom cryptographic configurations unintuitive. Removing them reduces the scope of our public API while preserving the friendliness of the high-level quinn API. --- interop/src/main.rs | 2 +- quinn-h3/examples/h3.rs | 3 +- quinn-h3/examples/shared/mod.rs | 2 +- quinn-proto/src/crypto.rs | 14 +- quinn-proto/src/crypto/rustls.rs | 254 ++----------------------------- quinn-proto/src/endpoint.rs | 9 +- quinn-proto/src/lib.rs | 2 +- quinn-proto/src/shared.rs | 57 ++++++- quinn-proto/src/tests/util.rs | 25 ++- quinn/src/builders.rs | 23 +-- quinn/src/lib.rs | 7 +- quinn/src/tls.rs | 102 +++++++++++++ 12 files changed, 215 insertions(+), 285 deletions(-) create mode 100644 quinn/src/tls.rs diff --git a/interop/src/main.rs b/interop/src/main.rs index f7c782dbf..5c0dd4ff0 100644 --- a/interop/src/main.rs +++ b/interop/src/main.rs @@ -250,7 +250,7 @@ fn run(options: Opt) -> Result<()> { tls_config.key_log = Arc::new(rustls::KeyLogFile::new()); } let client_config = quinn::ClientConfig { - crypto: quinn::crypto::rustls::ClientConfig::new(tls_config), + crypto: Arc::new(tls_config), transport: Arc::new(quinn::TransportConfig { idle_timeout: 1_000, ..Default::default() diff --git a/quinn-h3/examples/h3.rs b/quinn-h3/examples/h3.rs index 979d1533d..373e0af78 100644 --- a/quinn-h3/examples/h3.rs +++ b/quinn-h3/examples/h3.rs @@ -12,7 +12,7 @@ use structopt::{self, StructOpt}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use url::Url; -use quinn::ConnectionDriver as QuicDriver; +use quinn::{Certificate, CertificateChain, ConnectionDriver as QuicDriver, PrivateKey}; use quinn_h3::{ self, body::RecvBody, @@ -20,7 +20,6 @@ use quinn_h3::{ connection::ConnectionDriver, server::{Builder as ServerBuilder, IncomingRequest, Sender}, }; -use quinn_proto::crypto::rustls::{Certificate, CertificateChain, PrivateKey}; mod shared; use shared::build_certs; diff --git a/quinn-h3/examples/shared/mod.rs b/quinn-h3/examples/shared/mod.rs index c97e1770c..b83c4b13d 100644 --- a/quinn-h3/examples/shared/mod.rs +++ b/quinn-h3/examples/shared/mod.rs @@ -1,7 +1,7 @@ use std::{fs, io, path::PathBuf}; use anyhow::{bail, Context, Result}; -use quinn_proto::crypto::rustls::{Certificate, CertificateChain, PrivateKey}; +use quinn::{Certificate, CertificateChain, PrivateKey}; use tracing::info; pub fn build_certs( diff --git a/quinn-proto/src/crypto.rs b/quinn-proto/src/crypto.rs index 3668a1414..9ee76c0f7 100644 --- a/quinn-proto/src/crypto.rs +++ b/quinn-proto/src/crypto.rs @@ -20,10 +20,10 @@ use crate::{ /// Cryptography interface based on *ring* #[cfg(feature = "ring")] -pub mod ring; +pub(crate) mod ring; /// TLS interface based on rustls #[cfg(feature = "rustls")] -pub mod rustls; +pub(crate) mod rustls; /// A cryptographic session (commonly TLS) pub trait Session: Sized { @@ -87,6 +87,11 @@ pub trait ClientConfig where S: Session, { + /// Construct the default configuration + fn new() -> Self + where + Self: Sized; + /// Start a client session with this configuration fn start_session( &self, @@ -100,6 +105,11 @@ pub trait ServerConfig where S: Session, { + /// Construct the default configuration + fn new() -> Self + where + Self: Sized; + /// Start a server session with this configuration fn start_session(&self, params: &TransportParameters) -> S; } diff --git a/quinn-proto/src/crypto/rustls.rs b/quinn-proto/src/crypto/rustls.rs index fced71446..d8ac4afed 100644 --- a/quinn-proto/src/crypto/rustls.rs +++ b/quinn-proto/src/crypto/rustls.rs @@ -1,5 +1,5 @@ use std::{ - fmt, io, + io, ops::{Deref, DerefMut}, str, sync::Arc, @@ -9,9 +9,9 @@ use ring::{hkdf, hmac}; pub use rustls::TLSError; use rustls::{ self, - internal::{msgs::enums::HashAlgorithm, pemfile}, + internal::msgs::enums::HashAlgorithm, quic::{ClientQuicExt, Secrets, ServerQuicExt}, - KeyLogFile, NoClientAuth, ProtocolVersion, Session, + Session, }; use webpki::DNSNameRef; @@ -39,10 +39,10 @@ impl TlsSession { } impl crypto::Session for TlsSession { - type ClientConfig = ClientConfig; + type ClientConfig = Arc; type HmacKey = hmac::Key; type Keys = Crypto; - type ServerConfig = ServerConfig; + type ServerConfig = Arc; fn alpn_protocol(&self) -> Option<&[u8]> { self.get_alpn_protocol() @@ -159,77 +159,14 @@ impl DerefMut for TlsSession { } } -/// rustls configuration for client sessions -#[derive(Clone)] -pub struct ClientConfig(Arc); - -impl ClientConfig { - /// Initialize new configuration with an existing rustls `ClientConfig` - pub fn new(config: rustls::ClientConfig) -> Self { - Self(Arc::new(config)) - } - - /// Add a trusted certificate authority. - /// - /// For more advanced/less secure certificate verification, construct a [`ClientConfig`] - /// manually and use rustls's `dangerous_configuration` feature to override the certificate - /// verifier. - pub fn add_certificate_authority(&mut self, cert: Certificate) -> Result<(), webpki::Error> { - let anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(&cert.inner.0)?; - Arc::make_mut(&mut self.0) - .root_store - .add_server_trust_anchors(&webpki::TLSServerTrustAnchors(&[anchor])); - Ok(()) - } - - /// Enable NSS-compatible cryptographic key logging to the `SSLKEYLOGFILE` environment variable - /// - /// Useful for debugging encrypted communications with protocol analyzers such as Wireshark. - pub fn enable_keylog(&mut self) { - Arc::make_mut(&mut self.0).key_log = Arc::new(KeyLogFile::new()); - } - - /// Set the application-layer protocols to accept, in order of descending preference - /// - /// When set, clients which don't declare support for at least one of the supplied protocols will be rejected. - /// - /// The IANA maintains a [registry] of standard protocol IDs, but custom IDs may be used as well. - /// - /// [registry]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub fn set_protocols(&mut self, protocols: &[&[u8]]) { - Arc::make_mut(&mut self.0).alpn_protocols = protocols.iter().map(|x| x.to_vec()).collect(); - } -} - -impl Default for ClientConfig { - fn default() -> ClientConfig { +impl crypto::ClientConfig for Arc { + fn new() -> Self { let mut cfg = rustls::ClientConfig::new(); - cfg.versions = vec![ProtocolVersion::TLSv1_3]; + cfg.versions = vec![rustls::ProtocolVersion::TLSv1_3]; cfg.enable_early_data = true; - Self(Arc::new(cfg)) - } -} - -impl fmt::Debug for ClientConfig { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(fmt, "ClientConfig(rustls::ClientConfig)") - } -} - -impl Deref for ClientConfig { - type Target = Arc; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for ClientConfig { - fn deref_mut(&mut self) -> &mut Arc { - &mut self.0 + Arc::new(cfg) } -} -impl crypto::ClientConfig for ClientConfig { fn start_session( &self, server_name: &str, @@ -238,170 +175,23 @@ impl crypto::ClientConfig for ClientConfig { let pki_server_name = DNSNameRef::try_from_ascii_str(server_name) .map_err(|_| ConnectError::InvalidDnsName(server_name.into()))?; Ok(TlsSession::Client(rustls::ClientSession::new_quic( - &self.0, + self, pki_server_name, to_vec(params), ))) } } -/// rustls configuration for server sessions -#[derive(Clone)] -pub struct ServerConfig(Arc); - -impl ServerConfig { - /// Initialize new configuration with an existing rustls `ServerConfig` - pub fn new(config: rustls::ServerConfig) -> Self { - Self(Arc::new(config)) - } - - /// Set the certificate chain that will be presented to clients - pub fn set_certificate( - &mut self, - cert_chain: CertificateChain, - key: PrivateKey, - ) -> Result<(), TLSError> { - Arc::make_mut(&mut self.0).set_single_cert(cert_chain.certs, key.inner)?; - Ok(()) - } - - /// Enable NSS-compatible cryptographic key logging to the `SSLKEYLOGFILE` environment variable - /// - /// Useful for debugging encrypted communications with protocol analyzers such as Wireshark. - pub fn enable_keylog(&mut self) { - Arc::make_mut(&mut self.0).key_log = Arc::new(KeyLogFile::new()); - } - - /// Set the application-layer protocols to accept, in order of descending preference - /// - /// When set, clients which don't declare support for at least one of the supplied protocols will be rejected. - /// - /// The IANA maintains a [registry] of standard protocol IDs, but custom IDs may be used as well. - /// - /// [registry]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids - pub fn set_protocols(&mut self, protocols: &[&[u8]]) { - Arc::make_mut(&mut self.0).alpn_protocols = protocols.iter().map(|x| x.to_vec()).collect(); - } -} - -impl fmt::Debug for ServerConfig { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(fmt, "ServerConfig(rustls::ServerConfig)") - } -} - -impl Default for ServerConfig { - fn default() -> Self { - let mut cfg = rustls::ServerConfig::new(NoClientAuth::new()); - cfg.versions = vec![ProtocolVersion::TLSv1_3]; +impl crypto::ServerConfig for Arc { + fn new() -> Self { + let mut cfg = rustls::ServerConfig::new(rustls::NoClientAuth::new()); + cfg.versions = vec![rustls::ProtocolVersion::TLSv1_3]; cfg.max_early_data_size = u32::max_value(); - Self(Arc::new(cfg)) + Arc::new(cfg) } -} -impl crypto::ServerConfig for ServerConfig { fn start_session(&self, params: &TransportParameters) -> TlsSession { - TlsSession::Server(rustls::ServerSession::new_quic(&self.0, to_vec(params))) - } -} - -impl Deref for ServerConfig { - type Target = Arc; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for ServerConfig { - fn deref_mut(&mut self) -> &mut Arc { - &mut self.0 - } -} - -/// A single TLS certificate -#[derive(Debug, Clone)] -pub struct Certificate { - inner: rustls::Certificate, -} - -impl Certificate { - /// Parse a DER-formatted certificate - pub fn from_der(der: &[u8]) -> Result { - Ok(Self { - inner: rustls::Certificate(der.to_vec()), - }) - } -} - -/// A chain of signed TLS certificates ending the one to be used by a server -#[derive(Debug, Clone)] -pub struct CertificateChain { - certs: Vec, -} - -impl CertificateChain { - /// Parse a PEM-formatted certificate chain - /// - /// ```no_run - /// let pem = std::fs::read("fullchain.pem").expect("error reading certificates"); - /// let cert_chain = quinn_proto::crypto::rustls::PrivateKey::from_pem(&pem).expect("error parsing certificates"); - /// ``` - pub fn from_pem(pem: &[u8]) -> Result { - Ok(Self { - certs: pemfile::certs(&mut &pem[..]) - .map_err(|()| ParseError("malformed certificate chain"))?, - }) - } - - /// Construct a certificate chain from a list of certificates - pub fn from_certs(certs: impl IntoIterator) -> Self { - certs.into_iter().collect() - } -} - -impl std::iter::FromIterator for CertificateChain { - fn from_iter(iter: T) -> Self - where - T: IntoIterator, - { - CertificateChain { - certs: iter.into_iter().map(|x| x.inner).collect(), - } - } -} - -/// The private key of a TLS certificate to be used by a server -#[derive(Debug, Clone)] -pub struct PrivateKey { - inner: rustls::PrivateKey, -} - -impl PrivateKey { - /// Parse a PEM-formatted private key - /// - /// ```no_run - /// let pem = std::fs::read("key.pem").expect("error reading key"); - /// let key = quinn_proto::crypto::rustls::PrivateKey::from_pem(&pem).expect("error parsing key"); - /// ``` - pub fn from_pem(pem: &[u8]) -> Result { - let pkcs8 = pemfile::pkcs8_private_keys(&mut &pem[..]) - .map_err(|()| ParseError("malformed PKCS #8 private key"))?; - if let Some(x) = pkcs8.into_iter().next() { - return Ok(Self { inner: x }); - } - let rsa = pemfile::rsa_private_keys(&mut &pem[..]) - .map_err(|()| ParseError("malformed PKCS #1 private key"))?; - if let Some(x) = rsa.into_iter().next() { - return Ok(Self { inner: x }); - } - Err(ParseError("no private key found")) - } - - /// Parse a DER-encoded (binary) private key - pub fn from_der(der: &[u8]) -> Result { - Ok(Self { - inner: rustls::PrivateKey(der.to_vec()), - }) + TlsSession::Server(rustls::ServerSession::new_quic(self, to_vec(params))) } } @@ -419,18 +209,6 @@ fn update_secrets(hash_alg: HashAlgorithm, client: &hkdf::Prk, server: &hkdf::Pr } } -/// Errors encountered while parsing a TLS certificate or private key -#[derive(Debug, Clone)] -pub struct ParseError(&'static str); - -impl std::error::Error for ParseError {} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.pad(self.0) - } -} - fn to_vec(params: &TransportParameters) -> Vec { let mut bytes = Vec::new(); params.write(&mut bytes); diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index fdfaac87e..bd604af17 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -349,7 +349,7 @@ where /// Initiate a connection pub fn connect( &mut self, - config: ClientConfig, + config: ClientConfig, remote: SocketAddr, server_name: &str, ) -> Result<(ConnectionHandle, Connection), ConnectError> { @@ -405,7 +405,7 @@ where init_cid: ConnectionId, rem_cid: ConnectionId, remote: SocketAddr, - opts: ConnectionOpts, + opts: ConnectionOpts, now: Instant, ) -> Result<(ConnectionHandle, Connection), ConnectError> { let loc_cid = self.new_cid(); @@ -669,7 +669,6 @@ where impl fmt::Debug for Endpoint where S: crypto::Session, - S::ServerConfig: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("Endpoint") @@ -832,9 +831,9 @@ where NewConnection(Connection), } -enum ConnectionOpts { +enum ConnectionOpts { Client { - config: ClientConfig, + config: ClientConfig, server_name: String, }, Server { diff --git a/quinn-proto/src/lib.rs b/quinn-proto/src/lib.rs index 2c2f12bc9..2b7fb508f 100644 --- a/quinn-proto/src/lib.rs +++ b/quinn-proto/src/lib.rs @@ -76,7 +76,7 @@ mod rustls_impls { /// A `Connection` using rustls for the cryptography protocol pub type Connection = generic::Connection; /// A `ClientConfig` containing client-side rustls configuration - pub type ClientConfig = generic::ClientConfig; + pub type ClientConfig = generic::ClientConfig; /// An `Endpoint` using rustls for the cryptography protocol pub type Endpoint = generic::Endpoint; /// A `ServerConfig` containing server-side rustls configuration diff --git a/quinn-proto/src/shared.rs b/quinn-proto/src/shared.rs index eeaf3dd35..17001fafe 100644 --- a/quinn-proto/src/shared.rs +++ b/quinn-proto/src/shared.rs @@ -5,7 +5,11 @@ use err_derive::Error; use rand::{Rng, RngCore}; use tracing::warn; -use crate::{crypto, packet::PartialDecode, VarInt, MAX_CID_SIZE, RESET_TOKEN_SIZE}; +use crate::{ + crypto::{self, ClientConfig as _, ServerConfig as _}, + packet::PartialDecode, + VarInt, MAX_CID_SIZE, RESET_TOKEN_SIZE, +}; /// Parameters governing the core QUIC state machine /// @@ -285,12 +289,11 @@ where impl fmt::Debug for ServerConfig where S: crypto::Session, - S::ServerConfig: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("ServerConfig") .field("transport", &self.transport) - .field("crypto", &self.crypto) + .field("crypto", &"ServerConfig { elided }") .field("token_key", &"[ elided ]") .field("use_stateless_retry", &self.use_stateless_retry) .field("retry_token_lifetime", &self.retry_token_lifetime) @@ -303,7 +306,6 @@ where impl Default for ServerConfig where S: crypto::Session, - S::ServerConfig: Default, { fn default() -> Self { let rng = &mut rand::thread_rng(); @@ -313,7 +315,7 @@ where Self { transport: Arc::new(TransportConfig::default()), - crypto: S::ServerConfig::default(), + crypto: S::ServerConfig::new(), token_key, use_stateless_retry: false, @@ -347,13 +349,52 @@ where /// Configuration for outgoing connections /// /// Default values should be suitable for most internet applications. -#[derive(Clone, Debug, Default)] -pub struct ClientConfig { +pub struct ClientConfig +where + S: crypto::Session, +{ /// Transport configuration to use pub transport: Arc, /// Cryptographic configuration to use - pub crypto: C, + pub crypto: S::ClientConfig, +} + +impl Default for ClientConfig +where + S: crypto::Session, +{ + fn default() -> Self { + Self { + transport: Default::default(), + crypto: S::ClientConfig::new(), + } + } +} + +impl Clone for ClientConfig +where + S: crypto::Session, + S::ClientConfig: Clone, +{ + fn clone(&self) -> Self { + Self { + transport: self.transport.clone(), + crypto: self.crypto.clone(), + } + } +} + +impl fmt::Debug for ClientConfig +where + S: crypto::Session, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("ClientConfig") + .field("transport", &self.transport) + .field("crypto", &"ClientConfig { elided }") + .finish() + } } /// Errors in the configuration of an endpoint diff --git a/quinn-proto/src/tests/util.rs b/quinn-proto/src/tests/util.rs index bed28b8b8..2421df452 100644 --- a/quinn-proto/src/tests/util.rs +++ b/quinn-proto/src/tests/util.rs @@ -16,10 +16,7 @@ use rustls::KeyLogFile; use tracing::{info_span, trace}; use super::*; -use crate::{ - crypto::rustls::{CertificateChain, PrivateKey}, - timer::TimerKind, -}; +use crate::timer::TimerKind; pub struct Pair { pub server: TestEndpoint, @@ -362,11 +359,11 @@ pub fn server_config() -> ServerConfig { let key = CERTIFICATE.serialize_private_key_der(); let cert = CERTIFICATE.serialize_pem().unwrap(); - let mut crypto = crypto::rustls::ServerConfig::default(); - crypto - .set_certificate( - CertificateChain::from_pem(cert.as_bytes()).unwrap(), - PrivateKey::from_der(&key).unwrap(), + let mut crypto = crypto::ServerConfig::new(); + Arc::make_mut(&mut crypto) + .set_single_cert( + rustls::internal::pemfile::certs(&mut cert.as_bytes()).unwrap(), + rustls::PrivateKey(key.to_vec()), ) .unwrap(); ServerConfig { @@ -380,15 +377,15 @@ pub fn client_config() -> ClientConfig { let anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(&cert).unwrap(); let anchor_vec = vec![anchor]; - let mut tls_client_config = crypto::rustls::ClientConfig::default(); - Arc::make_mut(&mut tls_client_config) + let mut crypto = crypto::ClientConfig::new(); + Arc::make_mut(&mut crypto) .root_store .add_server_trust_anchors(&webpki::TLSServerTrustAnchors(&anchor_vec)); - Arc::make_mut(&mut tls_client_config).key_log = Arc::new(KeyLogFile::new()); - Arc::make_mut(&mut tls_client_config).enable_early_data = true; + Arc::make_mut(&mut crypto).key_log = Arc::new(KeyLogFile::new()); + Arc::make_mut(&mut crypto).enable_early_data = true; ClientConfig { transport: Default::default(), - crypto: tls_client_config, + crypto, ..Default::default() } } diff --git a/quinn/src/builders.rs b/quinn/src/builders.rs index f71085a06..56114da39 100644 --- a/quinn/src/builders.rs +++ b/quinn/src/builders.rs @@ -1,15 +1,13 @@ use std::{io, net::SocketAddr, str, sync::Arc}; use err_derive::Error; -use proto::{ - crypto::rustls::{Certificate, CertificateChain, PrivateKey}, - ClientConfig, EndpointConfig, ServerConfig, -}; +use proto::{ClientConfig, EndpointConfig, ServerConfig}; use rustls::TLSError; use crate::{ endpoint::{Endpoint, EndpointDriver, EndpointRef, Incoming}, udp::UdpSocket, + Certificate, CertificateChain, PrivateKey, }; /// A helper for constructing an `Endpoint`. @@ -132,7 +130,7 @@ impl ServerConfigBuilder { /// /// Useful for debugging encrypted communications with protocol analyzers such as Wireshark. pub fn enable_keylog(&mut self) -> &mut Self { - self.config.crypto.enable_keylog(); + Arc::make_mut(&mut self.config.crypto).key_log = Arc::new(rustls::KeyLogFile::new()); self } @@ -142,7 +140,7 @@ impl ServerConfigBuilder { cert_chain: CertificateChain, key: PrivateKey, ) -> Result<&mut Self, TLSError> { - self.config.crypto.set_certificate(cert_chain, key)?; + Arc::make_mut(&mut self.config.crypto).set_single_cert(cert_chain.certs, key.inner)?; Ok(self) } @@ -154,7 +152,8 @@ impl ServerConfigBuilder { /// /// [registry]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids pub fn protocols(&mut self, protocols: &[&[u8]]) -> &mut Self { - self.config.crypto.set_protocols(protocols); + Arc::make_mut(&mut self.config.crypto).alpn_protocols = + protocols.iter().map(|x| x.to_vec()).collect(); self } @@ -201,7 +200,10 @@ impl ClientConfigBuilder { &mut self, cert: Certificate, ) -> Result<&mut Self, webpki::Error> { - self.config.crypto.add_certificate_authority(cert)?; + let anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(&cert.inner.0)?; + Arc::make_mut(&mut self.config.crypto) + .root_store + .add_server_trust_anchors(&webpki::TLSServerTrustAnchors(&[anchor])); Ok(self) } @@ -209,7 +211,7 @@ impl ClientConfigBuilder { /// /// Useful for debugging encrypted communications with protocol analyzers such as Wireshark. pub fn enable_keylog(&mut self) -> &mut Self { - self.config.crypto.enable_keylog(); + Arc::make_mut(&mut self.config.crypto).key_log = Arc::new(rustls::KeyLogFile::new()); self } @@ -221,7 +223,8 @@ impl ClientConfigBuilder { /// /// [registry]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids pub fn protocols(&mut self, protocols: &[&[u8]]) -> &mut Self { - self.config.crypto.set_protocols(protocols); + Arc::make_mut(&mut self.config.crypto).alpn_protocols = + protocols.iter().map(|x| x.to_vec()).collect(); self } diff --git a/quinn/src/lib.rs b/quinn/src/lib.rs index c6845c7d5..22f4f4775 100644 --- a/quinn/src/lib.rs +++ b/quinn/src/lib.rs @@ -51,9 +51,7 @@ mod platform; mod udp; pub use proto::{ - crypto, - crypto::rustls::{Certificate, CertificateChain, PrivateKey}, - ClientConfig, ConnectError, ConnectionError, ConnectionId, DatagramEvent, ServerConfig, + crypto, ClientConfig, ConnectError, ConnectionError, ConnectionId, DatagramEvent, ServerConfig, Transmit, TransportConfig, VarInt, }; @@ -76,6 +74,9 @@ pub use streams::{ WriteError, }; +mod tls; +pub use tls::{Certificate, CertificateChain, PrivateKey}; + #[cfg(test)] mod tests; diff --git a/quinn/src/tls.rs b/quinn/src/tls.rs new file mode 100644 index 000000000..9e7973361 --- /dev/null +++ b/quinn/src/tls.rs @@ -0,0 +1,102 @@ +use std::fmt; + +use rustls::internal::pemfile; + +/// A single TLS certificate +#[derive(Debug, Clone)] +pub struct Certificate { + pub(crate) inner: rustls::Certificate, +} + +impl Certificate { + /// Parse a DER-formatted certificate + pub fn from_der(der: &[u8]) -> Result { + Ok(Self { + inner: rustls::Certificate(der.to_vec()), + }) + } +} + +/// A chain of signed TLS certificates ending the one to be used by a server +#[derive(Debug, Clone)] +pub struct CertificateChain { + pub(crate) certs: Vec, +} + +impl CertificateChain { + /// Parse a PEM-formatted certificate chain + /// + /// ```no_run + /// let pem = std::fs::read("fullchain.pem").expect("error reading certificates"); + /// let cert_chain = quinn::PrivateKey::from_pem(&pem).expect("error parsing certificates"); + /// ``` + pub fn from_pem(pem: &[u8]) -> Result { + Ok(Self { + certs: pemfile::certs(&mut &pem[..]) + .map_err(|()| ParseError("malformed certificate chain"))?, + }) + } + + /// Construct a certificate chain from a list of certificates + pub fn from_certs(certs: impl IntoIterator) -> Self { + certs.into_iter().collect() + } +} + +impl std::iter::FromIterator for CertificateChain { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + CertificateChain { + certs: iter.into_iter().map(|x| x.inner).collect(), + } + } +} + +/// The private key of a TLS certificate to be used by a server +#[derive(Debug, Clone)] +pub struct PrivateKey { + pub(crate) inner: rustls::PrivateKey, +} + +impl PrivateKey { + /// Parse a PEM-formatted private key + /// + /// ```no_run + /// let pem = std::fs::read("key.pem").expect("error reading key"); + /// let key = quinn::PrivateKey::from_pem(&pem).expect("error parsing key"); + /// ``` + pub fn from_pem(pem: &[u8]) -> Result { + let pkcs8 = pemfile::pkcs8_private_keys(&mut &pem[..]) + .map_err(|()| ParseError("malformed PKCS #8 private key"))?; + if let Some(x) = pkcs8.into_iter().next() { + return Ok(Self { inner: x }); + } + let rsa = pemfile::rsa_private_keys(&mut &pem[..]) + .map_err(|()| ParseError("malformed PKCS #1 private key"))?; + if let Some(x) = rsa.into_iter().next() { + return Ok(Self { inner: x }); + } + Err(ParseError("no private key found")) + } + + /// Parse a DER-encoded (binary) private key + pub fn from_der(der: &[u8]) -> Result { + Ok(Self { + inner: rustls::PrivateKey(der.to_vec()), + }) + } +} + +/// Errors encountered while parsing a TLS certificate or private key +#[derive(Debug, Clone)] +pub struct ParseError(&'static str); + +impl std::error::Error for ParseError {} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad(self.0) + } +}