pub mod attestation_doc;
pub mod cert;
pub mod error;
use attestation_doc::AttestationDoc;
use openssl::x509::X509;
fn true_or_invalid<E: Into<error::AttestError>>(check: bool, err: E) -> Result<(), E> {
if check {
Ok(())
} else {
Err(err)
}
}
pub fn parse_cert(given_cert: &[u8]) -> error::AttestResult<X509> {
let parsed_cert =
cert::parse_pem_cert(given_cert).or_else(|_| cert::parse_der_cert(given_cert))?;
Ok(parsed_cert)
}
pub fn validate_attestation_doc_in_cert(given_cert: &X509) -> error::AttestResult<AttestationDoc> {
let cose_signature = cert::extract_signed_cose_sign_1_from_certificate(given_cert)?;
let (cose_sign_1_decoded, decoded_attestation_doc) =
attestation_doc::decode_attestation_document(&cose_signature)?;
attestation_doc::validate_attestation_document_structure(&decoded_attestation_doc)?;
let attestation_doc_signing_cert = cert::parse_der_cert(&decoded_attestation_doc.certificate)?;
let received_certificates =
cert::parse_cert_stack_from_cabundle(decoded_attestation_doc.cabundle.as_ref())?;
cert::validate_cert_trust_chain(&attestation_doc_signing_cert, &received_certificates)?;
let attestation_doc_signing_cert = cert::parse_der_cert(&decoded_attestation_doc.certificate)?;
let attestation_doc_pub_key = cert::get_cert_public_key(&attestation_doc_signing_cert)?;
attestation_doc::validate_cose_signature(&attestation_doc_pub_key, &cose_sign_1_decoded)?;
let cage_cert_public_key = cert::export_public_key_to_der(given_cert)?;
attestation_doc::validate_expected_challenge(&decoded_attestation_doc, &cage_cert_public_key)?;
Ok(decoded_attestation_doc)
}
#[cfg(test)]
mod test {
use super::*;
use rcgen::generate_simple_self_signed;
fn embed_attestation_doc_in_cert(hostname: &str, cose_bytes: &[u8]) -> rcgen::Certificate {
let subject_alt_names = vec![
hostname.to_string(),
format!("{}.{hostname}", hex::encode(cose_bytes)),
];
generate_simple_self_signed(subject_alt_names).unwrap()
}
fn rcgen_cert_to_pem(cert: rcgen::Certificate) -> Vec<u8> {
cert.serialize_pem().unwrap().into_bytes()
}
fn rcgen_cert_to_der(cert: rcgen::Certificate) -> Vec<u8> {
cert.serialize_der().unwrap()
}
#[test]
fn test_der_cert_parsing() {
let sample_cose_sign_1_bytes = std::fs::read(std::path::Path::new(
"./test-files/valid-attestation-doc-bytes",
))
.unwrap();
let hostname = "debug.cage.com";
let cert = embed_attestation_doc_in_cert(hostname, &sample_cose_sign_1_bytes);
let der_cert = rcgen_cert_to_der(cert);
let parsed_cert = parse_cert(der_cert.as_ref()).unwrap();
let matched_hostname = parsed_cert
.subject_alt_names()
.into_iter()
.flat_map(|entries| entries.into_iter())
.any(|entries| {
entries
.dnsname()
.map(|san| san == hostname)
.unwrap_or(false)
});
assert!(matched_hostname);
}
#[test]
fn test_pem_cert_parsing() {
let sample_cose_sign_1_bytes = std::fs::read(std::path::Path::new(
"./test-files/valid-attestation-doc-bytes",
))
.unwrap();
let hostname = "debug.cage.com";
let cert = embed_attestation_doc_in_cert(hostname, &sample_cose_sign_1_bytes);
let cert = rcgen_cert_to_pem(cert);
let parsed_cert = parse_cert(cert.as_ref()).unwrap();
let matched_hostname = parsed_cert
.subject_alt_names()
.into_iter()
.flat_map(|entries| entries.into_iter())
.any(|entries| {
entries
.dnsname()
.map(|san| san == hostname)
.unwrap_or(false)
});
assert!(matched_hostname);
}
#[test]
fn validate_debug_mode_attestation_doc() {
let sample_cose_sign_1_bytes = std::fs::read(std::path::Path::new(
"./test-files/debug-mode-attestation-doc-bytes",
))
.unwrap();
let attestable_cert =
embed_attestation_doc_in_cert("test-cage.localhost:6789", &sample_cose_sign_1_bytes);
let cert = rcgen_cert_to_pem(attestable_cert);
let cert = parse_cert(&cert).unwrap();
let err = validate_attestation_doc_in_cert(&cert).unwrap_err();
assert!(matches!(
err,
error::AttestError::CertError(error::CertError::UntrustedCert)
));
}
}