@@ -298,6 +298,12 @@ pub struct VmConfig {
298298 /// `tdx_attestation_variant = "lite"` and omitted for legacy TDX.
299299 #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
300300 pub tdx_measurement : Option < TdxOsImageMeasurementDocument > ,
301+ /// GCP TDX no-image-download measurement material. Present for GCP
302+ /// deployments so `os_image_hash` can remain the unified image digest
303+ /// (`sha256(sha256sum.txt)`) while the verifier still binds the TPM UKI
304+ /// Authenticode event to that digest.
305+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
306+ pub gcp_measurement : Option < GcpOsImageMeasurementDocument > ,
301307}
302308
303309/// One OVMF SEV metadata section (gpa/size/type) that affects the SEV-SNP
@@ -331,6 +337,7 @@ fn sha256(bytes: &[u8]) -> [u8; 32] {
331337
332338pub const TDX_MEASUREMENT_FILENAME : & str = "measurement.tdx.cbor" ;
333339pub const SNP_MEASUREMENT_FILENAME : & str = "measurement.snp.cbor" ;
340+ pub const GCP_MEASUREMENT_FILENAME : & str = "measurement.gcp.cbor" ;
334341
335342pub fn image_hash_from_sha256sum ( checksum_file : & [ u8 ] ) -> [ u8 ; 32 ] {
336343 sha256 ( checksum_file)
@@ -401,6 +408,122 @@ pub fn verify_measurement_material(
401408 Ok ( ( ) )
402409}
403410
411+ /// Image-invariant GCP TDX measurement material. GCP's TPM event log measures
412+ /// the UKI as a PE/COFF Authenticode SHA-256 digest. The unified image identity
413+ /// remains `sha256(sha256sum.txt)`; this material is bound to that identity by
414+ /// the `measurement.gcp.cbor` entry in `sha256sum.txt`.
415+ #[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
416+ pub struct GcpOsImageMeasurement {
417+ #[ serde( with = "hex_bytes" ) ]
418+ pub uki_authenticode_sha256 : Vec < u8 > ,
419+ }
420+
421+ #[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
422+ struct CborGcpOsImageMeasurement {
423+ version : u32 ,
424+ #[ serde( rename = "uki_auth" , with = "hex_bytes" ) ]
425+ uki_authenticode_sha256 : Vec < u8 > ,
426+ }
427+
428+ impl From < & GcpOsImageMeasurement > for CborGcpOsImageMeasurement {
429+ fn from ( measurement : & GcpOsImageMeasurement ) -> Self {
430+ Self {
431+ version : GcpOsImageMeasurement :: VERSION ,
432+ uki_authenticode_sha256 : measurement. uki_authenticode_sha256 . clone ( ) ,
433+ }
434+ }
435+ }
436+
437+ impl From < CborGcpOsImageMeasurement > for GcpOsImageMeasurement {
438+ fn from ( measurement : CborGcpOsImageMeasurement ) -> Self {
439+ Self {
440+ uki_authenticode_sha256 : measurement. uki_authenticode_sha256 ,
441+ }
442+ }
443+ }
444+
445+ impl GcpOsImageMeasurement {
446+ pub const VERSION : u32 = 1 ;
447+
448+ pub fn new ( uki_authenticode_sha256 : Vec < u8 > ) -> Self {
449+ Self {
450+ uki_authenticode_sha256,
451+ }
452+ }
453+
454+ pub fn to_cbor_vec ( & self ) -> Vec < u8 > {
455+ cbor_to_vec (
456+ & CborGcpOsImageMeasurement :: from ( self ) ,
457+ "GcpOsImageMeasurement" ,
458+ )
459+ }
460+
461+ pub fn from_cbor_slice ( bytes : & [ u8 ] ) -> Result < Self , String > {
462+ let measurement: CborGcpOsImageMeasurement =
463+ cbor_from_slice ( bytes, "GcpOsImageMeasurement" ) ?;
464+ if measurement. version != Self :: VERSION {
465+ return Err ( format ! (
466+ "GcpOsImageMeasurement unsupported version {}, expected {}" ,
467+ measurement. version,
468+ Self :: VERSION
469+ ) ) ;
470+ }
471+ Ok ( measurement. into ( ) )
472+ }
473+
474+ pub fn cbor_json_value_from_slice ( bytes : & [ u8 ] ) -> Result < serde_json:: Value , String > {
475+ let measurement: CborGcpOsImageMeasurement =
476+ cbor_from_slice ( bytes, "GcpOsImageMeasurement" ) ?;
477+ serde_json:: to_value ( measurement)
478+ . map_err ( |e| format ! ( "GcpOsImageMeasurement: failed to convert to JSON: {e}" ) )
479+ }
480+
481+ pub fn measurement_hash ( & self ) -> [ u8 ; 32 ] {
482+ sha256 ( & self . to_cbor_vec ( ) )
483+ }
484+ }
485+
486+ #[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
487+ pub struct GcpOsImageMeasurementDocument {
488+ /// Raw checksum file bytes (`sha256sum.txt`). `sha256(checksum_file)` is
489+ /// the unified `os_image_hash`.
490+ #[ serde( with = "serde_human_bytes::base64" ) ]
491+ pub checksum_file : Vec < u8 > ,
492+ /// Raw bytes of `measurement.gcp.cbor`.
493+ #[ serde( with = "serde_human_bytes::base64" ) ]
494+ pub measurement : Vec < u8 > ,
495+ }
496+
497+ impl GcpOsImageMeasurementDocument {
498+ pub fn new ( checksum_file : Vec < u8 > , measurement : Vec < u8 > ) -> Self {
499+ Self {
500+ checksum_file,
501+ measurement,
502+ }
503+ }
504+
505+ pub fn from_measurement ( checksum_file : Vec < u8 > , measurement : GcpOsImageMeasurement ) -> Self {
506+ Self :: new ( checksum_file, measurement. to_cbor_vec ( ) )
507+ }
508+
509+ pub fn decode_measurement ( & self ) -> Result < GcpOsImageMeasurement , String > {
510+ GcpOsImageMeasurement :: from_cbor_slice ( & self . measurement )
511+ }
512+
513+ pub fn decode_measurement_value ( & self ) -> Result < serde_json:: Value , String > {
514+ GcpOsImageMeasurement :: cbor_json_value_from_slice ( & self . measurement )
515+ }
516+
517+ pub fn verify ( & self , os_image_hash : & [ u8 ] ) -> Result < ( ) , String > {
518+ verify_measurement_material (
519+ os_image_hash,
520+ & self . checksum_file ,
521+ & self . measurement ,
522+ GCP_MEASUREMENT_FILENAME ,
523+ )
524+ }
525+ }
526+
404527#[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
405528struct CborOvmfSection {
406529 gpa : u64 ,
@@ -796,6 +919,8 @@ pub struct OsImageMeasurementDocument {
796919 pub tdx : Option < TdxOsImageMeasurementDocument > ,
797920 #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
798921 pub snp : Option < SevOsImageMeasurementDocument > ,
922+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
923+ pub gcp : Option < GcpOsImageMeasurementDocument > ,
799924}
800925
801926impl OsImageMeasurementDocument {
@@ -804,11 +929,13 @@ impl OsImageMeasurementDocument {
804929 pub fn new (
805930 tdx : Option < TdxOsImageMeasurementDocument > ,
806931 snp : Option < SevOsImageMeasurementDocument > ,
932+ gcp : Option < GcpOsImageMeasurementDocument > ,
807933 ) -> Self {
808934 Self {
809935 version : Self :: VERSION ,
810936 tdx,
811937 snp,
938+ gcp,
812939 }
813940 }
814941}
0 commit comments