Skip to content

Commit 73fcc44

Browse files
Goiradkazk
authored andcommitted
Implement PKCS8 certificate support for all three backends.
1 parent 5a695e2 commit 73fcc44

File tree

15 files changed

+552
-8
lines changed

15 files changed

+552
-8
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ libc = "0.2"
2323
tempfile = "3.1.0"
2424

2525
[target.'cfg(target_os = "windows")'.dependencies]
26-
schannel = "0.1.16"
26+
schannel = "0.1.17"
2727

2828
[target.'cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))'.dependencies]
2929
log = "0.4.5"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ native-tls = "0.2"
2424

2525
An example client looks like:
2626

27-
```rust
27+
```rust,ignore
2828
extern crate native_tls;
2929
3030
use native_tls::TlsConnector;
@@ -46,7 +46,7 @@ fn main() {
4646

4747
To accept connections as a server from remote clients:
4848

49-
```rust,no_run
49+
```rust,ignore
5050
extern crate native_tls;
5151
5252
use native_tls::{Identity, TlsAcceptor, TlsStream};

examples/simple-server-pkcs8.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
extern crate native_tls;
2+
3+
use native_tls::{Identity, TlsAcceptor, TlsStream};
4+
use std::fs::File;
5+
use std::io::{Read, Write};
6+
use std::net::{TcpListener, TcpStream};
7+
use std::sync::Arc;
8+
use std::thread;
9+
10+
fn main() {
11+
let mut cert_file = File::open("test/cert.pem").unwrap();
12+
let mut certs = vec![];
13+
cert_file.read_to_end(&mut certs).unwrap();
14+
let mut key_file = File::open("test/key.pem").unwrap();
15+
let mut key = vec![];
16+
key_file.read_to_end(&mut key).unwrap();
17+
let pkcs8 = Identity::from_pkcs8(&certs, &key).unwrap();
18+
19+
let acceptor = TlsAcceptor::new(pkcs8).unwrap();
20+
let acceptor = Arc::new(acceptor);
21+
22+
let listener = TcpListener::bind("0.0.0.0:8443").unwrap();
23+
24+
fn handle_client(mut stream: TlsStream<TcpStream>) {
25+
let mut buf = [0; 1024];
26+
let read = stream.read(&mut buf).unwrap();
27+
let received = std::str::from_utf8(&buf[0..read]).unwrap();
28+
stream.write_all(format!("received '{}'", received).as_bytes()).unwrap();
29+
}
30+
31+
for stream in listener.incoming() {
32+
match stream {
33+
Ok(stream) => {
34+
let acceptor = acceptor.clone();
35+
thread::spawn(move || {
36+
let stream = acceptor.accept(stream).unwrap();
37+
handle_client(stream);
38+
});
39+
}
40+
Err(_e) => { /* connection failed */ }
41+
}
42+
}
43+
}

src/imp/openssl.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use self::openssl::error::ErrorStack;
55
use self::openssl::hash::MessageDigest;
66
use self::openssl::nid::Nid;
77
use self::openssl::pkcs12::Pkcs12;
8-
use self::openssl::pkey::PKey;
8+
use self::openssl::pkey::{PKey, Private};
99
use self::openssl::ssl::{
1010
self, MidHandshakeSslStream, SslAcceptor, SslConnector, SslContextBuilder, SslMethod,
1111
SslVerifyMode,
@@ -16,7 +16,6 @@ use std::fmt;
1616
use std::io;
1717
use std::sync::Once;
1818

19-
use self::openssl::pkey::Private;
2019
use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder};
2120

2221
#[cfg(have_min_max_version)]
@@ -161,6 +160,19 @@ impl Identity {
161160
chain: parsed.chain.into_iter().flatten().collect(),
162161
})
163162
}
163+
164+
pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result<Identity, Error> {
165+
let pkey = PKey::private_key_from_pem(key)?;
166+
let mut cert_chain = X509::stack_from_pem(buf)?.into_iter();
167+
let cert = cert_chain.next();
168+
let chain = cert_chain.collect();
169+
Ok(Identity {
170+
pkey,
171+
// an identity must have at least one certificate, the leaf cert
172+
cert: cert.expect("at least one certificate must be provided to create an identity"),
173+
chain,
174+
})
175+
}
164176
}
165177

