@@ -137,7 +137,7 @@ async fn verify_azure_attestation_with_given_timestamp(
137137 let hcl_ak_pub = hcl_report. ak_pub ( ) ?;
138138
139139 // Get attestation key from runtime claims
140- let ak_from_claims = {
140+ let ( ak_from_claims, user_data_input ) = {
141141 let runtime_data_raw = hcl_report. var_data ( ) ;
142142 let claims: HclRuntimeClaims = serde_json:: from_slice ( runtime_data_raw) ?;
143143
@@ -147,14 +147,26 @@ async fn verify_azure_attestation_with_given_timestamp(
147147 . find ( |k| k. kid == "HCLAkPub" )
148148 . ok_or ( MaaError :: ClaimsMissingHCLAkPub ) ?;
149149
150- RsaPubKey :: from_jwk ( ak_jwk) ?
150+ let user_data = claims
151+ . user_data
152+ . as_deref ( )
153+ . ok_or ( MaaError :: ClaimsMissingUserData ) ?;
154+ let user_data_bytes = hex:: decode ( user_data) ?;
155+ let user_data_input: [ u8 ; 64 ] = user_data_bytes
156+ . try_into ( )
157+ . map_err ( |_| MaaError :: ClaimsUserDataBadLength ) ?;
158+
159+ ( RsaPubKey :: from_jwk ( ak_jwk) ?, user_data_input)
151160 } ;
152161
153162 // Check that the TD report input data matches the HCL var data hash
154163 let td_report: az_tdx_vtpm:: tdx:: TdReport = hcl_report. try_into ( ) ?;
155164 if var_data_hash != td_report. report_mac . reportdata [ ..32 ] {
156165 return Err ( MaaError :: TdReportInputMismatch ) ;
157166 }
167+ if user_data_input != expected_input_data {
168+ return Err ( MaaError :: ClaimsUserDataInputMismatch ) ;
169+ }
158170
159171 // Verify the vTPM quote
160172 let vtpm_quote = attestation_document. tpm_attestation . quote ;
@@ -217,9 +229,8 @@ struct HclRuntimeClaims {
217229 #[ allow( unused) ]
218230 #[ serde( rename = "vm-configuration" ) ]
219231 vm_config : Option < serde_json:: Value > ,
220- #[ allow( unused) ]
221232 #[ serde( rename = "user-data" ) ]
222- user_data : Option < serde_json :: Value > ,
233+ user_data : Option < String > ,
223234}
224235
225236/// This is only used as a common type to compare public keys with different formats
@@ -295,6 +306,8 @@ pub enum MaaError {
295306 TdReportInputMismatch ,
296307 #[ error( "Base64 decode: {0}" ) ]
297308 Base64 ( #[ from] base64:: DecodeError ) ,
309+ #[ error( "Hex decode: {0}" ) ]
310+ Hex ( #[ from] hex:: FromHexError ) ,
298311 #[ error( "Attestation Key from HCL runtime claims does not match that from HCL report" ) ]
299312 AkFromClaimsNotEqualAkFromHcl ,
300313 #[ error( "Attestation Key from HCL runtime claims does not match that from attestation key certificate" ) ]
@@ -317,6 +330,12 @@ pub enum MaaError {
317330 JwkParse ,
318331 #[ error( "HCL runtime claims is missing HCLAkPub field" ) ]
319332 ClaimsMissingHCLAkPub ,
333+ #[ error( "HCL runtime claims is missing user-data field" ) ]
334+ ClaimsMissingUserData ,
335+ #[ error( "HCL runtime claims user-data must decode to exactly 64 bytes" ) ]
336+ ClaimsUserDataBadLength ,
337+ #[ error( "HCL runtime claims user-data does not match expected report input data" ) ]
338+ ClaimsUserDataInputMismatch ,
320339 #[ error( "DCAP verification: {0}" ) ]
321340 DcapVerification ( #[ from] crate :: attestation:: dcap:: DcapVerificationError ) ,
322341}
@@ -327,6 +346,18 @@ mod tests {
327346
328347 use super :: * ;
329348
349+ fn input_data_from_attestation ( attestation_bytes : & [ u8 ] ) -> [ u8 ; 64 ] {
350+ let attestation_document: AttestationDocument =
351+ serde_json:: from_slice ( attestation_bytes) . unwrap ( ) ;
352+ let hcl_report_bytes = BASE64_URL_SAFE
353+ . decode ( attestation_document. hcl_report_base64 )
354+ . unwrap ( ) ;
355+ let hcl_report = hcl:: HclReport :: new ( hcl_report_bytes) . unwrap ( ) ;
356+ let claims: HclRuntimeClaims = serde_json:: from_slice ( hcl_report. var_data ( ) ) . unwrap ( ) ;
357+ let user_data_hex = claims. user_data . unwrap ( ) ;
358+ hex:: decode ( user_data_hex) . unwrap ( ) . try_into ( ) . unwrap ( )
359+ }
360+
330361 #[ tokio:: test]
331362 async fn test_decode_hcl ( ) {
332363 // From cvm-reverse-proxy/internal/attestation/azure/tdx/testdata/hclreport.bin
@@ -395,4 +426,31 @@ mod tests {
395426
396427 measurement_policy. check_measurement ( & measurements) . unwrap ( ) ;
397428 }
429+
430+ #[ tokio:: test]
431+ async fn test_verify_fails_on_input_mismatch ( ) {
432+ let attestation_bytes: & ' static [ u8 ] =
433+ include_bytes ! ( "../../../test-assets/azure-tdx-1764662251380464271" ) ;
434+ let now = 1771423480 ;
435+
436+ let mut expected_input_data = input_data_from_attestation ( attestation_bytes) ;
437+ expected_input_data[ 63 ] ^= 0x01 ;
438+
439+ let collateral_bytes: & ' static [ u8 ] =
440+ include_bytes ! ( "../../../test-assets/azure-collateral02.json" ) ;
441+ let collateral = serde_json:: from_slice ( collateral_bytes) . unwrap ( ) ;
442+
443+ let err = verify_azure_attestation_with_given_timestamp (
444+ attestation_bytes. to_vec ( ) ,
445+ expected_input_data,
446+ None ,
447+ Some ( collateral) ,
448+ now,
449+ false ,
450+ )
451+ . await
452+ . unwrap_err ( ) ;
453+
454+ assert ! ( matches!( err, MaaError :: ClaimsUserDataInputMismatch ) ) ;
455+ }
398456}
0 commit comments