From 687cf04d47ce311c08d4ba03000eac63300b560a Mon Sep 17 00:00:00 2001 From: Nick Santana Date: Thu, 27 Apr 2023 09:57:24 -0700 Subject: [PATCH] Add logic to verify and get signing key from chain --- verifier/src/x509.rs | 1 + verifier/src/x509/certs.rs | 115 +++++++++++++++++++----- verifier/src/x509/chain.rs | 174 +++++++++++++++++++++++++++++++++++++ verifier/src/x509/crl.rs | 162 +++++++++++++++++++++------------- verifier/src/x509/error.rs | 8 ++ 5 files changed, 377 insertions(+), 83 deletions(-) create mode 100644 verifier/src/x509/chain.rs diff --git a/verifier/src/x509.rs b/verifier/src/x509.rs index 4f35502..5a1a7cb 100644 --- a/verifier/src/x509.rs +++ b/verifier/src/x509.rs @@ -3,6 +3,7 @@ // TODO: Remove dead_code exception once this is connected up to the rest of the codebase #![allow(dead_code)] mod certs; +mod chain; mod crl; mod error; diff --git a/verifier/src/x509/certs.rs b/verifier/src/x509/certs.rs index e434a51..942ef99 100644 --- a/verifier/src/x509/certs.rs +++ b/verifier/src/x509/certs.rs @@ -2,7 +2,10 @@ //! Verifier(s) for [`CertificationData`](`mc-sgx-dcap-types::CertificationData`). +extern crate alloc; + use super::{Error, Result}; +use alloc::vec::Vec; use core::time::Duration; use p256::ecdsa::signature::Verifier; use p256::ecdsa::{Signature, VerifyingKey}; @@ -11,28 +14,36 @@ use x509_cert::Certificate as X509Certificate; /// A certificate whose signature has not been verified. #[derive(Debug, PartialEq, Eq)] -pub struct UnverifiedCertificate<'a> { +pub struct UnverifiedCertificate { // In order to verify the signature, we need to access the original DER // bytes - der_bytes: &'a [u8], - certificate: X509Certificate, + der_bytes: Vec, + pub(crate) certificate: X509Certificate, // The signature and key are persisted here since they are fallible // operations and it's more ergonomic to fail fast than fail later for a // bad key or signature signature: Signature, - pub(crate) key: VerifyingKey, + key: VerifyingKey, } /// A certificate whose signature has been verified. #[derive(Debug, PartialEq, Eq)] -pub struct VerifiedCertificate<'a> { - _der_bytes: &'a [u8], +pub struct VerifiedCertificate { _certificate: X509Certificate, - _signature: Signature, - _key: VerifyingKey, + key: VerifyingKey, +} + +impl VerifiedCertificate { + pub(crate) fn public_key(&self) -> VerifyingKey { + self.key + } } -impl<'a> UnverifiedCertificate<'a> { +impl UnverifiedCertificate { + pub fn verify_self_signed(&self, unix_time: Duration) -> Result { + self.verify(&self.key, unix_time) + } + /// Verify the certificate signature and time are valid. /// /// # Arguments @@ -44,19 +55,13 @@ impl<'a> UnverifiedCertificate<'a> { /// SystemTime::now().duration_since(UNIX_EPOCH) /// ``` /// or equivalent - pub fn verify( - self, - key: &VerifyingKey, - unix_time: Duration, - ) -> Result> { + pub fn verify(&self, key: &VerifyingKey, unix_time: Duration) -> Result { self.verify_time(unix_time)?; self.verify_signature(key)?; Ok(VerifiedCertificate { - _der_bytes: self.der_bytes, - _certificate: self.certificate, - _signature: self.signature, - _key: self.key, + _certificate: self.certificate.clone(), + key: self.key, }) } @@ -91,11 +96,21 @@ impl<'a> UnverifiedCertificate<'a> { } } +/// Convert a PEM-encoded certificate into an [`UnverifiedCertificate`]. +impl TryFrom<&str> for UnverifiedCertificate { + type Error = Error; + + fn try_from(pem: &str) -> ::core::result::Result { + let (_, der_bytes) = pem_rfc7468::decode_vec(pem.as_bytes())?; + Self::try_from(&der_bytes[..]) + } +} + /// Convert a DER-encoded certificate into an [`UnverifiedCertificate`]. -impl<'a> TryFrom<&'a [u8]> for UnverifiedCertificate<'a> { +impl TryFrom<&[u8]> for UnverifiedCertificate { type Error = Error; - fn try_from(der_bytes: &'a [u8]) -> ::core::result::Result { + fn try_from(der_bytes: &[u8]) -> ::core::result::Result { let certificate = X509Certificate::from_der(der_bytes)?; let signature_bytes = certificate .signature @@ -113,7 +128,7 @@ impl<'a> TryFrom<&'a [u8]> for UnverifiedCertificate<'a> { ) .map_err(|_| Error::KeyDecoding)?; Ok(UnverifiedCertificate { - der_bytes, + der_bytes: der_bytes.to_vec(), certificate, signature, key, @@ -123,9 +138,8 @@ impl<'a> TryFrom<&'a [u8]> for UnverifiedCertificate<'a> { #[cfg(test)] mod test { - extern crate alloc; - use super::*; + use alloc::string::ToString; use const_oid::ObjectIdentifier; use yare::parameterized; @@ -133,6 +147,26 @@ mod test { const PROCESSOR_CA: &str = include_str!("../../data/tests/processor_ca.pem"); const ROOT_CA: &str = include_str!("../../data/tests/root_ca.pem"); + #[parameterized( + root = { ROOT_CA }, + processor = { PROCESSOR_CA }, + leaf = { LEAF_CERT }, + )] + fn try_from_pem(pem: &str) { + assert!(UnverifiedCertificate::try_from(pem).is_ok()); + } + + #[test] + fn try_from_bad_pem_errors() { + let pem = ROOT_CA.to_string(); + let bad_pem = pem.replace("-----END CERTIFICATE-----", ""); + + assert!(matches!( + UnverifiedCertificate::try_from(bad_pem.as_str()), + Err(Error::PemDecoding(_)) + )); + } + #[parameterized( root = { ROOT_CA }, processor = { PROCESSOR_CA }, @@ -381,4 +415,39 @@ mod test { Err(Error::CertificateExpired) ); } + + #[test] + fn verify_self_signed_root_ca() { + let root_cert = UnverifiedCertificate::try_from(ROOT_CA) + .expect("Failed to decode certificate from PEM"); + + let unix_time = root_cert + .certificate + .tbs_certificate + .validity + .not_after + .to_unix_duration(); + + assert_eq!(root_cert.verify_self_signed(unix_time).is_ok(), true); + } + + #[test] + fn verify_self_signed_root_ca_fails_when_expired() { + let root_cert = UnverifiedCertificate::try_from(ROOT_CA) + .expect("Failed to decode certificate from PEM"); + + let mut unix_time = root_cert + .certificate + .tbs_certificate + .validity + .not_after + .to_unix_duration(); + + unix_time += Duration::new(0, 1); + + assert_eq!( + root_cert.verify_self_signed(unix_time), + Err(Error::CertificateExpired) + ); + } } diff --git a/verifier/src/x509/chain.rs b/verifier/src/x509/chain.rs new file mode 100644 index 0000000..7b955a6 --- /dev/null +++ b/verifier/src/x509/chain.rs @@ -0,0 +1,174 @@ +// Copyright (c) 2023 The MobileCoin Foundation + +//! Support for verifying certificate chains + +extern crate alloc; + +use super::certs::UnverifiedCertificate; +use super::Result; +use crate::x509::certs::VerifiedCertificate; +use alloc::vec::Vec; +use core::time::Duration; +use p256::ecdsa::VerifyingKey; + +/// An X509 certificate chain. This is a valid path from the trust root to the +/// leaf certificate. +pub struct CertificateChain { + certificates: Vec, +} + +impl CertificateChain { + /// Create a new certificate chain from a path from a trust root to the + /// leaf certificate. + /// + /// A certificate chain without a valid path will result in errors for + /// functions like [`CertificateChain::signing_key`]. + pub fn new(certificates: Vec) -> Self { + Self { certificates } + } + + /// Returning the signing key from the leaf certificate + /// + /// The chain will be verified against the `trust_root` and the `unix_time`. + pub fn signing_key( + &self, + trust_root: VerifiedCertificate, + unix_time: Duration, + ) -> Result { + let mut key = trust_root.public_key(); + for cert in &self.certificates { + let verified_cert = cert.verify(&key, unix_time)?; + key = verified_cert.public_key(); + } + Ok(key) + } +} + +#[cfg(test)] +mod test { + use super::super::Error; + use super::*; + + use x509_cert::der::Decode; + use x509_cert::Certificate as X509Certificate; + use yare::parameterized; + + 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"); + + fn key_and_start_time(cert: &str) -> (VerifyingKey, Duration) { + let (_, der_bytes) = pem_rfc7468::decode_vec(cert.as_bytes()).expect("Failed decoding PEM"); + let cert = X509Certificate::from_der(der_bytes.as_slice()).expect("Falied decoding DER"); + + // The leaf certificate should have the narrowest time range. + let unix_time = cert.tbs_certificate.validity.not_before.to_unix_duration(); + let key = VerifyingKey::from_sec1_bytes( + cert.tbs_certificate + .subject_public_key_info + .subject_public_key + .as_bytes() + .expect("Failed decoding key"), + ) + .expect("Failed decoding key"); + + (key, unix_time) + } + + #[parameterized( + full_chain = { &[ROOT_CA, PROCESSOR_CA, LEAF_CERT] }, + to_intermediate = { &[ROOT_CA, PROCESSOR_CA] }, + only_root = { &[ROOT_CA] }, + )] + fn signing_key_from_certificate_chain(pem_chain: &[&str]) { + let certs = pem_chain + .iter() + .map(|pem| UnverifiedCertificate::try_from(*pem).expect("Failed decoding pem")) + .collect::>(); + + let end = pem_chain + .last() + .expect("Should be at least one certificate"); + let (expected_key, unix_time) = key_and_start_time(end); + + let chain = CertificateChain::new(certs); + let unverified_root = + UnverifiedCertificate::try_from(ROOT_CA).expect("Failed decoding pem"); + let root = unverified_root + .verify_self_signed(unix_time) + .expect("Failed verifying root certificate"); + + let signing_key = chain + .signing_key(root, unix_time) + .expect("Failed getting signing key"); + assert_eq!(signing_key, expected_key); + } + + #[test] + fn signing_key_fails_when_outside_valid_time() { + let pem_chain = [ROOT_CA, PROCESSOR_CA, LEAF_CERT]; + let certs = pem_chain + .iter() + .map(|pem| UnverifiedCertificate::try_from(*pem).expect("Failed decoding pem")) + .collect::>(); + + let (_, mut unix_time) = key_and_start_time(LEAF_CERT); + + unix_time -= Duration::from_nanos(1); + + let chain = CertificateChain::new(certs); + let unverified_root = + UnverifiedCertificate::try_from(ROOT_CA).expect("Failed decoding pem"); + let root = unverified_root + .verify_self_signed(unix_time) + .expect("Failed verifying root certificate"); + assert_eq!( + chain.signing_key(root, unix_time), + Err(Error::CertificateNotYetValid) + ); + } + + #[test] + fn cert_chain_out_of_order_fails() { + let pem_chain = [ROOT_CA, LEAF_CERT, PROCESSOR_CA]; + let certs = pem_chain + .iter() + .map(|pem| UnverifiedCertificate::try_from(*pem).expect("Failed decoding pem")) + .collect::>(); + + let (_, unix_time) = key_and_start_time(LEAF_CERT); + + let chain = CertificateChain::new(certs); + let unverified_root = + UnverifiedCertificate::try_from(ROOT_CA).expect("Failed decoding pem"); + let root = unverified_root + .verify_self_signed(unix_time) + .expect("Failed verifying root certificate"); + assert_eq!( + chain.signing_key(root, unix_time), + Err(Error::SignatureVerification) + ); + } + + #[test] + fn cert_chain_missing_intermediate_ca_fails() { + let pem_chain = [ROOT_CA, LEAF_CERT]; + let certs = pem_chain + .iter() + .map(|pem| UnverifiedCertificate::try_from(*pem).expect("Failed decoding pem")) + .collect::>(); + + let (_, unix_time) = key_and_start_time(LEAF_CERT); + + let chain = CertificateChain::new(certs); + let unverified_root = + UnverifiedCertificate::try_from(ROOT_CA).expect("Failed decoding pem"); + let root = unverified_root + .verify_self_signed(unix_time) + .expect("Failed verifying root certificate"); + assert_eq!( + chain.signing_key(root, unix_time), + Err(Error::SignatureVerification) + ); + } +} diff --git a/verifier/src/x509/crl.rs b/verifier/src/x509/crl.rs index 45adb97..1410397 100644 --- a/verifier/src/x509/crl.rs +++ b/verifier/src/x509/crl.rs @@ -2,7 +2,11 @@ //! Certificate Revocation List (CRL) support. +extern crate alloc; + use super::{Error, Result}; +use alloc::borrow::ToOwned; +use alloc::vec::Vec; use core::time::Duration; use p256::ecdsa::signature::Verifier; use p256::ecdsa::{Signature, VerifyingKey}; @@ -12,10 +16,10 @@ use x509_cert::time::Time; /// A certificate revocation list (CRL). #[derive(Debug, PartialEq, Eq)] -pub struct UnverifiedCrl<'a> { +pub struct UnverifiedCrl { // In order to verify the signature, we need to access the original DER // bytes - der_bytes: &'a [u8], + der_bytes: Vec, crl: CertificateList, // The signature is persisted here since they are fallible // operations and it's more ergonomic to fail fast than fail later for a @@ -30,12 +34,11 @@ pub struct UnverifiedCrl<'a> { /// A certificate whose signature has been verified. #[derive(Debug, PartialEq, Eq)] -pub struct VerifiedCrl<'a> { - _der_bytes: &'a [u8], +pub struct VerifiedCrl { _crl: CertificateList, } -impl<'a> UnverifiedCrl<'a> { +impl UnverifiedCrl { /// Verify the CRL signature is valid. /// /// # Arguments @@ -47,14 +50,11 @@ impl<'a> UnverifiedCrl<'a> { /// SystemTime::now().duration_since(UNIX_EPOCH) /// ``` /// or equivalent - pub fn verify(self, key: &VerifyingKey, unix_time: Duration) -> Result> { + pub fn verify(self, key: &VerifyingKey, unix_time: Duration) -> Result { self.verify_signature(key)?; self.verify_time(unix_time)?; - Ok(VerifiedCrl { - _der_bytes: self.der_bytes, - _crl: self.crl, - }) + Ok(VerifiedCrl { _crl: self.crl }) } fn verify_signature(&self, key: &VerifyingKey) -> Result<()> { @@ -84,11 +84,21 @@ impl<'a> UnverifiedCrl<'a> { } } -/// Convert a DER-encoded certificate into an [`UnverifiedCrl`]. -impl<'a> TryFrom<&'a [u8]> for UnverifiedCrl<'a> { +/// Convert a PEM-encoded CRL into an [`UnverifiedCrl`]. +impl TryFrom<&str> for UnverifiedCrl { + type Error = Error; + + fn try_from(pem: &str) -> ::core::result::Result { + let (_, der_bytes) = pem_rfc7468::decode_vec(pem.as_bytes())?; + Self::try_from(&der_bytes[..]) + } +} + +/// Convert a DER-encoded CRL into an [`UnverifiedCrl`]. +impl TryFrom<&[u8]> for UnverifiedCrl { type Error = Error; - fn try_from(der_bytes: &'a [u8]) -> ::core::result::Result { + fn try_from(der_bytes: &[u8]) -> ::core::result::Result { let crl = CertificateList::from_der(der_bytes)?; let signature_bytes = crl.signature.as_bytes().ok_or(Error::SignatureDecoding)?; let signature = @@ -100,7 +110,7 @@ impl<'a> TryFrom<&'a [u8]> for UnverifiedCrl<'a> { .ok_or(Error::CrlMissingNextUpdate)?; Ok(UnverifiedCrl { - der_bytes, + der_bytes: der_bytes.to_owned(), crl, signature, next_update, @@ -110,16 +120,38 @@ impl<'a> TryFrom<&'a [u8]> for UnverifiedCrl<'a> { #[cfg(test)] mod test { - use super::super::certs::UnverifiedCertificate; use super::*; + use alloc::string::ToString; + use x509_cert::der::Decode; + use x509_cert::Certificate as X509Certificate; use yare::parameterized; + const ROOT_CRL: &str = include_str!("../../data/tests/root_crl.pem"); const ROOT_CA: &str = include_str!("../../data/tests/root_ca.pem"); const PROCESSOR_CRL: &str = include_str!("../../data/tests/processor_crl.pem"); const PROCESSOR_CA: &str = include_str!("../../data/tests/processor_ca.pem"); + #[parameterized( + root = { ROOT_CRL }, + processor = { PROCESSOR_CRL }, + )] + fn try_from_pem(pem: &str) { + assert!(UnverifiedCrl::try_from(pem).is_ok()); + } + + #[test] + fn try_from_bad_pem_errors() { + let pem = ROOT_CRL.to_string(); + let bad_pem = pem.replace("-----END X509 CRL-----", ""); + + assert!(matches!( + UnverifiedCrl::try_from(bad_pem.as_str()), + Err(Error::PemDecoding(_)) + )); + } + #[parameterized( root = { ROOT_CRL }, processor = { PROCESSOR_CRL }, @@ -179,64 +211,66 @@ mod test { processor = { PROCESSOR_CA, PROCESSOR_CRL, }, }] fn verify_crl(ca_pem: &str, crl_pem: &str) { - let (_, der_bytes) = - pem_rfc7468::decode_vec(ca_pem.as_bytes()).expect("Failed to decode DER from PEM"); - let ca = UnverifiedCertificate::try_from(der_bytes.as_slice()) - .expect("Failed to decode certificate from DER"); - - let (_, der_bytes) = - pem_rfc7468::decode_vec(crl_pem.as_bytes()).expect("Failed to decode DER from PEM"); - let crl = UnverifiedCrl::try_from(der_bytes.as_slice()).expect("Failed to decode CRL"); + let crl = UnverifiedCrl::try_from(crl_pem).expect("Failed to decode CRL"); let unix_time = crl.crl.tbs_cert_list.this_update.to_unix_duration(); - assert_eq!(crl.verify(&ca.key, unix_time).is_ok(), true); + let (_, der_bytes) = + pem_rfc7468::decode_vec(ca_pem.as_bytes()).expect("Failed decoding PEM"); + let x509_cert = + X509Certificate::from_der(der_bytes.as_slice()).expect("Falied decoding DER"); + + let signing_key = VerifyingKey::from_sec1_bytes( + x509_cert + .tbs_certificate + .subject_public_key_info + .subject_public_key + .as_bytes() + .expect("Failed decoding key"), + ) + .expect("Failed decoding key"); + + assert_eq!(crl.verify(&signing_key, unix_time).is_ok(), true); } #[test] fn verify_fails_with_wrong_key() { - let (_, der_bytes) = - pem_rfc7468::decode_vec(ROOT_CA.as_bytes()).expect("Failed to decode DER from PEM"); - let ca = UnverifiedCertificate::try_from(der_bytes.as_slice()) - .expect("Failed to decode certificate from DER"); - - let (_, der_bytes) = pem_rfc7468::decode_vec(PROCESSOR_CRL.as_bytes()) - .expect("Failed to decode DER from PEM"); - let crl = UnverifiedCrl::try_from(der_bytes.as_slice()).expect("Failed to decode CRL"); + let crl = UnverifiedCrl::try_from(PROCESSOR_CRL).expect("Failed to decode CRL"); let unix_time = crl.crl.tbs_cert_list.this_update.to_unix_duration(); + let unverified_ca = + UnverifiedCertificate::try_from(ROOT_CA).expect("Failed to decode certificate"); + let ca = unverified_ca + .verify_self_signed(unix_time) + .expect("Failed to verify certificate"); + assert_eq!( - crl.verify(&ca.key, unix_time), + crl.verify(&ca.public_key(), unix_time), Err(Error::SignatureVerification) ); } #[test] fn clr_before_this_update_fails() { - let (_, der_bytes) = - pem_rfc7468::decode_vec(ROOT_CA.as_bytes()).expect("Failed to decode DER from PEM"); - let ca = UnverifiedCertificate::try_from(der_bytes.as_slice()) - .expect("Failed to decode certificate from DER"); - - let (_, der_bytes) = - pem_rfc7468::decode_vec(ROOT_CRL.as_bytes()).expect("Failed to decode DER from PEM"); - let crl = UnverifiedCrl::try_from(der_bytes.as_slice()).expect("Failed to decode CRL"); + let crl = UnverifiedCrl::try_from(ROOT_CRL).expect("Failed to decode CRL"); let mut unix_time = crl.crl.tbs_cert_list.this_update.to_unix_duration(); + let unverified_ca = + UnverifiedCertificate::try_from(ROOT_CA).expect("Failed to decode certificate"); + let ca = unverified_ca + .verify_self_signed(unix_time) + .expect("Failed to verify certificate"); + unix_time -= Duration::from_nanos(1); - assert_eq!(crl.verify(&ca.key, unix_time), Err(Error::CrlNotYetValid)); + assert_eq!( + crl.verify(&ca.public_key(), unix_time), + Err(Error::CrlNotYetValid) + ); } #[test] fn clr_before_next_update_succeeds() { - let (_, der_bytes) = - pem_rfc7468::decode_vec(ROOT_CA.as_bytes()).expect("Failed to decode DER from PEM"); - let ca = UnverifiedCertificate::try_from(der_bytes.as_slice()) - .expect("Failed to decode certificate from DER"); - - let (_, der_bytes) = - pem_rfc7468::decode_vec(ROOT_CRL.as_bytes()).expect("Failed to decode DER from PEM"); - let crl = UnverifiedCrl::try_from(der_bytes.as_slice()).expect("Failed to decode CRL"); + let crl = UnverifiedCrl::try_from(ROOT_CRL).expect("Failed to decode CRL"); let mut unix_time = crl .crl .tbs_cert_list @@ -244,21 +278,20 @@ mod test { .expect("Expected a valid update time") .to_unix_duration(); + let unverified_ca = + UnverifiedCertificate::try_from(ROOT_CA).expect("Failed to decode certificate"); + let ca = unverified_ca + .verify_self_signed(unix_time) + .expect("Failed to verify certificate"); + unix_time -= Duration::from_nanos(1); - assert_eq!(crl.verify(&ca.key, unix_time).is_ok(), true); + assert_eq!(crl.verify(&ca.public_key(), unix_time).is_ok(), true); } #[test] fn clr_at_next_update_fails() { - let (_, der_bytes) = - pem_rfc7468::decode_vec(ROOT_CA.as_bytes()).expect("Failed to decode DER from PEM"); - let ca = UnverifiedCertificate::try_from(der_bytes.as_slice()) - .expect("Failed to decode certificate from DER"); - - let (_, der_bytes) = - pem_rfc7468::decode_vec(ROOT_CRL.as_bytes()).expect("Failed to decode DER from PEM"); - let crl = UnverifiedCrl::try_from(der_bytes.as_slice()).expect("Failed to decode CRL"); + let crl = UnverifiedCrl::try_from(ROOT_CRL).expect("Failed to decode CRL"); let unix_time = crl .crl .tbs_cert_list @@ -266,6 +299,15 @@ mod test { .expect("Expected a valid update time") .to_unix_duration(); - assert_eq!(crl.verify(&ca.key, unix_time), Err(Error::CrlExpired)); + let unverified_ca = + UnverifiedCertificate::try_from(ROOT_CA).expect("Failed to decode certificate"); + let ca = unverified_ca + .verify_self_signed(unix_time) + .expect("Failed to verify certificate"); + + assert_eq!( + crl.verify(&ca.public_key(), unix_time), + Err(Error::CrlExpired) + ); } } diff --git a/verifier/src/x509/error.rs b/verifier/src/x509/error.rs index 28f621a..331923c 100644 --- a/verifier/src/x509/error.rs +++ b/verifier/src/x509/error.rs @@ -9,6 +9,8 @@ pub enum Error { CertificateExpired, /// An error occurred decoding the DER representation DerDecoding(x509_cert::der::Error), + /// An error occurred decoding the PEM representation + PemDecoding(pem_rfc7468::Error), /// The certificate is not yet valid CertificateNotYetValid, /// An error occurred decoding the key from a certificate @@ -26,3 +28,9 @@ impl From for Error { Error::DerDecoding(src) } } + +impl From for Error { + fn from(src: pem_rfc7468::Error) -> Self { + Error::PemDecoding(src) + } +}