Skip to content

Commit

Permalink
Add lower level test for PKITS 4.2.3
Browse files Browse the repository at this point in the history
The test case for PKITS 4.2.3 uses a `UTCTime` of "1950-01-01T00:00:00",
however the DER parsing logic is limited to the UNIX_EPOCH "1970-01-01".
In order to ensure the proper date handling of 1970-2000 a lower level
testing approach is used.
  • Loading branch information
nick-mobilecoin committed May 5, 2023
1 parent f942bfb commit 9751c54
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 6 deletions.
3 changes: 2 additions & 1 deletion verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ subtle = { version = "2.4.0", default-features = false }
x509-cert = { version = "0.2.0", default-features = false, optional = true }

[dev-dependencies]
chrono = { version = "0.4.24", default-features = false }
mc-sgx-core-sys-types = "0.6.0"
rand = { version = "0.8.4", default-features = false, features = ["std_rng"] }
textwrap = "0.16.0"
x509-cert = { version = "0.2.0", default-features = false, features = ["pem"] }
x509-cert = { version = "0.2.0", default-features = false, features = ["pem", "std"] }
yare = "1.0.2"

# At least one crate must have this configuration
Expand Down
6 changes: 6 additions & 0 deletions verifier/src/x509/certs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ impl TryFrom<&[u8]> for UnverifiedCertificate {
}
}

impl AsRef<X509Certificate> for UnverifiedCertificate {
fn as_ref(&self) -> &X509Certificate {
&self.certificate
}
}

/// A certificate whose signature has been verified.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct VerifiedCertificate {
Expand Down
10 changes: 10 additions & 0 deletions verifier/tests/data/pkits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,13 @@

Test vectors from the NIST PKITS test suite. See
<https://csrc.nist.gov/projects/pki-testing> for more information.

To view the contents of the certificates or CRLs one can use the following
command:

```console
openssl x509 -in <file> -text -noout
```

> The `-noout` option is to suppress the display of the PEM format at the end
> of the output.
Binary file not shown.
98 changes: 93 additions & 5 deletions verifier/tests/pkits-4-2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

//! Test for section 4.2 of the PKI test suite.
//! https://csrc.nist.gov/projects/pki-testing
//!
//! Test 4.2.3 was skipped because it used 1950 as the not before date. While
//! the DER decoder used by the x509-crt will only allow dates after 1970 to
//! work with UNIX_EPOCH.
mod common;

use crate::common::{GOOD_CA_CERT, GOOD_CA_CRL};
use chrono::NaiveDateTime;
use common::{TRUST_ANCHOR_ROOT_CERTIFICATE, TRUST_ANCHOR_ROOT_CRL};
use mc_attestation_verifier::x509::Error;
use mc_attestation_verifier::x509::{Error, UnverifiedCertificate};
use std::str::FromStr;
use std::time::SystemTime;

const BAD_NOT_BEFORE_DATE_CA_CERT: &[u8] =
include_bytes!("data/pkits/certs/BadnotBeforeDateCACert.crt");
const INVALID_CA_NOT_BEFORE_DATE_TEST_1EE: &[u8] =
include_bytes!("data/pkits/certs/InvalidCAnotBeforeDateTest1EE.crt");
const INVALID_EE_NOT_BEFORE_DATE_TEST_2EE: &[u8] =
include_bytes!("data/pkits/certs/InvalidEEnotBeforeDateTest2EE.crt");
const VALID_PRE_2000_UTC_NOT_BEFORE_DATE_TEST_3EE: &[u8] =
include_bytes!("data/pkits/certs/Validpre2000UTCnotBeforeDateTest3EE.crt");
const VALID_GENERALIZED_TIME_NOT_BEFORE_DATE_TEST_4EE: &[u8] =
include_bytes!("data/pkits/certs/ValidGeneralizedTimenotBeforeDateTest4EE.crt");
const INVALID_CA_NOT_AFTER_DATE_TEST_5EE: &[u8] =
Expand Down Expand Up @@ -81,6 +82,93 @@ fn invalid_ee_not_before_date_4_2_2() {
);
}

