Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ readme = "README.md"
[features]
vendored = ["openssl/vendored"]

[dependencies]
lazy_static = "1.0"
rustc-serialize = "0.3"

[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
security-framework = "0.3.1"
security-framework-sys = "0.3.1"
lazy_static = "1.0"
libc = "0.2"
tempfile = "3.0"

Expand Down
43 changes: 43 additions & 0 deletions examples/simple-server-pkcs8.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
extern crate native_tls;

use native_tls::{Identity, TlsAcceptor, TlsStream};
use std::fs::File;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::Arc;
use std::thread;

fn main() {
let mut cert_file = File::open("test/cert.pem").unwrap();
let mut certs = vec![];
cert_file.read_to_end(&mut certs).unwrap();
let mut key_file = File::open("test/key.pem").unwrap();
let mut key = vec![];
key_file.read_to_end(&mut key).unwrap();
let pkcs8 = Identity::from_pkcs8(&certs, &key).unwrap();

let acceptor = TlsAcceptor::new(pkcs8).unwrap();
let acceptor = Arc::new(acceptor);

let listener = TcpListener::bind("0.0.0.0:8443").unwrap();

fn handle_client(mut stream: TlsStream<TcpStream>) {
let mut buf = [0; 1024];
let read = stream.read(&mut buf).unwrap();
let received = std::str::from_utf8(&buf[0..read]).unwrap();
stream.write_all(format!("received '{}'", received).as_bytes()).unwrap();
}

for stream in listener.incoming() {
match stream {
Ok(stream) => {
let acceptor = acceptor.clone();
thread::spawn(move || {
let stream = acceptor.accept(stream).unwrap();
handle_client(stream);
});
}
Err(_e) => { /* connection failed */ }
}
}
}
31 changes: 27 additions & 4 deletions src/imp/openssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use self::openssl::error::ErrorStack;
use self::openssl::hash::MessageDigest;
use self::openssl::nid::Nid;
use self::openssl::pkcs12::Pkcs12;
use self::openssl::pkey::PKey;
use self::openssl::pkey::{PKey, Private};
use self::openssl::ssl::{
self, MidHandshakeSslStream, SslAcceptor, SslConnector, SslContextBuilder, SslMethod,
SslVerifyMode,
Expand All @@ -15,9 +15,9 @@ use std::error;
use std::fmt;
use std::io;
use std::sync::{Once, ONCE_INIT};
use pem;

use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder};
use self::openssl::pkey::Private;

#[cfg(have_min_max_version)]
fn supported_protocols(
Expand Down Expand Up @@ -168,6 +168,23 @@ impl Identity {
chain: parsed.chain.into_iter().flat_map(|x| x).collect(),
})
}

pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result<Identity, Error> {
let pkey = PKey::private_key_from_pem(key)?;
let mut cert_chain = vec!();
for buf in pem::PemBlock::new(buf) {
cert_chain.push(X509::from_pem(buf)?);
}
let mut cert_chain = cert_chain.into_iter();
let cert = cert_chain.next();
let chain = cert_chain.collect();
Ok(Identity {
pkey,
// an identity must have at least one certificate, the leaf cert
cert: cert.expect("at least one certificate must be provided to create an identity"),
chain: chain,
})
}
}