166178
#[derive(Clone)]
@@ -258,7 +270,10 @@ impl TlsConnector {
258270
if let Some(ref identity) = builder.identity {
259271
connector.set_certificate(&identity.0.cert)?;
260272
connector.set_private_key(&identity.0.pkey)?;
261-
for cert in identity.0.chain.iter().rev() {
273+
for cert in identity.0.chain.iter() {
274+
// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html
275+
// specifies that "When sending a certificate chain, extra chain certificates are
276+
// sent in order following the end entity certificate."
262277
connector.add_extra_chain_cert(cert.to_owned())?;
263278
}
264279
}
@@ -342,7 +357,10 @@ impl TlsAcceptor {
342357
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?;
343358
acceptor.set_private_key(&builder.identity.0.pkey)?;
344359
acceptor.set_certificate(&builder.identity.0.cert)?;
345-
for cert in builder.identity.0.chain.iter().rev() {
360+
for cert in builder.identity.0.chain.iter() {
361+
// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html
362+
// specifies that "When sending a certificate chain, extra chain certificates are
363+
// sent in order following the end entity certificate."
346364
acceptor.add_extra_chain_cert(cert.to_owned())?;
347365
}
348366
supported_protocols(builder.min_protocol, builder.max_protocol, &mut acceptor)?;

src/imp/schannel.rs

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
extern crate schannel;
22

3-
use self::schannel::cert_context::{CertContext, HashAlgorithm};
3+
use self::schannel::cert_context::{CertContext, HashAlgorithm, KeySpec};
44
use self::schannel::cert_store::{CertAdd, CertStore, Memory, PfxImportOptions};
55
use self::schannel::schannel_cred::{Direction, Protocol, SchannelCred};
6+
use self::schannel::crypt_prov::{AcquireOptions, ProviderType};
67
use self::schannel::tls_stream;
78
use std::error;
89
use std::fmt;
@@ -93,6 +94,38 @@ impl Identity {
9394

9495
Ok(Identity { cert: identity })
9596
}
97+
98+
pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result<Identity, Error> {
99+
let mut store = Memory::new()?.into_store();
100+
let mut cert_iter = pem::PemBlock::new(pem).into_iter();
101+
let leaf = cert_iter.next().expect("at least one certificate must be provided to create an identity");
102+
let cert = CertContext::from_pem(std::str::from_utf8(leaf).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "leaf cert contains invalid utf8"))?)?;
103+
104+
let mut options = AcquireOptions::new();
105+
options.container("schannel");
106+
let type_ = ProviderType::rsa_full();
107+
108+
let mut container = match options.acquire(type_) {
109+
Ok(container) => container,
110+
Err(_) => options.new_keyset(true).acquire(type_)?,
111+
};
112+
container.import()
113+
.import_pkcs8_pem(&key)?;
114+
115+
cert.set_key_prov_info()
116+
.container("schannel")
117+
.type_(type_)
118+
.keep_open(true)
119+
.key_spec(KeySpec::key_exchange())
120+
.set()?;
121+
let mut context = store.add_cert(&cert, CertAdd::Always)?;
122+
123+
for int_cert in cert_iter {
124+
let certificate = Certificate::from_pem(int_cert)?;
125+
context = store.add_cert(&certificate.0, schannel::cert_store::CertAdd::Always)?;
126+
}
127+
Ok(Identity{cert: context})
128+
}
96129
}
97130

98131
#[derive(Clone)]
@@ -384,3 +417,84 @@ impl<S: io::Read + io::Write> io::Write for TlsStream<S> {
384417
self.0.flush()
385418
}
386419
}
420+
421+
422+
mod pem {
423+
/// Split data by PEM guard lines
424+
pub struct PemBlock<'a> {
425+
pem_block: &'a str,
426+
cur_end: usize,
427+
}
428+
429+
impl<'a> PemBlock<'a> {
430+
pub fn new(data: &'a [u8]) -> PemBlock<'a> {
431+
let s = ::std::str::from_utf8(data).unwrap();
432+
PemBlock {
433+
pem_block: s,
434+
cur_end: s.find("-----BEGIN").unwrap_or(s.len()),
435+
}
436+
}
437+
}
438+
439+
impl<'a> Iterator for PemBlock<'a> {
440+
type Item = &'a [u8];
441+
fn next(&mut self) -> Option<Self::Item> {
442+
let last = self.pem_block.len();
443+
if self.cur_end >= last {
444+
return None;
445+
}
446+
let begin = self.cur_end;
447+
let pos = self.pem_block[begin + 1..].find("-----BEGIN");
448+
self.cur_end = match pos {
449+
Some(end) => end + begin + 1,
450+
None => last,
451+
};
452+
return Some(&self.pem_block[begin..self.cur_end].as_bytes());
453+
}
454+
}
455+
456+
#[test]
457+
fn test_split() {
458+
// Split three certs, CRLF line terminators.
459+
assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\
460+
-----BEGIN SECOND-----\r\n-----END SECOND\r\n\
461+
-----BEGIN THIRD-----\r\n-----END THIRD\r\n").collect::<Vec<&[u8]>>(),
462+
vec![b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8],
463+
b"-----BEGIN SECOND-----\r\n-----END SECOND\r\n",
464+
b"-----BEGIN THIRD-----\r\n-----END THIRD\r\n"]);
465+
// Split three certs, CRLF line terminators except at EOF.
466+
assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\
467+
-----BEGIN SECOND-----\r\n-----END SECOND-----\r\n\
468+
-----BEGIN THIRD-----\r\n-----END THIRD-----").collect::<Vec<&[u8]>>(),
469+
vec![b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8],
470+
b"-----BEGIN SECOND-----\r\n-----END SECOND-----\r\n",
471+
b"-----BEGIN THIRD-----\r\n-----END THIRD-----"]);
472+
// Split two certs, LF line terminators.
473+
assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n\
474+
-----BEGIN SECOND-----\n-----END SECOND\n").collect::<Vec<&[u8]>>(),
475+
vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8],
476+
b"-----BEGIN SECOND-----\n-----END SECOND\n"]);
477+
// Split two certs, CR line terminators.
478+
assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r-----END FIRST-----\r\
479+
-----BEGIN SECOND-----\r-----END SECOND\r").collect::<Vec<&[u8]>>(),
480+
vec![b"-----BEGIN FIRST-----\r-----END FIRST-----\r" as &[u8],
481+
b"-----BEGIN SECOND-----\r-----END SECOND\r"]);
482+
// Split two certs, LF line terminators except at EOF.
483+
assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n\
484+
-----BEGIN SECOND-----\n-----END SECOND").collect::<Vec<&[u8]>>(),
485+
vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8],
486+
b"-----BEGIN SECOND-----\n-----END SECOND"]);
487+
// Split a single cert, LF line terminators.
488+
assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n").collect::<Vec<&[u8]>>(),
489+
vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8]]);
490+
// Split a single cert, LF line terminators except at EOF.
491+
assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----").collect::<Vec<&[u8]>>(),
492+
vec![b"-----BEGIN FIRST-----\n-----END FIRST-----" as &[u8]]);
493+
// (Don't) split garbage.
494+
assert_eq!(PemBlock::new(b"junk").collect::<Vec<&[u8]>>(),
495+
Vec::<&[u8]>::new());
496+
assert_eq!(PemBlock::new(b"junk-----BEGIN garbage").collect::<Vec<&[u8]>>(),
497+
vec![b"-----BEGIN garbage" as &[u8]]);
498+
}
499+
}
500+

