From 646c8b8efc6456307b5d570d62f4405d22ebb774 Mon Sep 17 00:00:00 2001 From: Anthony Nowell Date: Sun, 30 Jul 2017 15:13:09 -0600 Subject: [PATCH] Support PKCS12 identity on the ClientBuilder --- src/async_impl/client.rs | 16 +++++++++++- src/client.rs | 37 +++++++++++++++++++++++++++- src/lib.rs | 2 +- src/tls.rs | 53 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 3 deletions(-) diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index de8df3853..a6203bea9 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -17,7 +17,7 @@ use super::response::{self, Response}; use connect::Connector; use into_url::to_uri; use redirect::{self, RedirectPolicy, check_redirect, remove_sensitive_headers}; -use {Certificate, IntoUrl, Method, proxy, Proxy, StatusCode, Url}; +use {Certificate, Identity, IntoUrl, Method, proxy, Proxy, StatusCode, Url}; static DEFAULT_USER_AGENT: &'static str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); @@ -115,6 +115,20 @@ impl ClientBuilder { Ok(self) } + /// Sets the identity to be used for client certificate authentication. + /// + /// This can be used in mutual authentication scenarios to identify to a server + /// with a Pkcs12 archive containing a certificate and private key for example. + /// + /// # Errors + /// + /// This method fails if adding client identity was unsuccessful. + pub fn identity(&mut self, identity: Identity) -> ::Result<&mut ClientBuilder> { + let pkcs12 = ::tls::pkcs12(identity); + try_!(self.config_mut().tls.identity(pkcs12)); + Ok(self) + } + /// Disable hostname verification. /// /// # Warning diff --git a/src/client.rs b/src/client.rs index 87e45bbb0..e75e82daa 100644 --- a/src/client.rs +++ b/src/client.rs @@ -8,7 +8,7 @@ use futures::sync::{mpsc, oneshot}; use request::{self, Request, RequestBuilder}; use response::{self, Response}; -use {async_impl, Certificate, Method, IntoUrl, Proxy, RedirectPolicy, wait}; +use {async_impl, Certificate, Identity, Method, IntoUrl, Proxy, RedirectPolicy, wait}; /// A `Client` to make Requests with. /// @@ -119,6 +119,41 @@ impl ClientBuilder { Ok(self) } + /// Sets the identity to be used for client certificate authentication. + /// + /// This can be used in mutual authentication scenarios to identify to a server + /// with a PKCS#12 archive containing a certificate and private key for example. + /// + /// # Example + /// ``` + /// # use std::fs::File; + /// # use std::io::Read; + /// # fn build_client() -> Result<(), Box> { + /// // read a local PKCS12 bundle + /// let mut buf = Vec::new(); + /// File::open("my-ident.pfx")?.read_to_end(&mut buf)?; + /// + /// // create an Identity from the PKCS#12 archive + /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?; + /// + /// // get a client builder + /// let client = reqwest::ClientBuilder::new()? + /// .identity(pkcs12)? + /// .build()?; + /// # drop(client); + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// This method fails if adding client identity was unsuccessful. + pub fn identity(&mut self, identity: Identity) -> ::Result<&mut ClientBuilder> { + self.inner.identity(identity)?; + Ok(self) + } + + /// Disable hostname verification. /// /// # Warning diff --git a/src/lib.rs b/src/lib.rs index 1b7ec6a79..892bd3f1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,7 +153,7 @@ pub use self::proxy::Proxy; pub use self::redirect::{RedirectAction, RedirectAttempt, RedirectPolicy}; pub use self::request::{Request, RequestBuilder}; pub use self::response::Response; -pub use self::tls::Certificate; +pub use self::tls::{Certificate, Identity}; // this module must be first because of the `try_` macro diff --git a/src/tls.rs b/src/tls.rs index 909539866..07b4eb860 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -38,8 +38,61 @@ impl fmt::Debug for Certificate { } } + +/// Represent a private key and X509 cert as a client certificate. +pub struct Identity(native_tls::Pkcs12); + +impl Identity { + /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key. + /// + /// The archive should contain a leaf certificate and its private key, as well any intermediate + /// certificates that allow clients to build a chain to a trusted root. + /// The chain certificates should be in order from the leaf certificate towards the root. + /// + /// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created + /// with the OpenSSL `pkcs12` tool: + /// + /// ```bash + /// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem + /// ``` + /// + /// # Examples + /// + /// ``` + /// # use std::fs::File; + /// # use std::io::Read; + /// # fn pkcs12() -> Result<(), Box> { + /// let mut buf = Vec::new(); + /// File::open("my-ident.pfx")? + /// .read_to_end(&mut buf)?; + /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?; + /// # drop(pkcs12); + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// If the provided buffer is not valid DER, an error will be returned. + pub fn from_pkcs12_der(der: &[u8], password: &str) -> ::Result { + let inner = try_!(native_tls::Pkcs12::from_der(der, password)); + Ok(Identity(inner)) + } +} + +impl fmt::Debug for Identity { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Identity") + .finish() + } +} + // pub(crate) pub fn cert(cert: Certificate) -> native_tls::Certificate { cert.0 } + +pub fn pkcs12(identity: Identity) -> native_tls::Pkcs12 { + identity.0 +}