Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 30 additions & 22 deletions dstack/ra-tls/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,25 @@ pub use dstack_attest::attestation::*;
use crate::{oids, traits::CertExt};
use anyhow::{bail, Context, Result};

/// Verified RA-TLS certificate evidence.
/// Attestation evidence verified and bound to an RA-TLS certificate key.
///
/// This is the certificate-level endpoint identity proof: the embedded dstack
/// attestation has been cryptographically verified and its `report_data` is
/// bound to the certificate SubjectPublicKeyInfo.
pub struct VerifiedRaTlsCert {
/// DER-encoded SubjectPublicKeyInfo from the verified certificate.
/// This type proves only the embedded attestation and its binding to the
/// certificate SubjectPublicKeyInfo. It does not authenticate the certificate
/// issuer or any other certificate extension. Application identity asserted by
/// a KMS-issued certificate is an issuer claim and must be consumed only after
/// normal certificate-chain verification.
pub struct VerifiedRaTlsAttestation {
/// DER-encoded SubjectPublicKeyInfo bound to the attestation.
pub public_key_der: Vec<u8>,
/// Verified dstack attestation embedded in the certificate.
pub attestation: VerifiedAttestation,
/// Optional app id certificate extension.
pub app_id: Option<Vec<u8>>,
/// Optional app info certificate extension.
pub app_info: Option<AppInfo>,
/// Optional dstack certificate usage extension.
pub special_usage: Option<String>,
}

impl VerifiedRaTlsAttestation {
/// Decode application identity from the verified attestation evidence.
pub fn decode_app_info(&self, allow_dummy: bool) -> Result<AppInfo> {
self.attestation.decode_app_info(allow_dummy)
}
}

/// Extract attestation from x509 certificate
Expand All @@ -41,14 +44,25 @@ pub fn from_der(cert: &[u8]) -> Result<Option<VersionedAttestation>> {
/// DER SubjectPublicKeyInfo. That binding prevents an operator-controlled
/// network endpoint from reusing valid attestation evidence with a different
/// TLS key.
pub async fn verify_der(cert: &[u8], verifier: &AttestationVerifier) -> Result<VerifiedRaTlsCert> {
///
/// This function does not verify a certificate chain and deliberately does not
/// return non-attestation certificate extensions. KMS-issued certificates may
/// omit attestation entirely; authenticate their issuer claims through the
/// certificate chain instead.
pub async fn verify_der(
cert: &[u8],
verifier: &AttestationVerifier,
) -> Result<VerifiedRaTlsAttestation> {
let (_, cert) =
x509_parser::parse_x509_certificate(cert).context("failed to parse certificate")?;
verify_cert(&cert, verifier).await
}

/// Verify the RA-TLS attestation embedded in a PEM-encoded X.509 certificate.
pub async fn verify_pem(cert: &[u8], verifier: &AttestationVerifier) -> Result<VerifiedRaTlsCert> {
pub async fn verify_pem(
cert: &[u8],
verifier: &AttestationVerifier,
) -> Result<VerifiedRaTlsAttestation> {
let (_, pem) = x509_parser::pem::parse_x509_pem(cert).context("failed to parse PEM")?;
verify_der(&pem.contents, verifier).await
}
Expand All @@ -57,26 +71,20 @@ pub async fn verify_pem(cert: &[u8], verifier: &AttestationVerifier) -> Result<V
async fn verify_cert(
cert: &x509_parser::prelude::X509Certificate<'_>,
verifier: &AttestationVerifier,
) -> Result<VerifiedRaTlsCert> {
) -> Result<VerifiedRaTlsAttestation> {
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() {
bail!("certificate SubjectPublicKeyInfo is empty");
}
let app_id = cert.get_app_id()?;
let app_info = cert.get_app_info()?;
let special_usage = cert.get_special_usage()?;
let attestation = attestation
.into_v1()
.verify_with_ra_pubkey(&public_key_der, verifier)
.await
.context("RA-TLS attestation verification failed")?;
Ok(VerifiedRaTlsCert {
Ok(VerifiedRaTlsAttestation {
public_key_der,
attestation,
app_id,
app_info,
special_usage,
})
}

Expand Down
4 changes: 1 addition & 3 deletions dstack/verifier/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ async fn run_cert_oneshot(file_path: &str, config: &Config) -> anyhow::Result<()
.await
.map_err(|e| anyhow::anyhow!("failed to verify RA-TLS certificate: {:#}", e))?;

let app_info = verified.attestation.decode_app_info(false).ok();
let app_info = verified.decode_app_info(false).ok();
// Bind the reported os_image_hash to the attested boot measurement. For
// every platform except TDX legacy this is a self-contained check (no image
// download); relying parties should only trust `os_image_hash` when
Expand All @@ -233,8 +233,6 @@ async fn run_cert_oneshot(file_path: &str, config: &Config) -> anyhow::Result<()
"tee_variant": verified.attestation.quote.variant(),
"report_data": hex::encode(verified.attestation.report_data),
"public_key_der": hex::encode(&verified.public_key_der),
"app_id_extension": verified.app_id.as_ref().map(hex::encode),
"special_usage": verified.special_usage,
"app_info": app_info.map(|info| serde_json::json!({
"app_id": hex::encode(info.app_id),
"compose_hash": hex::encode(info.compose_hash),
Expand Down
Loading