From 983c2e6717ccc94babc993a961401b79f2531945 Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Thu, 22 Apr 2021 21:40:17 -0500 Subject: [PATCH] Add support for in-memory certificates (#320) Pull in new bindings for the new blob APIs added to curl in 2020. Bindings were recently added in https://github.com/alexcrichton/curl-rust/pull/384. This of course only works when using a fairly new curl version. Addresses https://github.com/sagebind/isahc/issues/89. --- Cargo.toml | 4 +- src/config/ssl.rs | 195 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 183 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 258fba2f..337ded5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,8 @@ unstable-interceptors = [] [dependencies] async-channel = "1.6" crossbeam-utils = "0.8" -curl = ">=0.4.34, <=0.4.35" -curl-sys = "0.4.37" +curl = "0.4.36" +curl-sys = "0.4.42" futures-lite = "1.11" http = "0.2.1" log = "0.4" diff --git a/src/config/ssl.rs b/src/config/ssl.rs index ec363844..bbcadfa8 100644 --- a/src/config/ssl.rs +++ b/src/config/ssl.rs @@ -8,14 +8,23 @@ use std::{ path::PathBuf, }; -/// A public key certificate file. +#[derive(Clone, Debug)] +enum PathOrBlob { + Path(PathBuf), + Blob(Vec), +} + +/// A client certificate for SSL/TLS client validation. +/// +/// Note that this isn't merely an X.509 certificate, but rather a certificate +/// and private key pair. #[derive(Clone, Debug)] pub struct ClientCertificate { /// Name of the cert format. format: &'static str, - /// Path to the certificate file. - path: PathBuf, + /// The certificate data, either a path or a blob. + data: PathOrBlob, /// Private key corresponding to the SSL/TLS certificate. private_key: Option, @@ -25,41 +34,140 @@ pub struct ClientCertificate { } impl ClientCertificate { + /// Use a PEM-encoded certificate stored in the given byte buffer. + /// + /// The certificate object takes ownership of the byte buffer. If a borrowed + /// type is supplied, such as `&[u8]`, then the bytes will be copied. + /// + /// The certificate is not parsed or validated here. If the certificate is + /// malformed or the format is not supported by the underlying SSL/TLS + /// engine, an error will be returned when attempting to send a request + /// using the offending certificate. + pub fn pem(bytes: B, private_key: P) -> Self + where + B: Into>, + P: Into>, + { + Self { + format: "PEM", + data: PathOrBlob::Blob(bytes.into()), + private_key: private_key.into(), + password: None, + } + } + + /// Use a DER-encoded certificate stored in the given byte buffer. + /// + /// The certificate object takes ownership of the byte buffer. If a borrowed + /// type is supplied, such as `&[u8]`, then the bytes will be copied. + /// + /// The certificate is not parsed or validated here. If the certificate is + /// malformed or the format is not supported by the underlying SSL/TLS + /// engine, an error will be returned when attempting to send a request + /// using the offending certificate. + pub fn der(bytes: B, private_key: P) -> Self + where + B: Into>, + P: Into>, + { + Self { + format: "DER", + data: PathOrBlob::Blob(bytes.into()), + private_key: private_key.into(), + password: None, + } + } + + /// Use a certificate and private key from a PKCS #12 archive stored in the + /// given byte buffer. + /// + /// The certificate object takes ownership of the byte buffer. If a borrowed + /// type is supplied, such as `&[u8]`, then the bytes will be copied. + /// + /// The certificate is not parsed or validated here. If the certificate is + /// malformed or the format is not supported by the underlying SSL/TLS + /// engine, an error will be returned when attempting to send a request + /// using the offending certificate. + pub fn pkcs12(bytes: B, password: P) -> Self + where + B: Into>, + P: Into>, + { + Self { + format: "P12", + data: PathOrBlob::Blob(bytes.into()), + private_key: None, + password: password.into(), + } + } + /// Get a certificate from a PEM-encoded file. + /// + /// The certificate file is not loaded or validated here. If the file does + /// not exist or the format is not supported by the underlying SSL/TLS + /// engine, an error will be returned when attempting to send a request + /// using the offending certificate. pub fn pem_file(path: impl Into, private_key: impl Into>) -> Self { Self { format: "PEM", - path: path.into(), + data: PathOrBlob::Path(path.into()), private_key: private_key.into(), password: None, } } /// Get a certificate from a DER-encoded file. + /// + /// The certificate file is not loaded or validated here. If the file does + /// not exist or the format is not supported by the underlying SSL/TLS + /// engine, an error will be returned when attempting to send a request + /// using the offending certificate. pub fn der_file(path: impl Into, private_key: impl Into>) -> Self { Self { format: "DER", - path: path.into(), + data: PathOrBlob::Path(path.into()), private_key: private_key.into(), password: None, } } - /// Get a certificate from a PKCS#12-encoded file. - pub fn p12_file(path: impl Into, password: impl Into>) -> Self { + /// Get a certificate and private key from a PKCS #12-encoded file. + /// + /// The certificate file is not loaded or validated here. If the file does + /// not exist or the format is not supported by the underlying SSL/TLS + /// engine, an error will be returned when attempting to send a request + /// using the offending certificate. + pub fn pkcs12_file(path: impl Into, password: impl Into>) -> Self { Self { format: "P12", - path: path.into(), + data: PathOrBlob::Path(path.into()), private_key: None, password: password.into(), } } + + /// Get a certificate and private key from a PKCS #12-encoded file. + /// + /// Use [`pkcs12_file`][ClientCertificate::pkcs12_file] instead. + #[inline] + #[doc(hidden)] + #[deprecated( + since = "1.4.0", + note = "please use the more clearly-named `pkcs12_file` instead" + )] + pub fn p12_file(path: impl Into, password: impl Into>) -> Self { + Self::pkcs12_file(path, password) + } } impl SetOpt for ClientCertificate { fn set_opt(&self, easy: &mut Easy2) -> Result<(), curl::Error> { easy.ssl_cert_type(self.format)?; - easy.ssl_cert(&self.path)?; + + match &self.data { + PathOrBlob::Path(path) => easy.ssl_cert(path.as_path()), + PathOrBlob::Blob(bytes) => easy.ssl_cert_blob(bytes.as_slice()), + }?; if let Some(key) = self.private_key.as_ref() { key.set_opt(easy)?; @@ -79,28 +187,78 @@ pub struct PrivateKey { /// Key format name. format: &'static str, - /// Path to the key file. - path: PathBuf, + /// The certificate data, either a path or a blob. + data: PathOrBlob, /// Password to decrypt the key file. password: Option, } impl PrivateKey { + /// Use a PEM-encoded private key stored in the given byte buffer. + /// + /// The private key object takes ownership of the byte buffer. If a borrowed + /// type is supplied, such as `&[u8]`, then the bytes will be copied. + /// + /// The key is not parsed or validated here. If the key is malformed or the + /// format is not supported by the underlying SSL/TLS engine, an error will + /// be returned when attempting to send a request using the offending key. + pub fn pem(bytes: B, password: P) -> Self + where + B: Into>, + P: Into>, + { + Self { + format: "PEM", + data: PathOrBlob::Blob(bytes.into()), + password: password.into(), + } + } + + /// Use a DER-encoded private key stored in the given byte buffer. + /// + /// The private key object takes ownership of the byte buffer. If a borrowed + /// type is supplied, such as `&[u8]`, then the bytes will be copied. + /// + /// The key is not parsed or validated here. If the key is malformed or the + /// format is not supported by the underlying SSL/TLS engine, an error will + /// be returned when attempting to send a request using the offending key. + pub fn der(bytes: B, password: P) -> Self + where + B: Into>, + P: Into>, + { + Self { + format: "DER", + data: PathOrBlob::Blob(bytes.into()), + password: password.into(), + } + } + /// Get a PEM-encoded private key file. + /// + /// The key file is not loaded or validated here. If the file does not exist + /// or the format is not supported by the underlying SSL/TLS engine, an + /// error will be returned when attempting to send a request using the + /// offending key. pub fn pem_file(path: impl Into, password: impl Into>) -> Self { Self { format: "PEM", - path: path.into(), + data: PathOrBlob::Path(path.into()), password: password.into(), } } /// Get a DER-encoded private key file. + /// + /// The key file is not loaded or validated here. If the file does not exist + /// or the format is not supported by the underlying SSL/TLS engine, an + /// error will be returned when attempting to send a request using the + /// offending key. pub fn der_file(path: impl Into, password: impl Into>) -> Self { Self { format: "DER", - path: path.into(), + data: PathOrBlob::Path(path.into()), password: password.into(), } } @@ -108,9 +266,13 @@ impl PrivateKey { impl SetOpt for PrivateKey { fn set_opt(&self, easy: &mut Easy2) -> Result<(), curl::Error> { - easy.ssl_key(&self.path)?; easy.ssl_key_type(self.format)?; + match &self.data { + PathOrBlob::Path(path) => easy.ssl_key(path.as_path()), + PathOrBlob::Blob(bytes) => easy.ssl_key_blob(bytes.as_slice()), + }?; + if let Some(password) = self.password.as_ref() { easy.key_password(password)?; } @@ -129,6 +291,11 @@ pub struct CaCertificate { impl CaCertificate { /// Get a CA certificate from a path to a certificate bundle file. + /// + /// The certificate file is not loaded or validated here. If the file does + /// not exist or the format is not supported by the underlying SSL/TLS + /// engine, an error will be returned when attempting to send a request + /// using the offending certificate. pub fn file(ca_bundle_path: impl Into) -> Self { Self { path: ca_bundle_path.into(),