Skip to content

Commit 43cedd9

Browse files
committed
Tidy/refactor DCAP verification
1 parent f250af4 commit 43cedd9

File tree

4 files changed

+57
-87
lines changed

4 files changed

+57
-87
lines changed

src/attestation/azure/mod.rs

Lines changed: 11 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
mod ak_certificate;
33
mod nv_index;
44
use ak_certificate::{read_ak_certificate_from_tpm, verify_ak_cert_with_azure_roots};
5-
use std::string::FromUtf8Error;
65

76
use az_tdx_vtpm::{hcl, imds, report, vtpm};
87
use base64::{engine::general_purpose::URL_SAFE as BASE64_URL_SAFE, Engine as _};
@@ -12,11 +11,7 @@ use serde::{Deserialize, Serialize};
1211
use thiserror::Error;
1312
use x509_parser::prelude::*;
1413

15-
use crate::attestation::{
16-
self,
17-
dcap::get_quote_input_data,
18-
measurements::{CvmImageMeasurements, Measurements, PlatformMeasurements},
19-
};
14+
use crate::attestation::dcap::verify_dcap_attestation;
2015

2116
/// The attestation evidence payload that gets sent over the channel
2217
#[derive(Debug, Serialize, Deserialize)]
@@ -100,25 +95,6 @@ async fn verify_azure_attestation_with_given_timestamp(
10095
) -> Result<super::measurements::Measurements, MaaError> {
10196
let attestation_document: AttestationDocument = serde_json::from_slice(&input)?;
10297

103-
// Verify TDX quote (same as with DCAP) - TODO deduplicate this code
104-
let tdx_quote_bytes = BASE64_URL_SAFE.decode(attestation_document.tdx_quote_base64)?;
105-
106-
let quote = dcap_qvl::quote::Quote::parse(&tdx_quote_bytes)?;
107-
108-
let ca = quote.ca()?;
109-
let fmspc = hex::encode_upper(quote.fmspc()?);
110-
let collateral = dcap_qvl::collateral::get_collateral_for_fmspc(
111-
&pccs_url
112-
.clone()
113-
.unwrap_or(attestation::dcap::PCS_URL.to_string()),
114-
fmspc,
115-
ca,
116-
false, // Indicates not SGX
117-
)
118-
.await?;
119-
120-
let _verified_report = dcap_qvl::verify::verify(&tdx_quote_bytes, &collateral, now)?;
121-
12298
let hcl_report_bytes = BASE64_URL_SAFE.decode(attestation_document.hcl_report_base64)?;
12399

124100
let hcl_report = hcl::HclReport::new(hcl_report_bytes)?;
@@ -127,9 +103,11 @@ async fn verify_azure_attestation_with_given_timestamp(
127103
// Check that HCL var data hash matches TDX quote report data
128104
let mut expected_tdx_input_data = [0u8; 64];
129105
expected_tdx_input_data[..32].copy_from_slice(&var_data_hash);
130-
if get_quote_input_data(quote.report.clone()) != expected_tdx_input_data {
131-
return Err(MaaError::TdxQuoteInputMismatch);
132-
}
106+
107+
// Do DCAP verification
108+
let tdx_quote_bytes = BASE64_URL_SAFE.decode(attestation_document.tdx_quote_base64)?;
109+
let measurements =
110+
verify_dcap_attestation(tdx_quote_bytes, expected_tdx_input_data, pccs_url).await?;
133111

134112
let hcl_ak_pub = hcl_report.ak_pub()?;
135113

@@ -191,12 +169,7 @@ async fn verify_azure_attestation_with_given_timestamp(
191169
// Verify the AK certificate against microsoft root cert
192170
verify_ak_cert_with_azure_roots(ak_certificate_der_without_trailing_data, now)?;
193171

194-
Ok(Measurements {
195-
platform: PlatformMeasurements::from_dcap_qvl_quote(&quote)
196-
.map_err(|_| MaaError::CannotExtractMeasurementsFromQuote)?,
197-
cvm_image: CvmImageMeasurements::from_dcap_qvl_quote(&quote)
198-
.map_err(|_| MaaError::CannotExtractMeasurementsFromQuote)?,
199-
})
172+
Ok(measurements)
200173
}
201174

202175
/// JSON Web Key used in [HclRuntimeClaims]
@@ -272,8 +245,6 @@ impl RsaPubKey {
272245

273246
#[derive(Error, Debug)]
274247
pub enum MaaError {
275-
#[error("Failed to build input data: {0}")]
276-
InputData(String),
277248
#[error("Report: {0}")]
278249
Report(#[from] az_tdx_vtpm::report::ReportError),
279250
#[error("IMDS: {0}")]
@@ -284,12 +255,6 @@ pub enum MaaError {
284255
Hcl(#[from] hcl::HclError),
285256
#[error("JSON: {0}")]
286257
Json(#[from] serde_json::Error),
287-
#[error("HTTP Client: {0}")]
288-
HttpClient(#[from] reqwest::Error),
289-
#[error("MAA provider response: {0} - {1}")]
290-
MaaProvider(http::StatusCode, String),
291-
#[error("Token is bad UTF8: {0}")]
292-
BadUtf8(#[from] FromUtf8Error),
293258
#[error("vTPM quote: {0}")]
294259
VtpmQuote(#[from] vtpm::QuoteError),
295260
#[error("AK public key: {0}")]
@@ -300,8 +265,6 @@ pub enum MaaError {
300265
TssEsapi(#[from] tss_esapi::Error),
301266
#[error("PEM encode: {0}")]
302267
Pem(#[from] pem_rfc7468::Error),
303-
#[error("TDX quote input does not match hashed HCL var data")]
304-
TdxQuoteInputMismatch,
305268
#[error("TD report input does not match hashed HCL var data")]
306269
TdReportInputMismatch,
307270
#[error("Base64 decode: {0}")]
@@ -312,27 +275,11 @@ pub enum MaaError {
312275
AkFromClaimsNotEqualAkFromCertificate,
313276
#[error("WebPKI: {0}")]
314277
WebPki(#[from] webpki::Error),
315-
#[error("Certificate chain is empty")]
316-
NoCertificate,
317278
#[error("X509 parse: {0}")]
318279
X509Parse(#[from] x509_parser::asn1_rs::Err<x509_parser::error::X509Error>),
319280
#[error("X509: {0}")]
320281
X509(#[from] x509_parser::error::X509Error),
321-
#[error("Quote input is not as expected")]
322-
InputMismatch,
323-
#[error("Configuration mismatch - expected no remote attestation")]
324-
AttestationGivenWhenNoneExpected,
325-
#[error("Configfs-tsm quote generation: {0}")]
326-
QuoteGeneration(#[from] configfs_tsm::QuoteGenerationError),
327-
#[error("SGX quote given when TDX quote expected")]
328-
SgxNotSupported,
329-
#[error("Platform measurements do not match any accepted values")]
330-
UnacceptablePlatformMeasurements,
331-
#[error("OS image measurements do not match any accepted values")]
332-
UnacceptableOsImageMeasurements,
333-
#[error("DCAP quote verification: {0}")]
334-
DcapQvl(#[from] anyhow::Error),
335-
#[error("Cannot convert JSON web key to der")]
282+
#[error("Cannot encode JSON web key as DER")]
336283
JwkConversion,
337284
#[error("OpenSSL: {0}")]
338285
OpenSSL(#[from] ErrorStack),
@@ -344,6 +291,8 @@ pub enum MaaError {
344291
JwkParse,
345292
#[error("HCL runtime claims is missing HCLAkPub field")]
346293
ClaimsMissingHCLAkPub,
294+
#[error("DCAP verification: {0}")]
295+
DcapVerification(#[from] crate::attestation::dcap::DcapVerificationError),
347296
}
348297

349298
#[cfg(test)]
@@ -352,7 +301,7 @@ mod tests {
352301

353302
#[tokio::test]
354303
async fn test_decode_hcl() {
355-
// from cvm-reverse-proxy/internal/attestation/azure/tdx/testdata/hclreport.bin
304+
// From cvm-reverse-proxy/internal/attestation/azure/tdx/testdata/hclreport.bin
356305
let hcl_bytes: &'static [u8] = include_bytes!("../../../test-assets/hclreport.bin");
357306

358307
let hcl_report = hcl::HclReport::new(hcl_bytes.to_vec()).unwrap();

src/attestation/dcap.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use dcap_qvl::{
99
collateral::get_collateral_for_fmspc,
1010
quote::{Quote, Report},
1111
};
12+
use thiserror::Error;
1213

1314
/// For fetching collateral directly from Intel, if no PCCS is specified
1415
pub const PCS_URL: &str = "https://api.trustedservices.intel.com";
@@ -23,7 +24,7 @@ pub async fn verify_dcap_attestation(
2324
input: Vec<u8>,
2425
expected_input_data: [u8; 64],
2526
pccs_url: Option<String>,
26-
) -> Result<Measurements, AttestationError> {
27+
) -> Result<Measurements, DcapVerificationError> {
2728
let (platform_measurements, image_measurements) = if cfg!(not(test)) {
2829
let now = std::time::SystemTime::now()
2930
.duration_since(std::time::UNIX_EPOCH)?
@@ -47,14 +48,14 @@ pub async fn verify_dcap_attestation(
4748
CvmImageMeasurements::from_dcap_qvl_quote(&quote)?,
4849
);
4950
if get_quote_input_data(quote.report) != expected_input_data {
50-
return Err(AttestationError::InputMismatch);
51+
return Err(DcapVerificationError::InputMismatch);
5152
}
5253
measurements
5354
} else {
5455
// In tests we use mock quotes which will fail to verify
5556
let quote = tdx_quote::Quote::from_bytes(&input)?;
5657
if quote.report_input_data() != expected_input_data {
57-
return Err(AttestationError::InputMismatch);
58+
return Err(DcapVerificationError::InputMismatch);
5859
}
5960

6061
(
@@ -97,3 +98,32 @@ pub fn get_quote_input_data(report: Report) -> [u8; 64] {
9798
Report::SgxEnclave(r) => r.report_data,
9899
}
99100
}
101+
102+
/// An error when generating or verifying an attestation
103+
#[derive(Error, Debug)]
104+
pub enum DcapVerificationError {
105+
// #[error("Certificate chain is empty")]
106+
// NoCertificate,
107+
// #[error("X509 parse: {0}")]
108+
// X509Parse(#[from] x509_parser::asn1_rs::Err<x509_parser::error::X509Error>),
109+
// #[error("X509: {0}")]
110+
// X509(#[from] x509_parser::error::X509Error),
111+
#[error("Quote input is not as expected")]
112+
InputMismatch,
113+
// #[error("Configuration mismatch - expected no remote attestation")]
114+
// AttestationGivenWhenNoneExpected,
115+
// #[error("Configfs-tsm quote generation: {0}")]
116+
// QuoteGeneration(#[from] configfs_tsm::QuoteGenerationError),
117+
#[error("SGX quote given when TDX quote expected")]
118+
SgxNotSupported,
119+
// #[error("Platform measurements do not match any accepted values")]
120+
// UnacceptablePlatformMeasurements,
121+
// #[error("OS image measurements do not match any accepted values")]
122+
// UnacceptableOsImageMeasurements,
123+
#[error("System Time: {0}")]
124+
SystemTime(#[from] std::time::SystemTimeError),
125+
#[error("DCAP quote verification: {0}")]
126+
DcapQvl(#[from] anyhow::Error),
127+
#[error("Quote parse: {0}")]
128+
QuoteParse(#[from] tdx_quote::QuoteParseError),
129+
}

src/attestation/measurements.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Measurements and policy for enforcing them when validating a remote attestation
2-
use crate::attestation::{AttestationError, AttestationType};
2+
use crate::attestation::{dcap::DcapVerificationError, AttestationError, AttestationType};
33
use std::{collections::HashMap, path::PathBuf};
44

55
use dcap_qvl::quote::Report;
@@ -18,12 +18,14 @@ pub struct PlatformMeasurements {
1818

1919
impl PlatformMeasurements {
2020
/// Given a quote from the dcap_qvl library, extract the platform measurements
21-
pub fn from_dcap_qvl_quote(quote: &dcap_qvl::quote::Quote) -> Result<Self, AttestationError> {
21+
pub fn from_dcap_qvl_quote(
22+
quote: &dcap_qvl::quote::Quote,
23+
) -> Result<Self, DcapVerificationError> {
2224
let report = match quote.report {
2325
Report::TD10(report) => report,
2426
Report::TD15(report) => report.base,
2527
Report::SgxEnclave(_) => {
26-
return Err(AttestationError::SgxNotSupported);
28+
return Err(DcapVerificationError::SgxNotSupported);
2729
}
2830
};
2931
Ok(Self {
@@ -53,12 +55,14 @@ pub struct CvmImageMeasurements {
5355

5456
impl CvmImageMeasurements {
5557
/// Given a quote from the dcap_qvl library, extract the CVM image / application measurements
56-
pub fn from_dcap_qvl_quote(quote: &dcap_qvl::quote::Quote) -> Result<Self, AttestationError> {
58+
pub fn from_dcap_qvl_quote(
59+
quote: &dcap_qvl::quote::Quote,
60+
) -> Result<Self, DcapVerificationError> {
5761
let report = match quote.report {
5862
Report::TD10(report) => report,
5963
Report::TD15(report) => report.base,
6064
Report::SgxEnclave(_) => {
61-
return Err(AttestationError::SgxNotSupported);
65+
return Err(DcapVerificationError::SgxNotSupported);
6266
}
6367
};
6468
Ok(Self {

src/attestation/mod.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ use parity_scale_codec::{Decode, Encode};
88
use serde::{Deserialize, Serialize};
99
use std::{
1010
fmt::{self, Display, Formatter},
11-
time::{SystemTime, SystemTimeError, UNIX_EPOCH},
11+
time::{SystemTime, UNIX_EPOCH},
1212
};
1313

14-
use tdx_quote::QuoteParseError;
1514
use thiserror::Error;
1615

17-
use crate::attestation::measurements::MeasurementPolicy;
16+
use crate::attestation::{dcap::DcapVerificationError, measurements::MeasurementPolicy};
1817

1918
/// This is the type sent over the channel to provide an attestation
2019
#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)]
@@ -332,24 +331,12 @@ pub enum AttestationError {
332331
X509Parse(#[from] x509_parser::asn1_rs::Err<x509_parser::error::X509Error>),
333332
#[error("X509: {0}")]
334333
X509(#[from] x509_parser::error::X509Error),
335-
#[error("Quote input is not as expected")]
336-
InputMismatch,
337334
#[error("Configuration mismatch - expected no remote attestation")]
338335
AttestationGivenWhenNoneExpected,
339336
#[error("Configfs-tsm quote generation: {0}")]
340337
QuoteGeneration(#[from] configfs_tsm::QuoteGenerationError),
341-
#[error("SGX quote given when TDX quote expected")]
342-
SgxNotSupported,
343-
#[error("Platform measurements do not match any accepted values")]
344-
UnacceptablePlatformMeasurements,
345-
#[error("OS image measurements do not match any accepted values")]
346-
UnacceptableOsImageMeasurements,
347-
#[error("System Time: {0}")]
348-
SystemTime(#[from] SystemTimeError),
349-
#[error("DCAP quote verification: {0}")]
350-
DcapQvl(#[from] anyhow::Error),
351-
#[error("Quote parse: {0}")]
352-
QuoteParse(#[from] QuoteParseError),
338+
#[error("DCAP verification: {0}")]
339+
DcapVerification(#[from] DcapVerificationError),
353340
#[error("Attestation type not supported")]
354341
AttestationTypeNotSupported,
355342
#[error("Attestation type not accepted")]

0 commit comments

Comments
 (0)