Skip to content

Commit

Permalink
Add support for in-memory certificates (#320)
Browse files Browse the repository at this point in the history
Pull in new bindings for the new blob APIs added to curl in 2020. Bindings were recently added in alexcrichton/curl-rust#384. This of course only works when using a fairly new curl version.

Addresses #89.
  • Loading branch information
sagebind authored Apr 23, 2021
1 parent 4609d52 commit 983c2e6
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 16 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
195 changes: 181 additions & 14 deletions src/config/ssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ use std::{
path::PathBuf,
};

/// A public key certificate file.
#[derive(Clone, Debug)]
enum PathOrBlob {
Path(PathBuf),
Blob(Vec<u8>),
}

/// 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<PrivateKey>,
Expand All @@ -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<B, P>(bytes: B, private_key: P) -> Self
where
B: Into<Vec<u8>>,
P: Into<Option<PrivateKey>>,
{
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<B, P>(bytes: B, private_key: P) -> Self
where
B: Into<Vec<u8>>,
P: Into<Option<PrivateKey>>,
{
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<B, P>(bytes: B, password: P) -> Self
where
B: Into<Vec<u8>>,
P: Into<Option<String>>,
{
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<PathBuf>, private_key: impl Into<Option<PrivateKey>>) -> 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<PathBuf>, private_key: impl Into<Option<PrivateKey>>) -> 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<PathBuf>, password: impl Into<Option<String>>) -> 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<PathBuf>, password: impl Into<Option<String>>) -> 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<PathBuf>, password: impl Into<Option<String>>) -> Self {
Self::pkcs12_file(path, password)
}
}

impl SetOpt for ClientCertificate {
fn set_opt<H>(&self, easy: &mut Easy2<H>) -> 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)?;
Expand All @@ -79,38 +187,92 @@ 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<String>,
}

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<B, P>(bytes: B, password: P) -> Self
where
B: Into<Vec<u8>>,
P: Into<Option<String>>,
{
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<B, P>(bytes: B, password: P) -> Self
where
B: Into<Vec<u8>>,
P: Into<Option<String>>,
{
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<PathBuf>, password: impl Into<Option<String>>) -> 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<PathBuf>, password: impl Into<Option<String>>) -> Self {
Self {
format: "DER",
path: path.into(),
data: PathOrBlob::Path(path.into()),
password: password.into(),
}
}
}

impl SetOpt for PrivateKey {
fn set_opt<H>(&self, easy: &mut Easy2<H>) -> 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)?;
}
Expand All @@ -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<PathBuf>) -> Self {
Self {
path: ca_bundle_path.into(),
Expand Down

0 comments on commit 983c2e6

Please sign in to comment.