Skip to content

Commit 6710bb8

Browse files
committed
attestation: use direct CBOR V1 format
1 parent c14f2fe commit 6710bb8

6 files changed

Lines changed: 120 additions & 200 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ members = [
4141
"guest-api",
4242
"load_config",
4343
"key-provider-client",
44-
"dstack-attestation-types",
4544
"dstack-types",
4645
"cert-client",
4746
"lspci",
@@ -73,7 +72,6 @@ supervisor = { path = "supervisor" }
7372
supervisor-client = { path = "supervisor/client" }
7473
tdx-attest = { path = "tdx-attest" }
7574
dstack-attest = { path = "dstack-attest" }
76-
dstack-attestation-types = { path = "dstack-attestation-types" }
7775
certbot = { path = "certbot" }
7876
rocket-vsock-listener = { path = "rocket-vsock-listener" }
7977
host-api = { path = "host-api", default-features = false }

dstack-attest/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ anyhow.workspace = true
1414
cc-eventlog.workspace = true
1515
ciborium.workspace = true
1616
dcap-qvl.workspace = true
17-
dstack-attestation-types.workspace = true
17+
dstack-attestation-types = { path = "../../../dstack-attestation-types" }
1818
dstack-types.workspace = true
1919
ez-hash.workspace = true
2020
fs-err.workspace = true

dstack-attest/src/attestation.rs

Lines changed: 119 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,17 @@ use dstack_types::SysConfig;
2727
use dstack_types::{Platform, VmConfig};
2828
use ez_hash::{sha256, Hasher, Sha384};
2929
use or_panic::ResultOrPanic;
30-
use scale::{Decode, Encode};
30+
use scale::{Decode, Encode, Error as ScaleError, Input, Output};
3131
use serde::de::DeserializeOwned;
3232
use serde::{Deserialize, Serialize};
3333
use serde_human_bytes as hex_bytes;
3434
use 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";
3641
const DSTACK_TDX: &str = "dstack-tdx";
3742
const DSTACK_GCP_TDX: &str = "dstack-gcp-tdx";
3843
const 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)]
431454
pub 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+
444495
impl 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

885962
impl 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
}

dstack-attestation-types/Cargo.toml

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)