Skip to content

Commit f641a64

Browse files
authored
Merge pull request #209 from kazk/pkcs8
Add PKCS8 Support
2 parents 5a695e2 + 658800e commit f641a64

File tree

8 files changed

+508
-39
lines changed

8 files changed

+508
-39
lines changed

Cargo.toml

+2-2
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"
@@ -36,4 +36,4 @@ openssl-src = { version = "300.0.3", optional = true }
3636

3737
[dev-dependencies]
3838
tempfile = "3.0"
39-
test-cert-gen = "0.1"
39+
test-cert-gen = "0.7"

README.md

+2-2
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

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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
29+
.write_all(format!("received '{}'", received).as_bytes())
30+
.unwrap();
31+
}
32+
33+
for stream in listener.incoming() {
34+
match stream {
35+
Ok(stream) => {
36+
let acceptor = acceptor.clone();
37+
thread::spawn(move || {
38+
let stream = acceptor.accept(stream).unwrap();
39+
handle_client(stream);
40+
});
41+
}
42+
Err(_e) => { /* connection failed */ }
43+
}
44+
}
45+
}

src/imp/openssl.rs

+34-5
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)]
@@ -117,13 +116,17 @@ fn load_android_root_certs(connector: &mut SslContextBuilder) -> Result<(), Erro
117116
pub enum Error {
118117
Normal(ErrorStack),
119118
Ssl(ssl::Error, X509VerifyResult),
119+
EmptyChain,
120+
NotPkcs8,
120121
}
121122

122123
impl error::Error for Error {
123124
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
124125
match *self {
125126
Error::Normal(ref e) => error::Error::source(e),
126127
Error::Ssl(ref e, _) => error::Error::source(e),
128+
Error::EmptyChain => None,
129+
Error::NotPkcs8 => None,
127130
}
128131
}
129132
}
@@ -134,6 +137,11 @@ impl fmt::Display for Error {
134137
Error::Normal(ref e) => fmt::Display::fmt(e, fmt),
135138
Error::Ssl(ref e, X509VerifyResult::OK) => fmt::Display::fmt(e, fmt),
136139
Error::Ssl(ref e, v) => write!(fmt, "{} ({})", e, v),
140+
Error::EmptyChain => write!(
141+
fmt,
142+
"at least one certificate must be provided to create an identity"
143+
),
144+
Error::NotPkcs8 => write!(fmt, "expected PKCS#8 PEM"),
137145
}
138146
}
139147
}
@@ -158,9 +166,24 @@ impl Identity {
158166
Ok(Identity {
159167
pkey: parsed.pkey,
160168
cert: parsed.cert,
161-
chain: parsed.chain.into_iter().flatten().collect(),
169+
// > The stack is the reverse of what you might expect due to the way
170+
// > PKCS12_parse is implemented, so we need to load it backwards.
171+
// > https://github.com/sfackler/rust-native-tls/commit/05fb5e583be589ab63d9f83d986d095639f8ec44
172+
chain: parsed.chain.into_iter().flatten().rev().collect(),
162173
})
163174
}
175+
176+
pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result<Identity, Error> {
177+
if !key.starts_with(b"-----BEGIN PRIVATE KEY-----") {
178+
return Err(Error::NotPkcs8);
179+
}
180+
181+
let pkey = PKey::private_key_from_pem(key)?;
182+
let mut cert_chain = X509::stack_from_pem(buf)?.into_iter();
183+
let cert = cert_chain.next().ok_or(Error::EmptyChain)?;
184+
let chain = cert_chain.collect();
185+
Ok(Identity { pkey, cert, chain })
186+
}
164187
}
165188

