Skip to content

Commit

Permalink
Bring in MbedTls certificate verification
Browse files Browse the repository at this point in the history
Add verification of x509 certificate chain.
This verification lacks CRL processing.
  • Loading branch information
nick-mobilecoin committed May 19, 2023
1 parent 96adc4a commit 8701955
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 3 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ rust-version = "1.68"
[profile.release]
lto = "thin"

[patch.crates-io]
# mbedtls patched to allow certificate verification with a profile
mbedtls = { git = "https://github.com/mobilecoinfoundation/rust-mbedtls.git", rev = "6d8fe323a3292f87a6bce4b35963d47139a583f9" }
mbedtls-sys-auto = { git = "https://github.com/mobilecoinfoundation/rust-mbedtls.git", rev = "6d8fe323a3292f87a6bce4b35963d47139a583f9" }

[workspace.metadata.release]
shared-version = true
dev-version-ext = "beta.0"
Expand Down
27 changes: 24 additions & 3 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ unmaintained = "deny"
unsound = "deny"
yanked = "deny"
notice = "warn"
ignore = []
ignore = [
# This comes via mbedtls, but only when
# `[target.x86_64-fortanix-unknown-sgx.dependencies]`
# which we don't use
"RUSTSEC-2020-0071",
]

[licenses]
unlicensed = "deny"
Expand All @@ -27,6 +32,16 @@ default = "deny"
confidence-threshold = 0.8
exceptions = []

[[licenses.clarify]]
# This comes via mbedtls, but only when
# `[target.x86_64-fortanix-unknown-sgx.dependencies]`
# which we don't use. It's license is a BSD with a no nuclear clause
name = "rs-libc"
expression = "BSD-3-Clause"
license-files = [
{ path = "LICENSE", hash = 0x7933df3c },
]

[bans]
multiple-versions = "warn"
# Lint level for when a crate version requirement is `*`
Expand All @@ -38,17 +53,23 @@ deny = [
{ name = "ring" },
]
skip = [

# Workaround for path only dependencies,
# https://github.com/EmbarkStudios/cargo-deny/issues/241
# { name = "some/dev/only/path" },
]
skip-tree = [ ]
skip-tree = [
# mbedtls is held back a bit so it has duplicate versions
{ name = "mbedtls", version = "=0.8.1" },
]

[sources]
unknown-registry = "warn"
unknown-git = "warn"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []
allow-git = [
"https://github.com/mobilecoinfoundation/rust-mbedtls.git",
]

[sources.allow-org]
github = []
Expand Down
2 changes: 2 additions & 0 deletions verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ rust-version = { workspace = true }
alloc = ["pem-rfc7468/alloc", "dep:const-oid", "dep:p256", "dep:x509-cert", "tcb"]
tcb = ["dep:p256", "dep:serde_json", "dep:serde", "dep:der", "dep:hex", "advisories"]
advisories = ["dep:serde_json"]
x509 = ["dep:mbedtls"]

[dependencies]
const-oid = { version = "0.9.2", default-features = false, optional = true }
der = { version = "0.7.5", default-features = false, optional = true }
displaydoc = { version = "0.2.1", default-features = false }
hex = { version = "0.4.3", default-features = false, features = ["serde", "alloc"], optional = true }
mbedtls = { version = "0.8.1", default-features = false, features = ["no_std_deps"], optional = true }
mc-sgx-core-types = "0.6.0"
p256 = { version = "0.13.0", default-features = false, features = ["ecdsa"], optional = true }
pem-rfc7468 = { version = "0.7.0", default-features = false, optional = true }
Expand Down
174 changes: 174 additions & 0 deletions verifier/src/x509.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,179 @@ mod certs;
mod crl;
mod error;

extern crate alloc;
use alloc::string::String;
use alloc::vec;
pub use error::Error;
pub type Result<T> = core::result::Result<T, Error>;

use mbedtls::{
alloc::List as MbedtlsList,
hash::Type as HashType,
pk::{EcGroupId, Type as PkType},
x509::{Certificate, Profile},
};

#[derive(Clone)]
pub struct TrustAnchor(MbedtlsList<Certificate>);

/// Try to get a trust anchor from a PEM-encoded string.
///
/// # Errors
/// `Error::MbedTls` if the string is not valid PEM certificate.
impl TryFrom<&str> for TrustAnchor {
type Error = Error;

fn try_from(pem: &str) -> Result<Self> {
Self::try_from(String::from(pem))
}
}

/// Try to get a trust anchor from a PEM-encoded string.
///
/// # Errors
/// `Error::MbedTls` if the string is not valid PEM certificate.
impl TryFrom<String> for TrustAnchor {
type Error = Error;

fn try_from(mut pem: String) -> Result<Self> {
// Null terminate for Mbedtls
pem.push('\0');
let certs = Certificate::from_pem_multiple(pem.as_bytes())?;
Ok(Self(certs))
}
}

/// An unverified certificate chain.
///
/// This is mostly opaque meant to be used to verify and create a
/// [`VerifiedCertChain`].
#[derive(Clone)]
pub struct UnverifiedCertChain(MbedtlsList<Certificate>);

