Skip to content

Commit 0055210

Browse files
feat(identity): support non-PKCS#12 identities (#7)
PKCS#12 identity (aka .pfx files) are often used to transport private key material with a decryption password. In scenarios where a Vault-like server is used to emit PKI certificates, PFX files are not really used and certificates private key material is installed in non swappable RAM locations directly without any decryption password required. This simplifies automatic rotation and rollouts to production deployments when this is available. Before this, it would be necessary to re-encode PEM-encoded DER private key via openssl with an empty password and store it in another runtime location. Signed-off-by: Raito Bezarius <[email protected]>
1 parent efb09a7 commit 0055210

File tree

1 file changed

+77
-42
lines changed

1 file changed

+77
-42
lines changed

src/lib.rs

Lines changed: 77 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ pub enum TcpStream {
125125

126126
/// Holds extra TLS configuration
127127
#[derive(Default, Debug, PartialEq)]
128-
pub struct TLSConfig<'der, 'pass, 'chain> {
128+
pub struct TLSConfig<'der, 'cert, 'pass, 'chain> {
129129
/// Use for client certificate authentication
130-
pub identity: Option<Identity<'der, 'pass>>,
130+
pub identity: Option<Identity<'der, 'cert, 'pass>>,
131131
/// The custom certificates chain in PEM format
132132
pub cert_chain: Option<&'chain str>,
133133
}
@@ -144,39 +144,63 @@ pub struct OwnedTLSConfig {
144144
impl OwnedTLSConfig {
145145
/// Get the ephemeral `TLSConfig` corresponding to the `OwnedTLSConfig`
146146
#[must_use]
147-
pub fn as_ref(&self) -> TLSConfig<'_, '_, '_> {
147+
pub fn as_ref(&self) -> TLSConfig<'_, '_, '_, '_> {
148148
TLSConfig {
149149
identity: self.identity.as_ref().map(OwnedIdentity::as_ref),
150150
cert_chain: self.cert_chain.as_deref(),
151151
}
152152
}
153153
}
154154

155-
/// Holds PKCS#12 DER-encoded identity and decryption password
155+
/// Holds one of:
156+
/// - PKCS#12 DER-encoded identity and decryption password
157+
/// - PEM-encoded DER identity (without decryption password)
156158
#[derive(Debug, PartialEq)]
157-
pub struct Identity<'der, 'pass> {
158-
/// PKCS#12 DER-encoded identity
159-
pub der: &'der [u8],
160-
/// Decryption password
161-
pub password: &'pass str,
162-
}
163-
164-
/// Holds PKCS#12 DER-encoded identity and decryption password
159+
pub enum Identity<'der, 'cert, 'pass> {
160+
/// PKCS#12 DER-encoded identity with decryption password
161+
PKCS12 {
162+
/// PKCS#12 DER-encoded identity
163+
der: &'der [u8],
164+
/// Decryption password
165+
password: &'pass str,
166+
},
167+
/// PEM encoded DER private key with PEM encoded certificate
168+
PEM {
169+
/// PEM-encoded identity
170+
der: &'der [u8],
171+
/// PEM-encoded certificate
172+
cert: &'cert [u8]
173+
}
174+
}
175+
176+
/// Holds one of:
177+
/// - PKCS#12 DER-encoded identity and decryption password
178+
/// - PEM-encoded DER identity (without decryption password)
165179
#[derive(Debug, PartialEq)]
166-
pub struct OwnedIdentity {
167-
/// PKCS#12 DER-encoded identity
168-
pub der: Vec<u8>,
169-
/// Decryption password
170-
pub password: String,
180+
pub enum OwnedIdentity {
181+
/// PKCS#12 DER-encoded identity with decryption password
182+
PKCS12 {
183+
/// PKCS#12 DER-encoded identity
184+
der: Vec<u8>,
185+
/// Decryption password
186+
password: String,
187+
},
188+
/// PEM encoded DER private key with PEM encoded certificate
189+
PEM {
190+
/// PEM-encoded identity
191+
der: Vec<u8>,
192+
/// PEM-encoded certificate
193+
cert: Vec<u8>
194+
}
171195
}
172196

173197
impl OwnedIdentity {
174198
/// Get the ephemeral `Identity` corresponding to the `OwnedIdentity`
175199
#[must_use]
176-
pub fn as_ref(&self) -> Identity<'_, '_> {
177-
Identity {
178-
der: &self.der,
179-
password: &self.password,
200+
pub fn as_ref(&self) -> Identity<'_, '_, '_> {
201+
match self {
202+
Self::PEM { der, cert } => Identity::PEM { der, cert },
203+
Self::PKCS12 { der, password } => Identity::PKCS12 { der, password }
180204
}
181205
}
182206
}
@@ -241,7 +265,7 @@ impl TcpStream {
241265
pub fn into_tls(
242266
self,
243267
domain: &str,
244-
config: TLSConfig<'_, '_, '_>,
268+
config: TLSConfig<'_, '_, '_, '_>,
245269
) -> Result<Self, HandshakeError> {
246270
into_tls_impl(self, domain, config)
247271
}
@@ -325,9 +349,9 @@ fn into_rustls_common(
325349
s: TcpStream,
326350
mut c: RustlsConnectorConfig,
327351
domain: &str,
328-
config: TLSConfig<'_, '_, '_>,
352+
config: TLSConfig<'_, '_, '_, '_>,
329353
) -> HandshakeResult {
330-
use rustls_connector::rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
354+
use rustls_connector::rustls_pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
331355

332356
if let Some(cert_chain) = config.cert_chain {
333357
let mut cert_chain = std::io::BufReader::new(cert_chain.as_bytes());
@@ -337,19 +361,30 @@ fn into_rustls_common(
337361
c.add_parsable_certificates(certs);
338362
}
339363
let connector = if let Some(identity) = config.identity {
340-
let pfx = p12_keystore::KeyStore::from_pkcs12(identity.der, identity.password)
341-
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
342-
let Some((_, keychain)) = pfx.private_key_chain() else {
343-
return Err(
344-
io::Error::new(io::ErrorKind::Other, "No private key in pkcs12 DER").into(),
345-
);
364+
let (certs, key) = match identity {
365+
Identity::PKCS12 { der, password } => {
366+
let pfx = p12_keystore::KeyStore::from_pkcs12(der, password)
367+
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
368+
let Some((_, keychain)) = pfx.private_key_chain() else {
369+
return Err(
370+
io::Error::new(io::ErrorKind::Other, "No private key in pkcs12 DER").into(),
371+
);
372+
};
373+
let certs = keychain
374+
.chain()
375+
.iter()
376+
.map(|cert| CertificateDer::from(cert.as_der().to_vec()))
377+
.collect();
378+
(certs, PrivateKeyDer::from(PrivatePkcs8KeyDer::from(keychain.key().to_vec())))
379+
},
380+
Identity::PEM { der, cert } => {
381+
let mut cert_reader = std::io::BufReader::new(cert);
382+
let certs = rustls_pemfile::certs(&mut cert_reader)
383+
.collect::<Result<Vec<_>, _>>()
384+
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
385+
(certs, PrivateKeyDer::from_pem_slice(der).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?)
386+
}
346387
};
347-
let key = PrivateKeyDer::from(PrivatePkcs8KeyDer::from(keychain.key().to_vec()));
348-
let certs = keychain
349-
.chain()
350-
.iter()
351-
.map(|cert| CertificateDer::from(cert.as_der().to_vec()))
352-
.collect();
353388
c.connector_with_single_cert(certs, key)
354389
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?
355390
} else {
@@ -360,19 +395,19 @@ fn into_rustls_common(
360395

361396
cfg_if! {
362397
if #[cfg(feature = "rustls-native-certs")] {
363-
fn into_tls_impl(s: TcpStream, domain: &str, config: TLSConfig<'_, '_, '_>) -> HandshakeResult {
398+
fn into_tls_impl(s: TcpStream, domain: &str, config: TLSConfig<'_, '_, '_, '_>) -> HandshakeResult {
364399
into_rustls_common(s, RustlsConnectorConfig::new_with_native_certs()?, domain, config)
365400
}
366401
} else if #[cfg(feature = "rustls-webpki-roots-certs")] {
367-
fn into_tls_impl(s: TcpStream, domain: &str, config: TLSConfig<'_, '_, '_>) -> HandshakeResult {
402+
fn into_tls_impl(s: TcpStream, domain: &str, config: TLSConfig<'_, '_, '_, '_>) -> HandshakeResult {
368403
into_rustls_common(s, RustlsConnectorConfig::new_with_webpki_roots_certs(), domain, config)
369404
}
370405
} else if #[cfg(feature = "rustls-common")] {
371-
fn into_tls_impl(s: TcpStream, domain: &str, config: TLSConfig<'_, '_, '_>) -> HandshakeResult {
406+
fn into_tls_impl(s: TcpStream, domain: &str, config: TLSConfig<'_, '_, '_, '_>) -> HandshakeResult {
372407
into_rustls_common(s, RustlsConnectorConfig::default(), domain, config)
373408
}
374409
} else if #[cfg(feature = "openssl")] {
375-
fn into_tls_impl(s: TcpStream, domain: &str, config: TLSConfig<'_, '_, '_>) -> HandshakeResult {
410+
fn into_tls_impl(s: TcpStream, domain: &str, config: TLSConfig<'_, '_, '_, '_>) -> HandshakeResult {
376411
use openssl::x509::X509;
377412

378413
let mut builder = OpenSslConnector::builder(OpenSslMethod::tls())?;
@@ -398,7 +433,7 @@ cfg_if! {
398433
s.into_openssl(&builder.build(), domain)
399434
}
400435
} else if #[cfg(feature = "native-tls")] {
401-
fn into_tls_impl(s: TcpStream, domain: &str, config: TLSConfig<'_, '_, '_>) -> HandshakeResult {
436+
fn into_tls_impl(s: TcpStream, domain: &str, config: TLSConfig<'_, '_, '_, '_>) -> HandshakeResult {
402437
use native_tls::Certificate;
403438

404439
let mut builder = NativeTlsConnector::builder();
@@ -414,7 +449,7 @@ cfg_if! {
414449
s.into_native_tls(&builder.build().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?, domain)
415450
}
416451
} else {
417-
fn into_tls_impl(s: TcpStream, _domain: &str, _: TLSConfig<'_, '_, '_>) -> HandshakeResult {
452+
fn into_tls_impl(s: TcpStream, _domain: &str, _: TLSConfig<'_, '_, '_, '_>) -> HandshakeResult {
418453
Ok(s.into_plain()?)
419454
}
420455
}

0 commit comments

Comments
 (0)