diff --git a/dstack/ra-tls/Cargo.toml b/dstack/ra-tls/Cargo.toml index 3725f3330..fbc1b9708 100644 --- a/dstack/ra-tls/Cargo.toml +++ b/dstack/ra-tls/Cargo.toml @@ -24,7 +24,7 @@ rustls-pki-types.workspace = true serde.workspace = true serde_json.workspace = true sha2.workspace = true -x509-parser.workspace = true +x509-parser = { workspace = true, features = ["verify"] } yasna.workspace = true tracing.workspace = true sha3.workspace = true diff --git a/dstack/ra-tls/src/attestation.rs b/dstack/ra-tls/src/attestation.rs index 6d120020a..01b15afed 100644 --- a/dstack/ra-tls/src/attestation.rs +++ b/dstack/ra-tls/src/attestation.rs @@ -53,11 +53,42 @@ pub async fn verify_pem(cert: &[u8], verifier: &AttestationVerifier) -> Result) -> Result<()> { + cert.verify_signature(None) + .context("certificate self-signature verification failed")?; + if !cert.validity().is_valid() { + bail!("certificate is outside its validity period"); + } + let key_usage = cert + .key_usage() + .context("failed to decode certificate key usage")? + .context("certificate key usage extension missing")?; + if !key_usage.value.digital_signature() { + bail!("certificate key usage does not permit digital signatures"); + } + let extended = cert + .extended_key_usage() + .context("failed to decode certificate extended key usage")? + .context("certificate extended key usage extension missing")?; + if !extended.value.server_auth && !extended.value.client_auth { + bail!("certificate extended key usage permits neither server nor client authentication"); + } + let san = cert + .subject_alternative_name() + .context("failed to decode certificate SAN")? + .context("certificate SAN extension missing")?; + if san.value.general_names.is_empty() { + bail!("certificate SAN extension is empty"); + } + Ok(()) +} + /// Verify the RA-TLS attestation embedded in a parsed X.509 certificate. async fn verify_cert( cert: &x509_parser::prelude::X509Certificate<'_>, verifier: &AttestationVerifier, ) -> Result { + verify_certificate_profile(cert)?; let attestation = from_cert(cert)?.context("RA-TLS attestation extension missing")?; let public_key_der = cert.tbs_certificate.public_key().raw.to_vec(); if public_key_der.is_empty() { @@ -130,9 +161,11 @@ mod tests { #[tokio::test] async fn verify_der_rejects_missing_attestation_extension() { let key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256).unwrap(); + let alt_names = vec!["missing-attestation.example".to_string()]; let cert = CertRequest::builder() .key(&key) .subject("missing-attestation.example") + .alt_names(&alt_names) .usage_server_auth(true) .build() .self_signed() @@ -149,9 +182,11 @@ mod tests { async fn verify_der_rejects_attestation_not_bound_to_cert_key() { let key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256).unwrap(); let attestation = fake_tdx_attestation([0u8; 64]); + let alt_names = vec!["mismatched-attestation.example".to_string()]; let cert = CertRequest::builder() .key(&key) .subject("mismatched-attestation.example") + .alt_names(&alt_names) .usage_server_auth(true) .attestation(&attestation) .build()