Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rustls-mbedpki-provider crate #1

Merged
merged 7 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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
Copy link
Collaborator

Choose a reason for hiding this comment

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

I also recommend to include Cargo.lock into repo to ensure a more stable build/CI.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, for lib crates is that the preferred approach? I ask because I think lib crates should work with any sem-ver-compatible version of their dependencies.

Copy link
Collaborator

@Taowyoo Taowyoo Nov 1, 2023

Choose a reason for hiding this comment

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

I think the reason is mainly for CI. It help trace the dependencies and build error when incomparable issue happened (like GitHub's dependent bot will help check update and security vulnerability based on Cargo.toml and Cargo.lock ).
For user of this crates, they always generate a new lock file their project.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I will merge this crate into workspace after the crypto provider PR is merged

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see. I guess my point is: the libs should work with newer sem-ver compatible versions of their dependencies as well (which is what could happen in the CI). But I see your point. We don't want a situation where CI passes today and fails tomorrow while our code has not changed.

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