#[derive(Clone)]
Expand Down Expand Up @@ -265,7 +282,10 @@ impl TlsConnector {
if let Some(ref identity) = builder.identity {
connector.set_certificate(&identity.0.cert)?;
connector.set_private_key(&identity.0.pkey)?;
for cert in identity.0.chain.iter().rev() {
for cert in identity.0.chain.iter() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not believe this change is correct: 05fb5e5

Copy link
Contributor Author

@Goirad Goirad Jan 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When sending a certificate chain, extra chain certificates are sent in order following the end entity certificate.

This is from https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html , though looking at https://www.openssl.org/docs/manmaster/man3/PKCS12_parse.html I don't see any documentation about ordering restrictions for PKCS12_parse.
Edit: Looking again I think you might mean that the return certs from PKCS12_parse are in the opposite order, which seems awkward. Either the from_pkcs12 method or the from_pkcs8 method will need to reverse the certificate chain before creating the Identity if that is the case.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There really needs to be a cross-platform test for this behavior.

// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html
// specifies that "When sending a certificate chain, extra chain certificates are
// sent in order following the end entity certificate."
connector.add_extra_chain_cert(cert.to_owned())?;
}
}
Expand Down Expand Up @@ -314,7 +334,10 @@ impl TlsAcceptor {
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?;
acceptor.set_private_key(&builder.identity.0.pkey)?;
acceptor.set_certificate(&builder.identity.0.cert)?;
for cert in builder.identity.0.chain.iter().rev() {
for cert in builder.identity.0.chain.iter() {
// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html
// specifies that "When sending a certificate chain, extra chain certificates are
// sent in order following the end entity certificate."
acceptor.add_extra_chain_cert(cert.to_owned())?;
}
supported_protocols(builder.min_protocol, builder.max_protocol, &mut acceptor)?;
Expand Down
36 changes: 35 additions & 1 deletion src/imp/schannel.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
extern crate schannel;

use self::schannel::cert_context::{CertContext, HashAlgorithm};
use self::schannel::cert_context::{CertContext, HashAlgorithm, KeySpec};
use self::schannel::cert_store::{CertAdd, CertStore, Memory, PfxImportOptions};
use self::schannel::schannel_cred::{Direction, Protocol, SchannelCred};
use self::schannel::crypt_prov::{AcquireOptions, ProviderType};
use self::schannel::tls_stream;
use std::error;
use std::fmt;
Expand Down Expand Up @@ -96,6 +97,39 @@ impl Identity {

Ok(Identity { cert: identity })
}

pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result<Identity, Error> {
let mut store = Memory::new()?.into_store();
let mut cert_iter = crate::pem::PemBlock::new(pem).into_iter();
let leaf = cert_iter.next().expect("at least one certificate must be provided to create an identity");
let cert = CertContext::from_pem(std::str::from_utf8(leaf).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "leaf cert contains invalid utf8"))?)?;

let mut options = AcquireOptions::new();
options.container("schannel");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I seem to remember there being problems on windows if the same container name is used multiple times. Probably worth a test that parses a couple of identities to confirm.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the test case that caused issues on my previous attempt: b0b9164

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test still needs to be added.

let type_ = ProviderType::rsa_full();

let mut container = match options.acquire(type_) {
Ok(container) => container,
Err(_) => options.new_keyset(true).acquire(type_)?,
};
let key = crate::pem::pem_to_der(key, Some(crate::pem::PEM_PRIVATE_KEY)).expect("invalid PKCS8 key provided");
container.import()
.import_pkcs8(&key)?;

cert.set_key_prov_info()
.container("schannel")
.type_(type_)
.keep_open(true)
.key_spec(KeySpec::key_exchange())
.set()?;
let mut context = store.add_cert(&cert, CertAdd::Always)?;

for int_cert in cert_iter {
let certificate = Certificate::from_pem(int_cert)?;
context = store.add_cert(&certificate.0, schannel::cert_store::CertAdd::Always)?;
}
Ok(Identity{cert: context})
}
}

#[derive(Clone)]
Expand Down
29 changes: 29 additions & 0 deletions src/imp/security_framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use self::security_framework::os::macos::certificate::{PropertyType, SecCertific
#[cfg(not(target_os = "ios"))]
use self::security_framework::os::macos::certificate_oids::CertificateOid;
#[cfg(not(target_os = "ios"))]
use self::security_framework::os::macos::identity::SecIdentityExt;
#[cfg(not(target_os = "ios"))]
use self::security_framework::os::macos::import_export::{ImportOptions, SecItems};
#[cfg(not(target_os = "ios"))]
use self::security_framework::os::macos::keychain::{self, KeychainSettings, SecKeychain};
Expand Down Expand Up @@ -85,6 +87,33 @@ pub struct Identity {
}

impl Identity {
pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result<Identity, Error> {
let dir = TempDir::new().unwrap();
let keychain = keychain::CreateOptions::new()
.password("password")
.create(dir.path().join("identity.keychain"))?;

let mut items = SecItems::default();

ImportOptions::new()
.filename("key.pem")
.items(&mut items)
.keychain(&keychain)
.import(&key)?;

ImportOptions::new()
.filename("chain.pem")
.items(&mut items)
.keychain(&keychain)
.import(&pem)?;

let ident = SecIdentity::with_certificate(&[keychain], &items.certificates[0])?;
Ok(Identity {
identity: ident,
chain: items.certificates
})
}

pub fn from_pkcs12(buf: &[u8], pass: &str) -> Result<Identity, Error> {
let mut imports = Identity::import_options(buf, pass)?;
let import = imports.pop().unwrap();
Expand Down
17 changes: 16 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
//! * TLS/SSL client communication
//! * TLS/SSL server communication
//! * PKCS#12 encoded identities
//! * PKCS#8 encoded identities
//! * Secure-by-default for client and server
//! * Includes hostname verification for clients
//! * Supports asynchronous I/O for both the server and the client
Expand Down Expand Up @@ -97,12 +98,14 @@
#![warn(missing_docs)]

#[macro_use]
#[cfg(any(target_os = "macos", target_os = "ios"))]
extern crate lazy_static;

#[cfg(test)]
extern crate hex;

extern crate rustc_serialize;
mod pem;

use std::any::Any;
use std::error;
use std::fmt;
Expand Down Expand Up @@ -183,6 +186,18 @@ impl Identity {
let identity = imp::Identity::from_pkcs12(der, password)?;
Ok(Identity(identity))
}

/// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first.
/// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate.
///
/// The certificate chain should contain any intermediate cerficates that should be sent to
/// clients to allow them to build a chain to a trusted root.
///
/// A certificate chain here means a series of PEM encoded certificates concatenated together.
pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result<Identity> {
let identity = imp::Identity::from_pkcs8(pem, key)?;
Ok(Identity(identity))
}
}

/// An X509 certificate.
Expand Down
Loading