166189
#[derive(Clone)]
@@ -258,7 +281,10 @@ impl TlsConnector {
258281
if let Some(ref identity) = builder.identity {
259282
connector.set_certificate(&identity.0.cert)?;
260283
connector.set_private_key(&identity.0.pkey)?;
261-
for cert in identity.0.chain.iter().rev() {
284+
for cert in identity.0.chain.iter() {
285+
// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html
286+
// specifies that "When sending a certificate chain, extra chain certificates are
287+
// sent in order following the end entity certificate."
262288
connector.add_extra_chain_cert(cert.to_owned())?;
263289
}
264290
}
@@ -342,7 +368,10 @@ impl TlsAcceptor {
342368
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?;
343369
acceptor.set_private_key(&builder.identity.0.pkey)?;
344370
acceptor.set_certificate(&builder.identity.0.cert)?;
345-
for cert in builder.identity.0.chain.iter().rev() {
371+
for cert in builder.identity.0.chain.iter() {
372+
// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html
373+
// specifies that "When sending a certificate chain, extra chain certificates are
374+
// sent in order following the end entity certificate."
346375
acceptor.add_extra_chain_cert(cert.to_owned())?;
347376
}
348377
supported_protocols(builder.min_protocol, builder.max_protocol, &mut acceptor)?;

src/imp/schannel.rs

+177-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
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};
5+
use self::schannel::crypt_prov::{AcquireOptions, ProviderType};
56
use self::schannel::schannel_cred::{Direction, Protocol, SchannelCred};
67
use self::schannel::tls_stream;
78
use std::error;
@@ -93,6 +94,59 @@ impl Identity {
9394

9495
Ok(Identity { cert: identity })
9596
}
97+
98+
pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result<Identity, Error> {
99+
if !key.starts_with(b"-----BEGIN PRIVATE KEY-----") {
100+
return Err(io::Error::new(io::ErrorKind::InvalidInput, "not a PKCS#8 key").into());
101+
}
102+
103+
let mut store = Memory::new()?.into_store();
104+
let mut cert_iter = pem::PemBlock::new(pem).into_iter();
105+
let leaf = cert_iter.next().ok_or_else(|| {
106+
io::Error::new(
107+
io::ErrorKind::InvalidInput,
108+
"at least one certificate must be provided to create an identity",
109+
)
110+
})?;
111+
let cert = CertContext::from_pem(std::str::from_utf8(leaf).map_err(|_| {
112+
io::Error::new(
113+
io::ErrorKind::InvalidInput,
114+
"leaf cert contains invalid utf8",
115+
)
116+
})?)?;
117+
118+
let name = gen_container_name();
119+
let mut options = AcquireOptions::new();
120+
options.container(&name);
121+
let type_ = ProviderType::rsa_full();
122+
123+
let mut container = match options.acquire(type_) {
124+
Ok(container) => container,
125+
Err(_) => options.new_keyset(true).acquire(type_)?,
126+
};
127+
container.import().import_pkcs8_pem(&key)?;
128+
129+
cert.set_key_prov_info()
130+
.container(&name)
131+
.type_(type_)
132+
.keep_open(true)
133+
.key_spec(KeySpec::key_exchange())
134+
.set()?;
135+
let mut context = store.add_cert(&cert, CertAdd::Always)?;
136+
137+
for int_cert in cert_iter {
138+
let certificate = Certificate::from_pem(int_cert)?;
139+
context = store.add_cert(&certificate.0, CertAdd::Always)?;
140+
}
141+
Ok(Identity { cert: context })
142+
}
143+
}
144+
145+
// The name of the container must be unique to have multiple active keys.
146+
fn gen_container_name() -> String {
147+
use std::sync::atomic::{AtomicUsize, Ordering};
148+
static COUNTER: AtomicUsize = AtomicUsize::new(0);
149+
format!("native-tls-{}", COUNTER.fetch_add(1, Ordering::Relaxed))
96150
}
97151

98152
#[derive(Clone)]
@@ -384,3 +438,125 @@ impl<S: io::Read + io::Write> io::Write for TlsStream<S> {
384438
self.0.flush()
385439
}
386440
}
441+
442+
mod pem {
443+
/// Split data by PEM guard lines
444+
pub struct PemBlock<'a> {
445+
pem_block: &'a str,
446+
cur_end: usize,
447+
}
448+
449+
impl<'a> PemBlock<'a> {
450+
pub fn new(data: &'a [u8]) -> PemBlock<'a> {
451+
let s = ::std::str::from_utf8(data).unwrap();
452+
PemBlock {
453+
pem_block: s,
454+
cur_end: s.find("-----BEGIN").unwrap_or(s.len()),
455+
}
456+
}
457+
}
458+
459+
impl<'a> Iterator for PemBlock<'a> {
460+
type Item = &'a [u8];
461+
fn next(&mut self) -> Option<Self::Item> {
462+
let last = self.pem_block.len();
463+
if self.cur_end >= last {
464+
return None;
465+
}
466+
let begin = self.cur_end;
467+
let pos = self.pem_block[begin + 1..].find("-----BEGIN");
468+
self.cur_end = match pos {
469+
Some(end) => end + begin + 1,
470+
None => last,
471+
};
472+
return Some(&self.pem_block[begin..self.cur_end].as_bytes());
473+
}
474+
}
475+
476+
#[test]
477+
fn test_split() {
478+
// Split three certs, CRLF line terminators.
479+
assert_eq!(
480+
PemBlock::new(
481+
b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\
482+
-----BEGIN SECOND-----\r\n-----END SECOND\r\n\
483+
-----BEGIN THIRD-----\r\n-----END THIRD\r\n"
484+
)
485+
.collect::<Vec<&[u8]>>(),
486+
vec![
487+
b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8],
488+
b"-----BEGIN SECOND-----\r\n-----END SECOND\r\n",
489+
b"-----BEGIN THIRD-----\r\n-----END THIRD\r\n"
490+
]
491+
);
492+
// Split three certs, CRLF line terminators except at EOF.
493+
assert_eq!(
494+
PemBlock::new(
495+
b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\
496+
-----BEGIN SECOND-----\r\n-----END SECOND-----\r\n\
497+
-----BEGIN THIRD-----\r\n-----END THIRD-----"
498+
)
499+
.collect::<Vec<&[u8]>>(),
500+
vec![
501+
b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8],
502+
b"-----BEGIN SECOND-----\r\n-----END SECOND-----\r\n",
503+
b"-----BEGIN THIRD-----\r\n-----END THIRD-----"
504+
]
505+
);
506+
// Split two certs, LF line terminators.
507+
assert_eq!(
508+
PemBlock::new(
509+
b"-----BEGIN FIRST-----\n-----END FIRST-----\n\
510+
-----BEGIN SECOND-----\n-----END SECOND\n"
511+
)
512+
.collect::<Vec<&[u8]>>(),
513+
vec![
514+
b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8],
515+
b"-----BEGIN SECOND-----\n-----END SECOND\n"
516+
]
517+
);
518+
// Split two certs, CR line terminators.
519+
assert_eq!(
520+
PemBlock::new(
521+
b"-----BEGIN FIRST-----\r-----END FIRST-----\r\
522+
-----BEGIN SECOND-----\r-----END SECOND\r"
523+
)
524+
.collect::<Vec<&[u8]>>(),
525+
vec![
526+
b"-----BEGIN FIRST-----\r-----END FIRST-----\r" as &[u8],
527+
b"-----BEGIN SECOND-----\r-----END SECOND\r"
528+
]
529+
);
530+
// Split two certs, LF line terminators except at EOF.
531+
assert_eq!(
532+
PemBlock::new(
533+
b"-----BEGIN FIRST-----\n-----END FIRST-----\n\
534+
-----BEGIN SECOND-----\n-----END SECOND"
535+
)
536+
.collect::<Vec<&[u8]>>(),
537+
vec![
538+
b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8],
539+
b"-----BEGIN SECOND-----\n-----END SECOND"
540+
]
541+
);
542+
// Split a single cert, LF line terminators.
543+
assert_eq!(
544+
PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n").collect::<Vec<&[u8]>>(),
545+
vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8]]
546+
);
547+
// Split a single cert, LF line terminators except at EOF.
548+
assert_eq!(
549+
PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----").collect::<Vec<&[u8]>>(),
550+
vec![b"-----BEGIN FIRST-----\n-----END FIRST-----" as &[u8]]
551+
);
552+
// (Don't) split garbage.
553+
assert_eq!(
554+
PemBlock::new(b"junk").collect::<Vec<&[u8]>>(),
555+
Vec::<&[u8]>::new()
556+
);
557+
assert_eq!(
558+
PemBlock::new(b"junk-----BEGIN garbage").collect::<Vec<&[u8]>>(),
559+
vec![b"-----BEGIN garbage" as &[u8]]
560+
);
561+
}
562+
}

0 commit comments

Comments
 (0)