22use std:: string:: FromUtf8Error ;
33
44use az_tdx_vtpm:: { hcl, imds, report, vtpm} ;
5- use tokio_rustls:: rustls:: pki_types:: CertificateDer ;
6- // use openssl::pkey::{PKey, Public};
75use base64:: { engine:: general_purpose:: URL_SAFE as BASE64_URL_SAFE , Engine as _} ;
8- use reqwest :: Client ;
9- use serde:: Serialize ;
6+ use openssl :: pkey :: PKey ;
7+ use serde:: { Deserialize , Serialize } ;
108use thiserror:: Error ;
9+ use tokio_rustls:: rustls:: pki_types:: CertificateDer ;
1110
12- use crate :: attestation:: { compute_report_input, AttestationError } ;
13-
14- // #[derive(Clone)]
15- // pub struct MaaGenerator {
16- // }
11+ use crate :: attestation:: {
12+ self , compute_report_input,
13+ measurements:: { CvmImageMeasurements , Measurements , PlatformMeasurements } ,
14+ } ;
1715
1816pub async fn create_azure_attestation (
1917 cert_chain : & [ CertificateDer < ' _ > ] ,
2018 exporter : [ u8 ; 32 ] ,
2119) -> Result < Vec < u8 > , MaaError > {
22- let maa_endpoint = "todo" . to_string ( ) ;
23- let aad_access_token = "todo" . to_string ( ) ;
2420 let input_data = compute_report_input ( cert_chain, exporter)
2521 . map_err ( |e| MaaError :: InputData ( e. to_string ( ) ) ) ?;
2622
2723 let td_report = report:: get_report ( ) ?;
2824
29- // let mrtd = td_report.tdinfo.mrtd;
30- // let rtmr0 = td_report.tdinfo.rtrm[0];
31- // let rtmr1 = td_report.tdinfo.rtrm[1];
32- // let rtmr2 = td_report.tdinfo.rtrm[2];
33- // let rtmr3 = td_report.tdinfo.rtrm[3];
34-
3525 // This makes a request to Azure Instance metadata service and gives us a binary response
3626 let td_quote_bytes = imds:: get_td_quote ( & td_report) ?;
3727
3828 let hcl_report_bytes = vtpm:: get_report_with_report_data ( & input_data) ?;
39- let hcl_report = hcl:: HclReport :: new ( hcl_report_bytes) ?;
40- let hcl_var_data = hcl_report. var_data ( ) ;
4129
42- // let bytes = vtpm::get_report().unwrap();
43- // let hcl_report = hcl::HclReport::new(bytes).unwrap();
44- // let var_data_hash = hcl_report.var_data_sha256();
45- // let _ak_pub = hcl_report.ak_pub().unwrap();
46- //
47- // let td_report: tdx::TdReport = hcl_report.try_into().unwrap();
48- // assert!(var_data_hash == td_report.report_mac.reportdata[..32]);
30+ // let quote_b64 = ;
31+ // let runtime_b64 = BASE64_URL_SAFE.encode(hcl_var_data);
4932
50- // let nonce = "a nonce".as_bytes();
51- //
52- // let tpm_quote = vtpm::get_quote(nonce).unwrap();
53- // let der = ak_pub.key.try_to_der().unwrap();
54- // let pub_key = PKey::public_key_from_der(&der).unwrap();
55- // tpm_quote.verify(&pub_key, nonce).unwrap();
56-
57- let quote_b64 = BASE64_URL_SAFE . encode ( & td_quote_bytes) ;
58- let runtime_b64 = BASE64_URL_SAFE . encode ( hcl_var_data) ;
59-
60- let tdx_vm_request = TdxVmRequest {
61- quote : quote_b64,
62- runtime_data : Some ( RuntimeData {
63- data : runtime_b64,
64- data_type : "Binary" ,
65- } ) ,
66- nonce : Some ( "my-app-nonce-or-session-id" . to_string ( ) ) , // TODO
33+ let tpm_attestation = TpmAttest {
34+ ak_pub : vtpm:: get_ak_pub ( ) ?,
35+ quote : vtpm:: get_quote ( & input_data) ?,
36+ event_log : Vec :: new ( ) ,
37+ instance_info : None ,
6738 } ;
68- let jwt_token = call_tdxvm_attestation ( maa_endpoint, aad_access_token, & tdx_vm_request) . await ?;
69- Ok ( jwt_token. as_bytes ( ) . to_vec ( ) )
70- }
7139
72- /// Get a signed JWT from the azure API
73- async fn call_tdxvm_attestation (
74- maa_endpoint : String ,
75- aad_access_token : String ,
76- tdx_vm_request : & TdxVmRequest < ' _ > ,
77- ) -> Result < String , MaaError > {
78- let url = format ! ( "{}/attest/TdxVm?api-version=2025-06-01" , maa_endpoint) ;
79-
80- let client = Client :: new ( ) ;
81- let res = client
82- . post ( & url)
83- . bearer_auth ( & aad_access_token)
84- . header ( "Content-Type" , "application/json" )
85- . body ( serde_json:: to_vec ( tdx_vm_request) ?)
86- . send ( )
87- . await ?;
88-
89- let status = res. status ( ) ;
90- let text = res. text ( ) . await ?;
91-
92- if !status. is_success ( ) {
93- return Err ( MaaError :: MaaProvider ( status, text) ) ;
94- }
95-
96- #[ derive( serde:: Deserialize ) ]
97- struct AttestationResponse {
98- token : String ,
99- }
40+ let attestation_document = AttestationDocument {
41+ tdx_quote_base64 : BASE64_URL_SAFE . encode ( & td_quote_bytes) ,
42+ hcl_report_base64 : BASE64_URL_SAFE . encode ( & hcl_report_bytes) ,
43+ tpm_attestation,
44+ } ;
10045
101- let parsed: AttestationResponse = serde_json:: from_str ( & text) ?;
102- Ok ( parsed. token ) // Microsoft-signed JWT
46+ Ok ( serde_json:: to_vec ( & attestation_document) ?)
10347}
10448
10549pub async fn verify_azure_attestation (
10650 input : Vec < u8 > ,
10751 cert_chain : & [ CertificateDer < ' _ > ] ,
10852 exporter : [ u8 ; 32 ] ,
53+ pccs_url : Option < String > ,
10954) -> Result < super :: measurements:: Measurements , MaaError > {
110- let _input_data = compute_report_input ( cert_chain, exporter)
55+ let input_data = compute_report_input ( cert_chain, exporter)
11156 . map_err ( |e| MaaError :: InputData ( e. to_string ( ) ) ) ?;
112- let token = String :: from_utf8 ( input) ?;
11357
114- decode_jwt ( & token ) . await . unwrap ( ) ;
58+ let attestation_document : AttestationDocument = serde_json :: from_slice ( & input ) ? ;
11559
116- todo ! ( )
117- }
60+ // Verify TDX quote (same as with DCAP) - TODO deduplicate this code
61+ let now = std:: time:: SystemTime :: now ( )
62+ . duration_since ( std:: time:: UNIX_EPOCH )
63+ . unwrap ( )
64+ . as_secs ( ) ;
65+
66+ let tdx_quote_bytes = BASE64_URL_SAFE
67+ . decode ( attestation_document. tdx_quote_base64 )
68+ . unwrap ( ) ;
69+
70+ let quote = dcap_qvl:: quote:: Quote :: parse ( & tdx_quote_bytes) . unwrap ( ) ;
11871
119- async fn decode_jwt ( token : & str ) -> Result < ( ) , AttestationError > {
120- // Parse payload (claims) without verification (TODO this will be swapped out once we have the
121- // key-getting logic)
122- let parts: Vec < & str > = token. split ( '.' ) . collect ( ) ;
123- let claims_json = BASE64_URL_SAFE . decode ( parts[ 1 ] ) . unwrap ( ) ;
72+ let ca = quote. ca ( ) . unwrap ( ) ;
73+ let fmspc = hex:: encode_upper ( quote. fmspc ( ) . unwrap ( ) ) ;
74+ let collateral = dcap_qvl:: collateral:: get_collateral_for_fmspc (
75+ & pccs_url
76+ . clone ( )
77+ . unwrap_or ( attestation:: dcap:: PCS_URL . to_string ( ) ) ,
78+ fmspc,
79+ ca,
80+ false , // Indicates not SGX
81+ )
82+ . await
83+ . unwrap ( ) ;
12484
125- let claims: serde_json:: Value = serde_json:: from_slice ( & claims_json) . unwrap ( ) ;
126- println ! ( "{claims}" ) ;
127- Ok ( ( ) )
85+ let _verified_report = dcap_qvl:: verify:: verify ( & input, & collateral, now) . unwrap ( ) ;
86+
87+ // Check that hcl_report_bytes (hashed?) matches TDX quote report data
88+ // if get_quote_input_data(quote.report) != quote_input {
89+ // return Err(AttestationError::InputMismatch);
90+ // }
91+
92+ let hcl_report_bytes = BASE64_URL_SAFE
93+ . decode ( attestation_document. hcl_report_base64 )
94+ . unwrap ( ) ;
95+
96+ let hcl_report = hcl:: HclReport :: new ( hcl_report_bytes) ?;
97+ //
98+ let var_data_hash = hcl_report. var_data_sha256 ( ) ;
99+ let hcl_ak_pub = hcl_report. ak_pub ( ) ?;
100+ let td_report: az_tdx_vtpm:: tdx:: TdReport = hcl_report. try_into ( ) ?;
101+ assert ! ( var_data_hash == td_report. report_mac. reportdata[ ..32 ] ) ;
102+
103+ let vtpm_quote = attestation_document. tpm_attestation . quote ;
104+ let hcl_ak_pub_der = hcl_ak_pub. key . try_to_der ( ) . unwrap ( ) ;
105+ let pub_key = PKey :: public_key_from_der ( & hcl_ak_pub_der) . unwrap ( ) ;
106+ vtpm_quote. verify ( & pub_key, & input_data) ?;
107+ let _pcrs = vtpm_quote. pcrs_sha256 ( ) ;
108+
109+ Ok ( Measurements {
110+ platform : PlatformMeasurements :: from_dcap_qvl_quote ( & quote) . unwrap ( ) ,
111+ cvm_image : CvmImageMeasurements :: from_dcap_qvl_quote ( & quote) . unwrap ( ) ,
112+ } )
128113}
129114
130- #[ derive( Serialize ) ]
131- struct RuntimeData < ' a > {
132- data : String , // base64url of VarData bytes
133- #[ serde( rename = "dataType" ) ]
134- data_type : & ' a str , // "Binary" in our case
115+ /// The attestation evidence payload that gets sent over the channel
116+ #[ derive( Debug , Serialize , Deserialize ) ]
117+ struct AttestationDocument {
118+ /// TDX quote from the IMDS
119+ tdx_quote_base64 : String ,
120+ /// Serialized HCL report
121+ hcl_report_base64 : String ,
122+ /// vTPM related evidence
123+ tpm_attestation : TpmAttest ,
135124}
136125
137- #[ derive( Serialize ) ]
138- struct TdxVmRequest < ' a > {
139- quote : String , // base64 (TDX quote)
140- #[ serde( rename = "runtimeData" , skip_serializing_if = "Option::is_none" ) ]
141- runtime_data : Option < RuntimeData < ' a > > ,
142- #[ serde( skip_serializing_if = "Option::is_none" ) ]
143- nonce : Option < String > ,
126+ #[ derive( Debug , Serialize , Deserialize ) ]
127+ pub struct TpmAttest {
128+ /// vTPM Attestation Key (AK) public key
129+ // TODO do we need this? it is already given in HCL report
130+ pub ak_pub : vtpm:: PublicKey ,
131+ /// vTPM quotes over the selected PCR bank(s).
132+ pub quote : vtpm:: Quote ,
133+ /// Raw TCG event log bytes (UEFI + IMA)
134+ ///
135+ /// `/sys/kernel/security/ima/ascii_runtime_measurements`,
136+ /// `/sys/kernel/security/tpm0/binary_bios_measurements`,
137+ pub event_log : Vec < u8 > ,
138+ /// Optional platform / instance metadata used to bind or verify the AK
139+ pub instance_info : Option < Vec < u8 > > ,
144140}
145141
146142#[ derive( Error , Debug ) ]
@@ -163,6 +159,12 @@ pub enum MaaError {
163159 MaaProvider ( http:: StatusCode , String ) ,
164160 #[ error( "Token is bad UTF8: {0}" ) ]
165161 BadUtf8 ( #[ from] FromUtf8Error ) ,
162+ #[ error( "vTPM quote: {0}" ) ]
163+ VtpmQuote ( #[ from] vtpm:: QuoteError ) ,
164+ #[ error( "AK public key: {0}" ) ]
165+ AkPub ( #[ from] vtpm:: AKPubError ) ,
166+ #[ error( "vTPM quote could not be verified: {0}" ) ]
167+ TpmQuoteVerify ( #[ from] vtpm:: VerifyError ) ,
166168}
167169
168170#[ cfg( test) ]
0 commit comments