#[test]
fn valid_pre2000_utc_before_date_4_2_3() {
// tl;dr - Test dates from 1970-2049
//
// Per https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5.1
// The `UTCTime` format supports dates from 1950-2049. The provided
// certificate `VALID_PRE_2000_UTC_NOT_BEFORE_DATE_TEST_3EE` uses a
// date of 1950-01-01T00:00:00Z. The DER decoder being used will treat this
// as 1950, but when it goes to convert this to a date, it is bound by 1970,
// the unix epoch. To best mimic this test case we will manually test the
// date range during decoding. Because we are modifying the certificate
// bytes, the signature will not be valid so we will not be able to test
// that and will only focus on the low level date conversion logic.
// See https://obj-sys.com/asn1tutorial/node124.html
const UTC_TIME_TAG: u8 = 23;
// UTCTime in RFC5280 is limited to the form of "YYMMDDHHMMSSZ"
const UTC_TIME_LENGTH: u8 = 13;
const UTC_TIME_TAG_LENGTH: usize = 2;

const EARLIEST_VALID_UTC_TIME: &[u8] = b"700101000000Z";
const EARLIEST_INVALID_UTC_TIME: &[u8] = b"691231235959Z";
const LATEST_VALID_UTC_TIME: &[u8] = b"491231235959Z";
// Normally this would be valid, but due to the backend limiting times to
// by the unix epoch (1970) it's not valid.
const LATEST_INVALID_UTC_TIME: &[u8] = b"500101000000Z";

let search_time = vec![UTC_TIME_TAG, UTC_TIME_LENGTH, '5' as u8, '0' as u8];

let mut der_bytes = VALID_PRE_2000_UTC_NOT_BEFORE_DATE_TEST_3EE.to_vec();

// Show that the certificate as provided will fail to parse with the DER
// backend
assert!(matches!(
UnverifiedCertificate::try_from(der_bytes.as_slice()),
Err(Error::DerDecoding(_))
));

let mut not_before_offset = der_bytes
.windows(search_time.len())
.position(|window| window == search_time)
.expect("Failed to find not before time");

not_before_offset += UTC_TIME_TAG_LENGTH;
let not_before_end = not_before_offset + UTC_TIME_LENGTH as usize;

der_bytes[not_before_offset..not_before_end].copy_from_slice(EARLIEST_INVALID_UTC_TIME);
assert!(matches!(
UnverifiedCertificate::try_from(der_bytes.as_slice()),
Err(Error::DerDecoding(_))
));

der_bytes[not_before_offset..not_before_end].copy_from_slice(EARLIEST_VALID_UTC_TIME);
let unverified_cert = UnverifiedCertificate::try_from(der_bytes.as_slice())
.expect("Failed to decode certificate time");
let x509_cert = unverified_cert.as_ref();
assert_eq!(
x509_cert
.tbs_certificate
.validity
.not_before
.to_system_time(),
SystemTime::UNIX_EPOCH
);

der_bytes[not_before_offset..not_before_end].copy_from_slice(LATEST_VALID_UTC_TIME);
let unverified_cert = UnverifiedCertificate::try_from(der_bytes.as_slice())
.expect("Failed to decode certificate time");
let x509_cert = unverified_cert.as_ref();
let expected_time =
NaiveDateTime::from_str("2049-12-31T23:59:59").expect("Failed to create date");
let duration = x509_cert
.tbs_certificate
.validity
.not_before
.to_unix_duration();
let actual_time =
NaiveDateTime::from_timestamp_opt(duration.as_secs() as i64, duration.subsec_nanos())
.expect("Failed to create date");
assert_eq!(actual_time, expected_time);

der_bytes[not_before_offset..not_before_end].copy_from_slice(LATEST_INVALID_UTC_TIME);
assert!(matches!(
UnverifiedCertificate::try_from(der_bytes.as_slice()),
Err(Error::DerDecoding(_))
));
}

#[test]
fn valid_generalizedtime_before_date_4_2_4() {
let (chain, expected_key) = common::chain_and_leaf_key(
Expand Down

0 comments on commit 9751c54

Please sign in to comment.