@@ -27,12 +27,17 @@ use dstack_types::SysConfig;
2727use dstack_types:: { Platform , VmConfig } ;
2828use ez_hash:: { sha256, Hasher , Sha384 } ;
2929use or_panic:: ResultOrPanic ;
30- use scale:: { Decode , Encode } ;
30+ use scale:: { Decode , Encode , Error as ScaleError , Input , Output } ;
3131use serde:: de:: DeserializeOwned ;
3232use serde:: { Deserialize , Serialize } ;
3333use serde_human_bytes as hex_bytes;
3434use sha2:: Digest as _;
3535
36+ const NATIVE_PLATFORM_TDX : & str = "tdx" ;
37+ const NATIVE_PLATFORM_GCP_TDX : & str = "gcp-tdx" ;
38+ const NATIVE_PLATFORM_NITRO_ENCLAVE : & str = "nitro-enclave" ;
39+ const DSTACK_VARIANT_DSTACK : & str = "dstack" ;
40+ const DSTACK_VARIANT_DSTACK_K8S : & str = "dstack-k8s" ;
3641const DSTACK_TDX : & str = "dstack-tdx" ;
3742const DSTACK_GCP_TDX : & str = "dstack-gcp-tdx" ;
3843const DSTACK_NITRO_ENCLAVE : & str = "dstack-nitro-enclave" ;
@@ -132,7 +137,15 @@ fn report_data_payload_v1(attestation: &AttestationV1) -> Option<&str> {
132137 }
133138}
134139
135- fn legacy_into_v1 ( attestation : Attestation , extensions : Vec < V1Extension > ) -> AttestationV1 {
140+ fn is_cbor_map_prefix ( byte : u8 ) -> bool {
141+ matches ! ( byte, 0xa0 ..=0xbb | 0xbf )
142+ }
143+
144+ fn legacy_into_v1 (
145+ attestation : Attestation ,
146+ variant : & str ,
147+ extensions : Vec < V1Extension > ,
148+ ) -> AttestationV1 {
136149 let Attestation {
137150 quote,
138151 runtime_events,
@@ -141,7 +154,12 @@ fn legacy_into_v1(attestation: Attestation, extensions: Vec<V1Extension>) -> Att
141154 report : _,
142155 } = attestation;
143156
144- let mut platform = PlatformEvidence :: new ( quote. mode ( ) . as_str ( ) ) ;
157+ let native_platform = match & quote {
158+ AttestationQuote :: DstackTdx ( _) => NATIVE_PLATFORM_TDX ,
159+ AttestationQuote :: DstackGcpTdx => NATIVE_PLATFORM_GCP_TDX ,
160+ AttestationQuote :: DstackNitroEnclave => NATIVE_PLATFORM_NITRO_ENCLAVE ,
161+ } ;
162+ let mut platform = PlatformEvidence :: new ( native_platform, variant) ;
145163 match quote {
146164 AttestationQuote :: DstackTdx ( TdxQuote { quote, event_log } ) => {
147165 platform
@@ -182,8 +200,8 @@ fn v1_into_legacy(attestation: AttestationV1) -> Result<Attestation> {
182200 . unwrap_or_default ( ) ;
183201 let config = optional_text_attribute ( & attestation. optional_attributes , ATTR_CONFIG ) ?;
184202
185- let quote = match attestation. platform . platform_id . as_str ( ) {
186- DSTACK_TDX => {
203+ let quote = match attestation. platform . platform . as_str ( ) {
204+ NATIVE_PLATFORM_TDX => {
187205 let quote =
188206 required_bytes_attribute ( & attestation. platform . basic_attributes , ATTR_TDX_QUOTE ) ?;
189207 let event_log: Vec < TdxEvent > = optional_decoded_attribute (
@@ -193,9 +211,9 @@ fn v1_into_legacy(attestation: AttestationV1) -> Result<Attestation> {
193211 . unwrap_or_default ( ) ;
194212 AttestationQuote :: DstackTdx ( TdxQuote { quote, event_log } )
195213 }
196- DSTACK_GCP_TDX => AttestationQuote :: DstackGcpTdx ,
197- DSTACK_NITRO_ENCLAVE => AttestationQuote :: DstackNitroEnclave ,
198- other => bail ! ( "Unsupported platform_id in AttestationV1: {other}" ) ,
214+ NATIVE_PLATFORM_GCP_TDX => AttestationQuote :: DstackGcpTdx ,
215+ NATIVE_PLATFORM_NITRO_ENCLAVE => AttestationQuote :: DstackNitroEnclave ,
216+ other => bail ! ( "Unsupported platform in AttestationV1: {other}" ) ,
199217 } ;
200218
201219 Ok ( Attestation {
@@ -426,8 +444,13 @@ pub struct NsmQuote {
426444 pub document : Vec < u8 > ,
427445}
428446
429- /// Represents a versioned attestation.
430447#[ derive( Clone , Encode , Decode ) ]
448+ enum LegacyVersionedAttestation {
449+ V0 { attestation : Attestation } ,
450+ }
451+
452+ /// Represents a versioned attestation.
453+ #[ derive( Clone ) ]
431454pub enum VersionedAttestation {
432455 /// Legacy SCALE-encoded attestation.
433456 V0 {
@@ -441,15 +464,69 @@ pub enum VersionedAttestation {
441464 } ,
442465}
443466
467+ impl Encode for VersionedAttestation {
468+ fn size_hint ( & self ) -> usize {
469+ self . to_scale ( ) . len ( )
470+ }
471+
472+ fn encode_to < T : Output + ?Sized > ( & self , dest : & mut T ) {
473+ dest. write ( & self . to_scale ( ) ) ;
474+ }
475+ }
476+
477+ impl Decode for VersionedAttestation {
478+ fn decode < I : Input > ( input : & mut I ) -> Result < Self , ScaleError > {
479+ let Some ( remaining_len) = input. remaining_len ( ) ? else {
480+ return Err ( ScaleError :: from (
481+ "VersionedAttestation requires a bounded input to decode" ,
482+ ) ) ;
483+ } ;
484+ let mut bytes = vec ! [ 0u8 ; remaining_len] ;
485+ input. read ( & mut bytes) ?;
486+ Self :: from_scale ( & bytes) . map_err ( |err| {
487+ ScaleError :: from ( std:: io:: Error :: new (
488+ std:: io:: ErrorKind :: InvalidData ,
489+ err. to_string ( ) ,
490+ ) )
491+ } )
492+ }
493+ }
494+
444495impl VersionedAttestation {
445496 /// Decode versioned attestation bytes.
446497 pub fn from_scale ( scale : & [ u8 ] ) -> Result < Self > {
447- Self :: decode ( & mut & scale[ ..] ) . context ( "Failed to decode VersionedAttestation" )
498+ let Some ( first) = scale. first ( ) . copied ( ) else {
499+ bail ! ( "Empty attestation bytes" ) ;
500+ } ;
501+ if first == 0x00 {
502+ let legacy = LegacyVersionedAttestation :: decode ( & mut & scale[ ..] )
503+ . context ( "Failed to decode legacy VersionedAttestation" ) ?;
504+ return match legacy {
505+ LegacyVersionedAttestation :: V0 { attestation } => Ok ( Self :: V0 { attestation } ) ,
506+ } ;
507+ }
508+ if is_cbor_map_prefix ( first) {
509+ let attestation = AttestationV1 :: from_cbor ( scale) ?;
510+ return Ok ( Self :: V1 { attestation } ) ;
511+ }
512+ if first == 0x01 && scale. get ( 1 ) . is_some_and ( |byte| is_cbor_map_prefix ( * byte) ) {
513+ let attestation = AttestationV1 :: from_cbor ( & scale[ 1 ..] ) ?;
514+ return Ok ( Self :: V1 { attestation } ) ;
515+ }
516+ bail ! ( "Unknown attestation wire format" ) ;
448517 }
449518
450519 /// Encode versioned attestation bytes.
451520 pub fn to_scale ( & self ) -> Vec < u8 > {
452- self . encode ( )
521+ match self {
522+ Self :: V0 { attestation } => LegacyVersionedAttestation :: V0 {
523+ attestation : attestation. clone ( ) ,
524+ }
525+ . encode ( ) ,
526+ Self :: V1 { attestation } => attestation
527+ . to_cbor ( )
528+ . or_panic ( "AttestationV1 should encode as CBOR" ) ,
529+ }
453530 }
454531
455532 /// Try to project any version into the legacy attestation structure.
@@ -884,11 +961,19 @@ impl Attestation {
884961
885962impl Attestation {
886963 pub fn into_v1 ( self ) -> AttestationV1 {
887- legacy_into_v1 ( self , Vec :: new ( ) )
964+ legacy_into_v1 ( self , DSTACK_VARIANT_DSTACK , Vec :: new ( ) )
888965 }
889966
890967 pub fn into_v1_with_extensions ( self , extensions : Vec < V1Extension > ) -> AttestationV1 {
891- legacy_into_v1 ( self , extensions)
968+ legacy_into_v1 ( self , DSTACK_VARIANT_DSTACK , extensions)
969+ }
970+
971+ pub fn into_v1_with_variant_and_extensions (
972+ self ,
973+ variant : & str ,
974+ extensions : Vec < V1Extension > ,
975+ ) -> AttestationV1 {
976+ legacy_into_v1 ( self , variant, extensions)
892977 }
893978
894979 /// Verify the quote with optional custom time (testing hook)
@@ -928,11 +1013,14 @@ impl Attestation {
9281013 report_data_payload : String ,
9291014 ) -> VersionedAttestation {
9301015 VersionedAttestation :: V1 {
931- attestation : self . into_v1_with_extensions ( vec ! [ V1Extension :: new(
932- EXT_REPORT_DATA_PAYLOAD ,
933- true ,
934- text_value( report_data_payload) ,
935- ) ] ) ,
1016+ attestation : self . into_v1_with_variant_and_extensions (
1017+ DSTACK_VARIANT_DSTACK_K8S ,
1018+ vec ! [ V1Extension :: new(
1019+ EXT_REPORT_DATA_PAYLOAD ,
1020+ true ,
1021+ text_value( report_data_payload) ,
1022+ ) ] ,
1023+ ) ,
9361024 }
9371025 }
9381026
@@ -1119,9 +1207,15 @@ mod tests {
11191207 let encoded = dummy_tdx_attestation ( report_data)
11201208 . into_versioned_with_report_data_payload ( payload. clone ( ) )
11211209 . to_scale ( ) ;
1210+ assert ! ( matches!( encoded. first( ) , Some ( 0xa0 ..=0xbf ) ) ) ;
11221211 let decoded = VersionedAttestation :: from_scale ( & encoded) . expect ( "decode attestation" ) ;
11231212 assert_eq ! ( decoded. report_data_payload( ) , Some ( payload. as_str( ) ) ) ;
11241213 assert_eq ! ( decoded. clone( ) . into_inner( ) . report_data, report_data) ;
1214+ let VersionedAttestation :: V1 { attestation } = decoded else {
1215+ panic ! ( "expected V1 attestation" ) ;
1216+ } ;
1217+ assert_eq ! ( attestation. platform. platform, NATIVE_PLATFORM_TDX ) ;
1218+ assert_eq ! ( attestation. platform. variant, DSTACK_VARIANT_DSTACK_K8S ) ;
11251219 }
11261220
11271221 #[ test]
@@ -1132,4 +1226,11 @@ mod tests {
11321226 assert_eq ! ( patched. report_data_payload( ) , Some ( "payload" ) ) ;
11331227 assert_eq ! ( patched. into_inner( ) . report_data, [ 9u8 ; 64 ] ) ;
11341228 }
1229+
1230+ #[ test]
1231+ fn legacy_v0_upgrade_uses_dstack_variant ( ) {
1232+ let upgraded = dummy_tdx_attestation ( [ 3u8 ; 64 ] ) . into_v1 ( ) ;
1233+ assert_eq ! ( upgraded. platform. platform, NATIVE_PLATFORM_TDX ) ;
1234+ assert_eq ! ( upgraded. platform. variant, DSTACK_VARIANT_DSTACK ) ;
1235+ }
11351236}
0 commit comments