use std::{
fmt::{self, Debug, Display},
include_bytes,
sync::{Arc, LazyLock},
};
use anyhow::{Context, bail, ensure, format_err};
use asn1_rs::FromDer;
use dcap_ql::quote::{
CertificationDataType, Quote, Quote3SignatureEcdsaP256, RawQe3CertData,
};
use lexe_byte_array::ByteArray;
use lexe_common::env::DeployEnv;
use lexe_crypto::ed25519;
use lexe_enclave::enclave::{self, Measurement};
use lexe_hex::hex;
use lexe_sha256::sha256;
use rustls::{
DigitallySignedStruct, DistinguishedName,
client::danger::{
HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier,
},
pki_types::{
CertificateDer, ServerName, SignatureVerificationAlgorithm,
TrustAnchor, UnixTime, pem::PemObject,
},
server::danger::{ClientCertVerified, ClientCertVerifier},
};
use x509_parser::certificate::X509Certificate;
use crate::{
attest_client::{cert::SgxAttestationExtension, quote::ReportData},
ed25519_ext::Ed25519PublicKeyExt,
};
const INTEL_QE_IDENTITY_MRSIGNER: Measurement =
Measurement::new(hex::decode_const(
b"8c4f5775d796503e96137f77c68a829a0056ac8ded70140b081b094490c57bff",
));
const INTEL_SGX_ROOT_CA_CERT_DER: &CertificateDer<'static> =
&CertificateDer::from_slice(include_bytes!(
"../../data/intel-sgx-root-ca.der"
));
static INTEL_SGX_TRUST_ANCHOR: LazyLock<[TrustAnchor<'static>; 1]> =
LazyLock::new(|| {
let trust_anchor = webpki::anchor_from_trusted_cert(
INTEL_SGX_ROOT_CA_CERT_DER,
)
.expect("Failed to deserialize Intel SGX root CA cert from der bytes");
[trust_anchor]
});
static SUPPORTED_SIG_ALGS: &[&dyn SignatureVerificationAlgorithm] = &[
webpki::ring::ECDSA_P256_SHA256,
webpki::ring::ECDSA_P256_SHA384,
webpki::ring::ECDSA_P384_SHA256,
webpki::ring::ECDSA_P384_SHA384,
webpki::ring::ED25519,
webpki::ring::RSA_PKCS1_2048_8192_SHA256,
webpki::ring::RSA_PKCS1_2048_8192_SHA384,
webpki::ring::RSA_PKCS1_2048_8192_SHA512,
webpki::ring::RSA_PKCS1_3072_8192_SHA384,
];
#[derive(Debug)]
pub struct AttestationCertVerifier {
pub expect_dummy_quote: bool,
pub enclave_policy: EnclavePolicy,
}
enum VerifierParams<'param> {
Server {
server_name: &'param ServerName<'param>,
ocsp_response: &'param [u8],
},
Client,
}
enum CertVerified {
Client(ClientCertVerified),
Server(ServerCertVerified),
}
impl AttestationCertVerifier {
fn verify_attestation_cert(
&self,
verifier_params: VerifierParams,
end_entity: &CertificateDer,
intermediates: &[CertificateDer],
now: UnixTime,
) -> Result<CertVerified, rustls::Error> {
if !intermediates.is_empty() {
return Err(rustls_err("received unexpected intermediate certs"));
}
let mut trust_roots = rustls::RootCertStore::empty();
trust_roots.add(end_entity.to_owned()).map_err(rustls_err)?;
let cert_verified = match verifier_params {
VerifierParams::Client => {
let webpki_verifier =
rustls::server::WebPkiClientVerifier::builder_with_provider(
Arc::new(trust_roots),
lexe_tls_core::LEXE_CRYPTO_PROVIDER.clone(),
)
.build()
.map_err(|e| rustls::Error::General(e.to_string()))?;
webpki_verifier
.verify_client_cert(end_entity, &[], now)
.map(CertVerified::Client)?
}
VerifierParams::Server {
server_name,
ocsp_response,
} => {
let webpki_verifier =
rustls::client::WebPkiServerVerifier::builder_with_provider(
Arc::new(trust_roots),
lexe_tls_core::LEXE_CRYPTO_PROVIDER.clone(),
)
.build()
.map_err(|e| rustls::Error::General(e.to_string()))?;
webpki_verifier
.verify_server_cert(
end_entity,
&[],
server_name,
ocsp_response,
now,
)
.map(CertVerified::Server)?
}
};
let evidence = AttestEvidence::parse_cert_der(end_entity)?;
let enclave_report = if self.expect_dummy_quote {
sgx_isa::Report::try_copy_from(evidence.cert_ext.quote.as_ref())
.ok_or_else(|| rustls_err("Could not copy Report"))?
} else {
let quote_verifier = SgxQuoteVerifier;
quote_verifier
.verify(&evidence.cert_ext.quote, now)
.map_err(|err| {
rustls_err(format!("invalid SGX Quote: {err:#}"))
})?
};
let reportdata =
self.enclave_policy.verify(&enclave_report).map_err(|err| {
rustls_err(format!(
"our trust policy rejected the remote enclave: {err:#}"
))
})?;
if !reportdata.contains(&evidence.cert_pk) {
return Err(rustls_err(
"enclave's report is not binding to the presented x509 cert",
));
}
Ok(cert_verified)
}
}
impl ServerCertVerifier for AttestationCertVerifier {
fn verify_server_cert(
&self,
end_entity: &CertificateDer,
intermediates: &[CertificateDer],
server_name: &ServerName,
ocsp_response: &[u8],
now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
let verifier_params = VerifierParams::Server {
server_name,
ocsp_response,
};
let cert_verified = self.verify_attestation_cert(
verifier_params,
end_entity,
intermediates,
now,
)?;
match cert_verified {
CertVerified::Client(_) =>
panic!("verify_attestation_cert returned wrong token kind"),
CertVerified::Server(verified) => Ok(verified),
}
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
let error = rustls::PeerIncompatible::ServerDoesNotSupportTls12Or13;
Err(rustls::Error::PeerIncompatible(error))
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls13_signature(
message,
cert,
dss,
&lexe_tls_core::LEXE_SIGNATURE_ALGORITHMS,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
lexe_tls_core::LEXE_SUPPORTED_VERIFY_SCHEMES.clone()
}
}
impl ClientCertVerifier for AttestationCertVerifier {
fn root_hint_subjects(&self) -> &[DistinguishedName] {
&[]
}
fn verify_client_cert(
&self,
end_entity: &CertificateDer,
intermediates: &[CertificateDer],
now: UnixTime,
) -> Result<ClientCertVerified, rustls::Error> {
let verifier_params = VerifierParams::Client;
let cert_verified = self.verify_attestation_cert(
verifier_params,
end_entity,
intermediates,
now,
)?;
match cert_verified {
CertVerified::Client(verified) => Ok(verified),
CertVerified::Server(_) =>
panic!("verify_attestation_cert returned wrong token kind"),
}
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
let error = rustls::PeerIncompatible::ServerDoesNotSupportTls12Or13;
Err(rustls::Error::PeerIncompatible(error))
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls13_signature(
message,
cert,
dss,
&lexe_tls_core::LEXE_SIGNATURE_ALGORITHMS,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
lexe_tls_core::LEXE_SUPPORTED_VERIFY_SCHEMES.clone()
}
}
pub struct AttestEvidence<'quote> {
cert_pk: ed25519::PublicKey,
cert_ext: SgxAttestationExtension<'quote>,
}
impl<'a> AttestEvidence<'a> {
pub fn parse_cert_der(cert_der: &'a [u8]) -> Result<Self, rustls::Error> {
use std::io;
fn invalid_cert_error(
error: impl std::error::Error + Send + Sync + 'static,
) -> rustls::Error {
let other_error = rustls::OtherError(Arc::new(error));
let cert_error = rustls::CertificateError::Other(other_error);
rustls::Error::InvalidCertificate(cert_error)
}
let (unparsed_data, cert) =
X509Certificate::from_der(cert_der).map_err(invalid_cert_error)?;
if !unparsed_data.is_empty() {
let msg = "leftover unparsed cert data";
let io_error = io::Error::other(msg);
return Err(invalid_cert_error(io_error));
}
let cert_pk = ed25519::PublicKey::try_from_spki(cert.public_key())
.map_err(invalid_cert_error)?;
let sgx_ext_oid = SgxAttestationExtension::oid_asn1_rs();
let cert_ext = cert
.get_extension_unique(&sgx_ext_oid)
.map_err(invalid_cert_error)?
.ok_or_else(|| {
let msg = "no SGX attestation extension";
invalid_cert_error(io::Error::other(msg))
})?;
let cert_ext = SgxAttestationExtension::from_der_bytes(cert_ext.value)
.map_err(|e| {
let msg = format!("invalid SGX attestation: {e:#}");
invalid_cert_error(io::Error::other(msg))
})?;
Ok(Self { cert_pk, cert_ext })
}
}
pub struct SgxQuoteVerifier;
impl SgxQuoteVerifier {
pub fn verify(
&self,
quote_bytes: &[u8],
now: UnixTime,
) -> anyhow::Result<sgx_isa::Report> {
let quote = Quote::parse(quote_bytes)
.map_err(DisplayErr::new)
.context("Failed to parse SGX Quote")?;
let sig = quote
.signature::<Quote3SignatureEcdsaP256>()
.map_err(DisplayErr::new)
.context("Failed to parse SGX ECDSA Quote signature")?;
ensure!(
sig.certification_data_type()
== CertificationDataType::PckCertificateChain,
"unexpected SGX quote certification data type",
);
let cert_chain_pem = sig
.certification_data::<RawQe3CertData>()
.map_err(DisplayErr::new)
.context("Failed to parse PCK cert chain")?;
let mut cert_iter = CertificateDer::pem_slice_iter(&cert_chain_pem);
let pck_cert_der = cert_iter.next().context("Missing PCK cert")??;
let pck_platform_cert_der =
cert_iter.next().context("Missing PCK platform cert")??;
let sgx_root_ca_cert_der =
cert_iter.next().context("Missing SGX root CA cert")??;
ensure!(cert_iter.next().is_none(), "unexpected extra certificate");
let pck_cert = webpki::EndEntityCert::try_from(&pck_cert_der)
.context("Invalid PCK cert")?;
let key_usage = webpki::KeyUsage::server_auth();
let revocation = None;
let verify_path = None;
pck_cert
.verify_for_usage(
SUPPORTED_SIG_ALGS,
INTEL_SGX_TRUST_ANCHOR.as_slice(),
&[pck_platform_cert_der, sgx_root_ca_cert_der],
now,
key_usage,
revocation,
verify_path,
)
.context("PCK cert chain failed validation")?;
let qe3_sig = get_ecdsa_sig_der(sig.qe3_signature())?;
let qe3_report_bytes = sig.qe3_report();
pck_cert
.verify_signature(
webpki::ring::ECDSA_P256_SHA256,
qe3_report_bytes,
&qe3_sig,
)
.context(
"PCK cert's signature on the Quoting Enclave Report is invalid",
)?;
let expected_reportdata = sha256::digest_many(&[
sig.attestation_public_key(),
sig.authentication_data(),
]);
let qe3_report = report_try_from_truncated(qe3_report_bytes)
.context("Invalid QE Report")?;
let qe3_reportdata = EnclavePolicy::trust_intel_qe()
.verify(&qe3_report)
.context("Invalid QE identity")?;
ensure!(
&qe3_reportdata.as_inner()[..32] == expected_reportdata.as_slice(),
"Quoting Enclave's Report data doesn't match the Quote attestation pk: \
actual: '{}', expected: '{}'",
hex::display(&qe3_reportdata.as_inner()[..32]),
expected_reportdata,
);
let attestation_public_key =
read_attestation_pk(sig.attestation_public_key())?;
let msg_len = 432;
ensure!(quote_bytes.len() >= msg_len, "Quote malformed");
let msg = "e_bytes[..432];
attestation_public_key.verify(msg, sig.signature())
.map_err(|_| format_err!("QE signature on application enclave report failed to verify"))?;
let report = report_try_from_truncated(quote.report_body())
.context("Invalid application enclave Report")?;
Ok(report)
}
}
#[derive(Debug)]
struct DisplayErr(String);
impl DisplayErr {
fn new(err: impl Display) -> Self {
Self(format!("{err:#}"))
}
}
impl Display for DisplayErr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for DisplayErr {
fn description(&self) -> &str {
&self.0
}
}
#[derive(Debug)]
pub struct EnclavePolicy {
pub allow_debug: bool,
pub trusted_mrenclaves: Option<Vec<Measurement>>,
pub trusted_mrsigner: Option<Measurement>,
}
impl EnclavePolicy {
pub fn trust_measurements_with_signer(
use_sgx: bool,
deploy_env: DeployEnv,
measurements: Vec<Measurement>,
) -> Self {
let is_dev = deploy_env.is_dev();
Self {
allow_debug: is_dev,
trusted_mrenclaves: Some(measurements),
trusted_mrsigner: Some(Measurement::expected_signer(
use_sgx, is_dev,
)),
}
}
pub fn trust_expected_signer(use_sgx: bool, deploy_env: DeployEnv) -> Self {
let is_dev = deploy_env.is_dev();
Self {
allow_debug: is_dev,
trusted_mrenclaves: None,
trusted_mrsigner: Some(Measurement::expected_signer(
use_sgx, is_dev,
)),
}
}
pub fn dangerous_trust_any() -> Self {
Self {
allow_debug: true,
trusted_mrenclaves: None,
trusted_mrsigner: None,
}
}
pub fn trust_intel_qe() -> Self {
Self {
allow_debug: false,
trusted_mrenclaves: None,
trusted_mrsigner: Some(INTEL_QE_IDENTITY_MRSIGNER),
}
}
pub fn trust_self() -> Self {
let self_report = enclave::report();
let report_mrenclave = Measurement::new(self_report.mrenclave);
let report_mrsigner = Measurement::new(self_report.mrsigner);
let allow_debug = self_report
.attributes
.flags
.contains(sgx_isa::AttributesFlags::DEBUG);
let trusted_mrenclaves = Some(vec![report_mrenclave]);
let trusted_mrsigner = Some(report_mrsigner);
Self {
allow_debug,
trusted_mrenclaves,
trusted_mrsigner,
}
}
pub fn verify(
&self,
report: &sgx_isa::Report,
) -> anyhow::Result<ReportData> {
if !self.allow_debug {
let is_debug = report
.attributes
.flags
.contains(sgx_isa::AttributesFlags::DEBUG);
ensure!(!is_debug, "enclave is in debug mode",);
}
let report_mrenclave = Measurement::new(report.mrenclave);
if let Some(mrenclaves) = self.trusted_mrenclaves.as_ref() {
ensure!(
mrenclaves.contains(&report_mrenclave),
"enclave measurement '{report_mrenclave}' is not trusted",
);
}
let report_mrsigner = Measurement::new(report.mrsigner);
if let Some(mrsigner) = self.trusted_mrsigner.as_ref() {
ensure!(
mrsigner == &report_mrsigner,
"enclave signer '{report_mrsigner}' is not trusted, trusted signer: '{mrsigner}'",
);
}
Ok(ReportData::new(report.reportdata))
}
}
fn get_ecdsa_sig_der(sig: &[u8]) -> anyhow::Result<Vec<u8>> {
if !sig.len().is_multiple_of(2) {
bail!("sig not even: {}", sig.len());
}
let (r_bytes, s_bytes) = sig.split_at(sig.len() / 2);
let r = num_bigint::BigUint::from_bytes_be(r_bytes);
let s = num_bigint::BigUint::from_bytes_be(s_bytes);
let der = yasna::construct_der(|writer| {
writer.write_sequence(|writer| {
writer.next().write_biguint(&r);
writer.next().write_biguint(&s);
})
});
Ok(der)
}
fn read_attestation_pk(
bytes: &[u8],
) -> anyhow::Result<ring::signature::UnparsedPublicKey<[u8; 65]>> {
ensure!(
bytes.len() == 64,
"Attestation public key is in an unrecognized format; expected exactly 64 bytes, actual len: {}",
bytes.len()
);
let mut attestation_public_key = [0u8; 65];
attestation_public_key[0] = 0x4;
attestation_public_key[1..].copy_from_slice(bytes);
Ok(ring::signature::UnparsedPublicKey::new(
&ring::signature::ECDSA_P256_SHA256_FIXED,
attestation_public_key,
))
}
fn report_try_from_truncated(bytes: &[u8]) -> anyhow::Result<sgx_isa::Report> {
use sgx_isa::Report;
let len = bytes.len();
let expected_len = Report::TRUNCATED_SIZE;
ensure!(
len == expected_len,
"report has the wrong size: {len}, expected: {expected_len}",
);
let mut unpadded = vec![0u8; Report::UNPADDED_SIZE];
unpadded[..Report::TRUNCATED_SIZE].copy_from_slice(bytes);
Ok(Report::try_copy_from(&unpadded).expect("Should never fail"))
}
pub struct ReportDebug<'a>(&'a sgx_isa::Report);
impl Debug for ReportDebug<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Report")
.field("cpusvn", &hex::display(&self.0.cpusvn))
.field("miscselect", &self.0.miscselect)
.field("attributes", &self.0.attributes.flags)
.field("xfrm", &format!("{:016x}", self.0.attributes.xfrm))
.field("mrenclave", &hex::display(&self.0.mrenclave))
.field("mrsigner", &hex::display(&self.0.mrsigner))
.field("isvprodid", &self.0.isvprodid)
.field("isvsvn", &self.0.isvsvn)
.field("reportdata", &hex::display(&self.0.reportdata))
.field("keyid", &hex::display(&self.0.keyid))
.field("mac", &hex::display(&self.0.mac))
.finish()
}
}
fn rustls_err(s: impl Display) -> rustls::Error {
rustls::Error::General(s.to_string())
}
#[cfg(test)]
mod test {
use std::include_str;
use super::*;
const SGX_SERVER_CERT_PEM: &str =
include_str!("../../test_data/attest_cert.pem");
const SERVER_MRENCLAVE: Measurement = Measurement::new(hex::decode_const(
b"738f61792535f905807365a0f6023275b6a44972f48986c94aa7976c31bf1eb6",
));
const INTEL_SGX_ROOT_CA_CERT_PEM: &str =
include_str!("../../test_data/intel-sgx-root-ca.pem");
#[test]
fn test_intel_sgx_trust_anchor_der_pem_equal() {
let sgx_trust_anchor_der1 = INTEL_SGX_ROOT_CA_CERT_DER;
let sgx_trust_anchor_der2 = CertificateDer::from_pem_slice(
INTEL_SGX_ROOT_CA_CERT_PEM.as_bytes(),
)
.unwrap();
assert_eq!(sgx_trust_anchor_der1, &sgx_trust_anchor_der2);
let _sgx_trust_anchor = &*INTEL_SGX_TRUST_ANCHOR;
}
#[test]
fn test_verify_sgx_server_quote() {
let cert_der =
CertificateDer::from_pem_slice(SGX_SERVER_CERT_PEM.as_bytes())
.unwrap();
let evidence = AttestEvidence::parse_cert_der(&cert_der).unwrap();
let now = UnixTime::now();
let verifier = SgxQuoteVerifier;
let report = verifier.verify(&evidence.cert_ext.quote, now).unwrap();
let enclave_policy = EnclavePolicy {
allow_debug: true,
trusted_mrenclaves: Some(vec![SERVER_MRENCLAVE]),
trusted_mrsigner: None,
};
enclave_policy.verify(&report).unwrap();
}
#[test]
fn test_verify_sgx_server_cert() {
let cert_der =
CertificateDer::from_pem_slice(SGX_SERVER_CERT_PEM.as_bytes())
.unwrap();
let verifier = AttestationCertVerifier {
expect_dummy_quote: false,
enclave_policy: EnclavePolicy {
allow_debug: true,
trusted_mrenclaves: Some(vec![SERVER_MRENCLAVE]),
trusted_mrsigner: None,
},
};
let intermediates = &[];
let ocsp_response = &[];
verifier
.verify_server_cert(
&cert_der,
intermediates,
&ServerName::try_from("localhost").unwrap(),
ocsp_response,
UnixTime::now(),
)
.unwrap();
}
}