impl UnverifiedCertChain {
/// Verify the certificate chain is valid for the given `trust_anchor`.
///
/// # Errors
/// `Error::MbedTls` if the certificate chain is not valid.
pub fn verify(self, trust_anchor: &TrustAnchor) -> Result<VerifiedCertChain> {
let profile = Profile::new(
vec![HashType::Sha256, HashType::Sha384, HashType::Sha512],
// The note on `PkType::Ecdsa` is a lie:
//
// > This type is never returned by the mbedTLS key parsing routines
//
// It comes back when using the Intel cert chain.
vec![PkType::Rsa, PkType::Eckey, PkType::Ecdsa],
vec![
EcGroupId::Curve25519,
EcGroupId::SecP256K1,
EcGroupId::SecP256R1,
EcGroupId::SecP384R1,
EcGroupId::SecP521R1,
],
2048,
);
Certificate::verify_with_profile(&self.0, &trust_anchor.0, None, Some(&profile), None)?;
Ok(VerifiedCertChain(self.0))
}
}

/// Try to get a certificate chain from a PEM-encoded string.
///
/// # Errors
/// `Error::MbedTls` if the string is not valid PEM certificate(s).
impl TryFrom<&str> for UnverifiedCertChain {
type Error = Error;

fn try_from(pem: &str) -> Result<Self> {
Self::try_from(String::from(pem))
}
}

/// Try to get a certificate chain from a PEM-encoded string.
///
/// # Errors
/// `Error::MbedTls` if the string is not valid PEM certificate(s).
impl TryFrom<String> for UnverifiedCertChain {
type Error = Error;

fn try_from(mut pem: String) -> Result<Self> {
// Null terminate for Mbedtls
pem.push('\0');
let certs = Certificate::from_pem_multiple(pem.as_bytes())?;
Ok(Self(certs))
}
}

/// A verified certificate chain.
///
/// See [`UnverifiedCertChain::verify`] for creating one.
pub struct VerifiedCertChain(MbedtlsList<Certificate>);

#[cfg(test)]
mod test {
use super::*;
use alloc::format;

const LEAF_CERT: &str = include_str!("../data/tests/leaf_cert.pem");
const PROCESSOR_CA: &str = include_str!("../data/tests/processor_ca.pem");
const ROOT_CA: &str = include_str!("../data/tests/root_ca.pem");

#[test]
fn cert_chain_from_one_pem_cert() {
let cert_chain =
UnverifiedCertChain::try_from(LEAF_CERT).expect("failed to parse cert chain");
// Counting manually, because `MbedtlsList` is a linked list without
// `len()` method.
let count = cert_chain.0.iter().fold(0, |sum, _| sum + 1);
assert_eq!(count, 1);
}

#[test]
fn cert_chain_from_two_pem_certs() {
let raw_pems = format!("{LEAF_CERT}\n{PROCESSOR_CA}");
let cert_chain =
UnverifiedCertChain::try_from(raw_pems.as_str()).expect("failed to parse cert chain");
let count = cert_chain.0.iter().fold(0, |sum, _| sum + 1);
assert_eq!(count, 2);
}

#[test]
fn cert_chain_from_invalid_pem_cert() {
assert!(matches!(
UnverifiedCertChain::try_from(&LEAF_CERT[1..]),
Err(Error::MbedTls(_))
));
}

#[test]
fn verify_valid_cert_chain() {
let raw_pems = format!("{LEAF_CERT}\n{PROCESSOR_CA}\n{ROOT_CA}");
let cert_chain =
UnverifiedCertChain::try_from(raw_pems.as_str()).expect("failed to parse cert chain");
let trust_anchor = TrustAnchor::try_from(ROOT_CA).expect("failed to parse root cert");
assert_eq!(cert_chain.verify(&trust_anchor).is_ok(), true);
}

#[test]
fn invalid_cert_chain() {
let raw_pems = format!("{LEAF_CERT}\n{ROOT_CA}");
let cert_chain =
UnverifiedCertChain::try_from(raw_pems.as_str()).expect("failed to parse cert chain");
let trust_anchor = TrustAnchor::try_from(ROOT_CA).expect("failed to parse root cert");
assert!(matches!(
cert_chain.verify(&trust_anchor),
Err(Error::MbedTls(_))
));
}

#[test]
fn unordered_cert_chain_succeeds() {
let raw_pems = format!("{PROCESSOR_CA}\n{ROOT_CA}\n{LEAF_CERT}");
let cert_chain =
UnverifiedCertChain::try_from(raw_pems.as_str()).expect("failed to parse cert chain");
let trust_anchor = TrustAnchor::try_from(ROOT_CA).expect("failed to parse root cert");
assert_eq!(cert_chain.verify(&trust_anchor).is_ok(), true);
}
}
8 changes: 8 additions & 0 deletions verifier/src/x509/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/// Error type for decoding and verifying certificates.
#[derive(Debug, displaydoc::Display, PartialEq, Eq)]
pub enum Error {
/// An error occurred working with MbedTls
MbedTls(mbedtls::Error),
/// An error occurred decoding the signature from a certificate
SignatureDecoding,
/// The certification signature does not match with the verifying key
Expand All @@ -26,3 +28,9 @@ impl From<x509_cert::der::Error> for Error {
Error::DerDecoding(src)
}
}

impl From<mbedtls::Error> for Error {
fn from(src: mbedtls::Error) -> Self {
Error::MbedTls(src)
}
}

0 comments on commit 8701955

Please sign in to comment.