src/imp/security_framework.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ use self::security_framework::os::macos::certificate::{PropertyType, SecCertific
2424
#[cfg(not(target_os = "ios"))]
2525
use self::security_framework::os::macos::certificate_oids::CertificateOid;
2626
#[cfg(not(target_os = "ios"))]
27+
use self::security_framework::os::macos::identity::SecIdentityExt;
28+
#[cfg(not(target_os = "ios"))]
2729
use self::security_framework::os::macos::import_export::{
2830
ImportOptions, Pkcs12ImportOptionsExt, SecItems,
2931
};
@@ -82,6 +84,33 @@ pub struct Identity {
8284
}
8385

8486
impl Identity {
87+
pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result<Identity, Error> {
88+
let dir = TempDir::new().unwrap();
89+
let keychain = keychain::CreateOptions::new()
90+
.password("password")
91+
.create(dir.path().join("identity.keychain"))?;
92+
93+
let mut items = SecItems::default();
94+
95+
ImportOptions::new()
96+
.filename("key.pem")
97+
.items(&mut items)
98+
.keychain(&keychain)
99+
.import(&key)?;
100+
101+
ImportOptions::new()
102+
.filename("chain.pem")
103+
.items(&mut items)
104+
.keychain(&keychain)
105+
.import(&pem)?;
106+
107+
let ident = SecIdentity::with_certificate(&[keychain], &items.certificates[0])?;
108+
Ok(Identity {
109+
identity: ident,
110+
chain: items.certificates
111+
})
112+
}
113+
85114
pub fn from_pkcs12(buf: &[u8], pass: &str) -> Result<Identity, Error> {
86115
let mut imports = Identity::import_options(buf, pass)?;
87116
let import = imports.pop().unwrap();

src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
//! * TLS/SSL client communication
2828
//! * TLS/SSL server communication
2929
//! * PKCS#12 encoded identities
30+
//! * X.509/PKCS#8 encoded identities
3031
//! * Secure-by-default for client and server
3132
//! * Includes hostname verification for clients
3233
//! * Supports asynchronous I/O for both the server and the client
@@ -177,6 +178,18 @@ impl Identity {
177178
let identity = imp::Identity::from_pkcs12(der, password)?;
178179
Ok(Identity(identity))
179180
}
181+
182+
/// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first.
183+
/// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate.
184+
///
185+
/// The certificate chain should contain any intermediate cerficates that should be sent to
186+
/// clients to allow them to build a chain to a trusted root.
187+
///
188+
/// A certificate chain here means a series of PEM encoded certificates concatenated together.
189+
pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result<Identity> {
190+
let identity = imp::Identity::from_pkcs8(pem, key)?;
191+
Ok(Identity(identity))
192+
}
180193
}
181194

182195
/// An X509 certificate.

0 commit comments

Comments
 (0)