Skip to content

Commit

Permalink
Add rustls-mbedpki-provider crate (#1)
Browse files Browse the repository at this point in the history
* Create ci.yml

* Add rustls-mbedpki-provider crate

* Formatting changes

* Check for mbedpki-provider warnings in ci.yml

* Implement signature verification methods of `ClientCertVerifier` and `ServerSertVerifier`

* Address review comments

* Add doc comments to public items

---------

Co-authored-by: Arash Sahebolamri <[email protected]>
  • Loading branch information
s-arash and Arash Sahebolamri authored Nov 1, 2023
1 parent aaf0d87 commit 9931669
Show file tree
Hide file tree
Showing 13 changed files with 1,150 additions and 0 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Rust

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

env:
CARGO_TERM_COLOR: always

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: check mbedpki-provider
working-directory: rustls-mbedpki-provider
env:
RUSTFLAGS: -D warnings
run: cargo check

- name: Run mbedpki-provider tests
working-directory: rustls-mbedpki-provider
run: cargo test
3 changes: 3 additions & 0 deletions .rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
chain_width=40
max_width = 128
struct_lit_width = 80
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# rustls-mbedtls-provider

This repository will contain code to allow [mbedtls](https://github.com/fortanix/rust-mbedtls) to be used
as the crypto and PKI provider for [rustls](https://github.com/rustls/rustls).

## PKI provider
Implements [`ClientCertVerifier`](https://docs.rs/rustls/latest/rustls/server/trait.ClientCertVerifier.html) and [`ClientCertVerifier`](https://docs.rs/rustls/latest/rustls/client/trait.ServerCertVerifier.html) traits from `rustls` using mbedtls.


# Contributing

Expand Down
16 changes: 16 additions & 0 deletions rustls-mbedpki-provider/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "rustls-mbedpki-provider"
version = "0.1.0"
edition = "2021"
description = "Implements rustls PKI traits using mbedtls"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rustls = {version = "0.21", features = ["dangerous_configuration"]}
mbedtls = {version = "0.12.0-alpha.1", features = ["x509", "chrono", "std"], default_features = false}

x509-parser = "0.15"
chrono = "0.4"

[dev-dependencies]
rustls-pemfile = "1.0"
244 changes: 244 additions & 0 deletions rustls-mbedpki-provider/src/client_cert_verifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
use std::time::SystemTime;

use rustls::{
server::{ClientCertVerified, ClientCertVerifier},
DistinguishedName,
};

use crate::{
mbedtls_err_into_rustls_err, mbedtls_err_into_rustls_err_with_error_msg, rustls_cert_to_mbedtls_cert,
verify_certificates_active, verify_tls_signature,
};

/// A `rustls` `ClientCertVerifier` implemented using the PKI functionality of
/// `mbedtls`
#[derive(Clone)]
pub struct MbedTlsClientCertVerifier {
trusted_cas: mbedtls::alloc::List<mbedtls::x509::Certificate>,
root_subjects: Vec<rustls::DistinguishedName>,
}

impl MbedTlsClientCertVerifier {
/// Constructs a new `MbedTlsClientCertVerifier` object given the provided trusted certificate authority
/// certificates.
///
/// Returns an error if any of the certificates are invalid.
pub fn new<'a>(trusted_cas: impl IntoIterator<Item = &'a rustls::Certificate>) -> mbedtls::Result<Self> {
let trusted_cas = trusted_cas
.into_iter()
.map(rustls_cert_to_mbedtls_cert)
.collect::<mbedtls::Result<Vec<_>>>()?
.into_iter()
.collect();
Self::new_from_mbedtls_trusted_cas(trusted_cas)
}

/// Constructs a new `MbedTlsClientCertVerifier` object given the provided trusted certificate authority
/// certificates.
pub fn new_from_mbedtls_trusted_cas(
trusted_cas: mbedtls::alloc::List<mbedtls::x509::Certificate>,
) -> mbedtls::Result<Self> {
let mut root_subjects = vec![];
for ca in trusted_cas.iter() {
root_subjects.push(DistinguishedName::from(ca.subject_raw()?));
}
Ok(Self { trusted_cas, root_subjects })
}

/// The certificate authority certificates used to construct this object
pub fn trusted_cas(&self) -> &mbedtls::alloc::List<mbedtls::x509::Certificate> {
&self.trusted_cas
}

/// the Subjects of the client authentication trust anchors to share with the client when
/// requesting client authentication, extractd from CA certificates.
pub fn root_subjects(&self) -> &[DistinguishedName] {
self.root_subjects.as_ref()
}
}

impl ClientCertVerifier for MbedTlsClientCertVerifier {
fn client_auth_root_subjects(&self) -> &[DistinguishedName] {
&self.root_subjects
}

fn verify_client_cert(
&self,
end_entity: &rustls::Certificate,
intermediates: &[rustls::Certificate],
now: SystemTime,
) -> Result<rustls::server::ClientCertVerified, rustls::Error> {
let now = chrono::DateTime::<chrono::Local>::from(now).naive_local();

let chain: mbedtls::alloc::List<_> = [end_entity]
.into_iter()
.chain(intermediates)
.map(rustls_cert_to_mbedtls_cert)
.collect::<mbedtls::Result<Vec<_>>>()
.map_err(mbedtls_err_into_rustls_err)?
.into_iter()
.collect();

verify_certificates_active(chain.iter().map(|c| &**c), now)?;

let mut error_msg = String::default();
mbedtls::x509::Certificate::verify(&chain, &self.trusted_cas, None, Some(&mut error_msg))
.map_err(|e| mbedtls_err_into_rustls_err_with_error_msg(e, &error_msg))?;

Ok(ClientCertVerified::assertion())
}

fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::Certificate,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::HandshakeSignatureValid, rustls::Error> {
verify_tls_signature(message, cert, dss, false)
}

fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::Certificate,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::HandshakeSignatureValid, rustls::Error> {
verify_tls_signature(message, cert, dss, true)
}
}

// ../test-data/rsa/client.fullchain has these three certificates:
// cert subject: CN=ponytown client, cert issuer: CN=ponytown RSA level 2 intermediate
// cert subject: CN=ponytown RSA level 2 intermediate, cert issuer: CN=ponytown RSA CA
// cert subject: CN=ponytown RSA CA, cert issuer: CN=ponytown RSA CA
#[cfg(test)]
mod tests {

use chrono::DateTime;
use rustls::{
server::ClientCertVerifier, Certificate, ClientConfig, ClientConnection, RootCertStore, ServerConfig, ServerConnection,
};
use std::{sync::Arc, time::SystemTime};

use super::MbedTlsClientCertVerifier;
use crate::tests_common::{do_handshake_until_error, get_chain, get_key};

fn server_config_with_verifier(client_cert_verifier: MbedTlsClientCertVerifier) -> ServerConfig {
ServerConfig::builder()
.with_safe_defaults()
.with_client_cert_verifier(Arc::new(client_cert_verifier))
.with_single_cert(
get_chain(include_bytes!("../test-data/rsa/end.fullchain")),
get_key(include_bytes!("../test-data/rsa/end.key")),
)
.unwrap()
}

#[test]
fn connection_client_cert_verifier() {
let client_config = ClientConfig::builder().with_safe_defaults();
let root_ca = Certificate(include_bytes!("../test-data/rsa/ca.der").to_vec());
let mut root_store = RootCertStore::empty();
root_store.add(&root_ca).unwrap();
let cert_chain = get_chain(include_bytes!("../test-data/rsa/client.fullchain"));

let client_config = client_config
.with_root_certificates(root_store)
.with_client_auth_cert(cert_chain, get_key(include_bytes!("../test-data/rsa/client.key")))
.unwrap();

let client_cert_verifier = MbedTlsClientCertVerifier::new([&root_ca]).unwrap();

let server_config = server_config_with_verifier(client_cert_verifier);

let mut client_conn = ClientConnection::new(Arc::new(client_config), "localhost".try_into().unwrap()).unwrap();
let mut server_conn = ServerConnection::new(Arc::new(server_config)).unwrap();

assert!(do_handshake_until_error(&mut client_conn, &mut server_conn).is_ok());
}

fn test_connection_client_cert_verifier_with_invalid_certs(invalid_cert_chain: Vec<Certificate>) {
let client_config = ClientConfig::builder().with_safe_defaults();
let root_ca = Certificate(include_bytes!("../test-data/rsa/ca.der").to_vec());
let mut root_store = RootCertStore::empty();
root_store.add(&root_ca).unwrap();

let client_config = client_config
.with_root_certificates(root_store)
.with_client_auth_cert(invalid_cert_chain, get_key(include_bytes!("../test-data/rsa/client.key")))
.unwrap();

let client_cert_verifier = MbedTlsClientCertVerifier::new([&root_ca]).unwrap();

let server_config = server_config_with_verifier(client_cert_verifier);

let mut client_conn = ClientConnection::new(Arc::new(client_config), "localhost".try_into().unwrap()).unwrap();
let mut server_conn = ServerConnection::new(Arc::new(server_config)).unwrap();

let res = do_handshake_until_error(&mut client_conn, &mut server_conn);
assert!(matches!(res, Err(rustls::Error::InvalidCertificate(_))));
}

#[test]
fn connection_client_cert_verifier_with_invalid_certs() {
let cert_chain = get_chain(include_bytes!("../test-data/rsa/client.fullchain"));

let mut invalid_chain1 = cert_chain.clone();
invalid_chain1.remove(1);

let mut invalid_chain2 = cert_chain.clone();
invalid_chain2.remove(0);

let mut invalid_chain3 = cert_chain.clone();
invalid_chain3.swap(0, 1);

for invalid_chain in [invalid_chain1, invalid_chain2, invalid_chain3] {
test_connection_client_cert_verifier_with_invalid_certs(invalid_chain);
}
}

#[test]
fn client_cert_verifier_valid_chain() {
let cert_chain = get_chain(include_bytes!("../test-data/rsa/client.fullchain"));
let trusted_cas = [Certificate(include_bytes!("../test-data/rsa/ca.der").to_vec())];

let verifier = MbedTlsClientCertVerifier::new(trusted_cas.iter()).unwrap();

let now = SystemTime::from(chrono::DateTime::parse_from_rfc3339("2023-11-26T12:00:00+00:00").unwrap());

assert!(verifier
.verify_client_cert(&cert_chain[0], &cert_chain[1..], now)
.is_ok());
}

#[test]
fn client_cert_verifier_broken_chain() {
let mut cert_chain = get_chain(include_bytes!("../test-data/rsa/client.fullchain"));
cert_chain.remove(1);
let trusted_cas = [Certificate(include_bytes!("../test-data/rsa/ca.der").to_vec())];

let verifier = MbedTlsClientCertVerifier::new(trusted_cas.iter()).unwrap();

let now = SystemTime::from(DateTime::parse_from_rfc3339("2023-11-26T12:00:00+00:00").unwrap());

let verify_res = verifier.verify_client_cert(&cert_chain[0], &cert_chain[1..], now);
assert!(matches!(verify_res, Err(rustls::Error::InvalidCertificate(_))));
}

#[test]
fn client_cert_verifier_expired_certs() {
let cert_chain = get_chain(include_bytes!("../test-data/rsa/client.fullchain"));
let trusted_cas = [Certificate(include_bytes!("../test-data/rsa/ca.der").to_vec())];

let verifier = MbedTlsClientCertVerifier::new(trusted_cas.iter()).unwrap();

let now = SystemTime::from(DateTime::parse_from_rfc3339("2052-11-26T12:00:00+00:00").unwrap());

assert_eq!(
verifier
.verify_client_cert(&cert_chain[0], &cert_chain[1..], now)
.unwrap_err(),
rustls::Error::InvalidCertificate(rustls::CertificateError::Expired)
);
}
}
Loading

0 comments on commit 9931669

Please sign in to comment.