From 896e6530f9cb0d9d64bf266075a9798ff590a37b 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. --- 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 +++++++++++++ 11 files changed, 214 insertions(+), 284 deletions(-) create mode 100644 quinn/src/tls.rs diff --git a/quinn-h3/examples/h3.rs b/quinn-h3/examples/h3.rs index 979d1533d6..373e0af786 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 c97e1770c7..b83c4b13d8 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 3668a14145..9ee76c0f7b 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 fced714460..d8ac4afed8 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 68754baa18..d684842daa 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> { @@ -402,7 +402,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(); @@ -654,7 +654,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") @@ -817,9 +816,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 2c2f12bc99..2b7fb508fd 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 eeaf3dd351..17001fafe0 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 bed28b8b83..2421df4522 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 f71085a06d..56114da393 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 c6845c7d56..22f4f4775a 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 0000000000..9e79733617 --- /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) + } +}