Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rs/ic_os/attestation/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ rust_library(
"@crate_index//:itertools",
"@crate_index//:pem",
"@crate_index//:prost",
"@crate_index//:rand",
"@crate_index//:ring",
"@crate_index//:serde",
"@crate_index//:sev",
Expand Down
1 change: 1 addition & 0 deletions rs/ic_os/attestation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ der = { workspace = true, features = ["alloc", "derive", "std"] }
itertools = { workspace = true }
pem = { workspace = true }
prost = { workspace = true }
rand = { workspace = true }
ring = { workspace = true }
serde = { workspace = true, features = ["derive"] }
sev = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion rs/ic_os/attestation/src/attestation_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub fn generate_attestation_package(
custom_data: &(impl EncodeSevCustomData + Debug),
) -> Result<SevAttestationPackage> {
let attestation_report = sev_firmware
.get_report(None, Some(custom_data.encode_for_sev()?), None)
.get_report(None, Some(custom_data.encode_for_sev()?.to_bytes()), None)
.context("Failed to get attestation report from SEV firmware")?;
let parsed_attestation_report = AttestationReport::from_bytes(&attestation_report);
if let Err(err) = parsed_attestation_report {
Expand Down
68 changes: 37 additions & 31 deletions rs/ic_os/attestation/src/custom_data.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
use der::Encode;
use ic_sev::guest::custom_data::SevCustomData;
use std::error::Error;
use std::fmt::{Debug, Formatter};
use std::fmt::Debug;
use thiserror::Error;

// Re-export SevCustomDataNamespace so DerEncodedCustomData implementors don't need to directly
// depend on the ic_sev crate.
pub use ic_sev::guest::custom_data::SevCustomDataNamespace;

#[derive(Debug, Error)]
#[error("EncodingError({0})")]
pub struct EncodingError(#[from] pub Box<dyn Error + Send + Sync>);
Expand All @@ -11,48 +16,49 @@ pub struct EncodingError(#[from] pub Box<dyn Error + Send + Sync>);
/// It's important that the encoding is deterministic and does not change between versions or
/// environments.
pub trait EncodeSevCustomData {
fn encode_for_sev(&self) -> Result<[u8; 64], EncodingError>;
/// Encodes the struct into a SevCustomData object for use as SEV custom data.
fn encode_for_sev(&self) -> Result<SevCustomData, EncodingError>;

/// Encodes the struct into a legacy 64-byte array for use as SEV custom data.
#[deprecated = "Should only be used for verifying potentially old clients"]
fn encode_for_sev_legacy(&self) -> Result<[u8; 64], EncodingError>;
}

/// A trait for types that can be encoded into SEV custom data using DER encoding.
pub trait DerEncodedCustomData: Encode {
fn namespace(&self) -> SevCustomDataNamespace;
}

/// Wrapper to implement `EncodeSevCustomData` for all types that implement `der::Encode`
///
/// DER is a well-defined, stable encoding format. We apply the also stable SHA-512 hash function to
/// the output of the DER encoding to produce a 64-byte array.
///
/// This makes it easy to make a type suitable for SEV custom data by annotating it with
/// `#[derive(der::Sequence)]`.
pub struct DerEncodedCustomData<T>(pub T);

impl<T: Encode> EncodeSevCustomData for DerEncodedCustomData<T> {
fn encode_for_sev(&self) -> Result<[u8; 64], EncodingError> {
impl<T: DerEncodedCustomData> EncodeSevCustomData for T {
fn encode_for_sev(&self) -> Result<SevCustomData, EncodingError> {
let mut encoded = vec![];
self.0
.encode(&mut encoded)
self.encode(&mut encoded)
.map_err(|err| EncodingError(Box::new(err)))?;

// Take first 60 bytes of SHA-512 hash
let hash = ring::digest::digest(&ring::digest::SHA512, &encoded);
Ok(hash.as_ref().try_into().unwrap())
Ok(SevCustomData::new(
self.namespace(),
hash.as_ref()[..60].try_into().unwrap(),
))
}
}

impl<T: Debug> Debug for DerEncodedCustomData<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
fn encode_for_sev_legacy(&self) -> Result<[u8; 64], EncodingError> {
let mut encoded = vec![];
self.encode(&mut encoded)
.map_err(|err| EncodingError(Box::new(err)))?;

let hash = ring::digest::digest(&ring::digest::SHA512, &encoded);
Ok(hash.as_ref().try_into().unwrap())
}
}

/// A simple implementation of `EncodeSevCustomData` that directly uses a raw 64-byte array.
#[derive(Clone, Copy)]
pub struct RawCustomData(pub [u8; 64]);

impl EncodeSevCustomData for RawCustomData {
fn encode_for_sev(&self) -> Result<[u8; 64], EncodingError> {
Ok(self.0)
impl EncodeSevCustomData for SevCustomData {
fn encode_for_sev(&self) -> Result<SevCustomData, EncodingError> {
Ok(*self)
}
}

impl Debug for RawCustomData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
fn encode_for_sev_legacy(&self) -> Result<[u8; 64], EncodingError> {
Ok(self.to_bytes())
}
}
78 changes: 64 additions & 14 deletions rs/ic_os/attestation/src/e2e_tests.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use crate::attestation_package::generate_attestation_package;
use crate::custom_data::{DerEncodedCustomData, EncodeSevCustomData};
use crate::verification::{SevRootCertificateVerification, verify_attestation_package};
use crate::{SevAttestationPackage, VerificationErrorDescription, VerificationErrorDetail};
use crate::{
SevAttestationPackage, SevCertificateChain, VerificationErrorDescription,
VerificationErrorDetail,
};
use config_types::TrustedExecutionEnvironmentConfig;
use ic_sev::guest::custom_data::SevCustomDataNamespace;
use ic_sev::guest::firmware::MockSevGuestFirmware;
use ic_sev::guest::testing::{FakeAttestationReportSigner, MockSevGuestFirmwareBuilder};
use ic_sev::guest::testing::{
AttestationReportBuilder, FakeAttestationReportSigner, MockSevGuestFirmwareBuilder,
};
use sev::firmware::guest::AttestationReport;
use sev::parser::ByteParser;

Expand All @@ -17,6 +23,12 @@ struct FooCustomData {
b: i64,
}

impl DerEncodedCustomData for FooCustomData {
fn namespace(&self) -> SevCustomDataNamespace {
SevCustomDataNamespace::Test
}
}

const CUSTOM_DATA: FooCustomData = FooCustomData {
a: 42,
b: 1234567890,
Expand All @@ -37,7 +49,7 @@ fn generate_valid_attestation_package() -> SevAttestationPackage {
&TrustedExecutionEnvironmentConfig {
sev_cert_chain_pem: FakeAttestationReportSigner::default().get_certificate_chain_pem(),
},
&DerEncodedCustomData(CUSTOM_DATA),
&CUSTOM_DATA,
)
.expect("Failed to generate attestation package")
}
Expand All @@ -54,16 +66,17 @@ fn test_valid_attestation_package() {
assert_eq!(attestation_report.measurement.as_slice(), MEASUREMENT);
assert_eq!(
attestation_report.report_data.as_slice(),
&DerEncodedCustomData(CUSTOM_DATA)
CUSTOM_DATA
.encode_for_sev()
.expect("Failed to encode custom data for SEV"),
.expect("Failed to encode custom data for SEV")
.to_bytes(),
);

verify_attestation_package(
&attestation_package,
SevRootCertificateVerification::TestOnlySkipVerification,
&[MEASUREMENT],
&DerEncodedCustomData(CUSTOM_DATA),
&CUSTOM_DATA,
Some(&CHIP_ID),
)
.expect("Failed to verify attestation package");
Expand All @@ -72,7 +85,7 @@ fn test_valid_attestation_package() {
&attestation_package,
SevRootCertificateVerification::TestOnlySkipVerification,
&[MEASUREMENT],
&DerEncodedCustomData(CUSTOM_DATA),
&CUSTOM_DATA,
None, // Skip chip ID check
)
.expect("Failed to verify attestation package");
Expand All @@ -93,7 +106,7 @@ fn test_invalid_attestation_report() {
&attestation_package,
SevRootCertificateVerification::TestOnlySkipVerification,
&[MEASUREMENT],
&DerEncodedCustomData(CUSTOM_DATA),
&CUSTOM_DATA,
Some(&CHIP_ID),
)
.expect_err("Verification should fail due to invalid attestation report")
Expand Down Expand Up @@ -124,7 +137,7 @@ fn test_invalid_signature() {
&attestation_package,
SevRootCertificateVerification::TestOnlySkipVerification,
&[MEASUREMENT],
&DerEncodedCustomData(CUSTOM_DATA),
&CUSTOM_DATA,
Some(&CHIP_ID),
)
.expect_err("Verification should fail due to invalid signature")
Expand All @@ -150,7 +163,7 @@ fn test_invalid_custom_data() {
&attestation_package,
SevRootCertificateVerification::TestOnlySkipVerification,
&[MEASUREMENT],
&DerEncodedCustomData(invalid_custom_data),
&invalid_custom_data,
Some(&CHIP_ID),
)
.expect_err("Verification should fail due to invalid custom data")
Expand All @@ -171,7 +184,7 @@ fn test_invalid_measurement() {
&attestation_package,
SevRootCertificateVerification::TestOnlySkipVerification,
&[[0; 48]], // Different from MEASUREMENT
&DerEncodedCustomData(CUSTOM_DATA),
&CUSTOM_DATA,
Some(&CHIP_ID),
)
.expect_err("Verification should fail due to invalid measurement")
Expand All @@ -192,7 +205,7 @@ fn test_invalid_chip_id() {
&attestation_package,
SevRootCertificateVerification::TestOnlySkipVerification,
&[MEASUREMENT],
&DerEncodedCustomData(CUSTOM_DATA),
&CUSTOM_DATA,
Some(&[0; 64]), // Different from CHIP_ID
)
.expect_err("Verification should fail due to invalid chip ID")
Expand Down Expand Up @@ -221,7 +234,7 @@ fn test_invalid_certificate_chain() {
&attestation_package,
SevRootCertificateVerification::TestOnlySkipVerification,
&[MEASUREMENT],
&DerEncodedCustomData(CUSTOM_DATA),
&CUSTOM_DATA,
Some(&CHIP_ID),
)
.expect_err("Verification should fail due to invalid certificate chain")
Expand All @@ -247,7 +260,7 @@ fn test_invalid_root_certificate() {
&attestation_package,
SevRootCertificateVerification::Verify,
&[MEASUREMENT],
&DerEncodedCustomData(CUSTOM_DATA),
&CUSTOM_DATA,
Some(&CHIP_ID),
)
.expect_err("Verification should fail due to invalid root certificate")
Expand All @@ -263,3 +276,40 @@ fn test_invalid_root_certificate() {
"Expected error about unexpected root certificate, got {error:?}",
);
}

#[test]
fn test_legacy_custom_data_accepted() {
let signer = FakeAttestationReportSigner::default();

#[allow(deprecated)]
let legacy_custom_data = CUSTOM_DATA
.encode_for_sev_legacy()
.expect("Failed to encode custom data in legacy format");

let attestation_report_bytes = AttestationReportBuilder::new()
.with_custom_data(legacy_custom_data)
.with_measurement(MEASUREMENT)
.with_chip_id(CHIP_ID)
.build_signed(&signer)
.to_bytes()
.unwrap();

let attestation_package = SevAttestationPackage {
attestation_report: Some(attestation_report_bytes.to_vec()),
certificate_chain: Some(SevCertificateChain {
vcek_pem: Some(signer.get_vcek_pem()),
ask_pem: Some(signer.get_ask_pem()),
ark_pem: Some(signer.get_ark_pem()),
}),
custom_data_debug_info: None,
};

verify_attestation_package(
&attestation_package,
SevRootCertificateVerification::TestOnlySkipVerification,
&[MEASUREMENT],
&CUSTOM_DATA,
Some(&CHIP_ID),
)
.expect("Failed to verify attestation package with legacy custom data format");
}
18 changes: 16 additions & 2 deletions rs/ic_os/attestation/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,24 @@ fn verify_custom_data(
let actual_report_data = attestation_report.report_data.as_slice();
let expected_report_data = expected_custom_data.encode_for_sev().map_err(|e| {
VerificationError::internal(format!("Could not encode expected custom data: {e}"))
})?;
if actual_report_data != expected_report_data {
});
// TODO: remove this once clients no longer send legacy custom data
#[allow(deprecated)]
let expected_report_data_legacy = expected_custom_data.encode_for_sev_legacy().map_err(|e| {
VerificationError::internal(format!(
"Could not encode expected custom data (legacy): {e}"
))
});
if !expected_report_data
.as_ref()
.is_ok_and(|expected| actual_report_data == expected.to_bytes())
&& !expected_report_data_legacy
.as_ref()
.is_ok_and(|expected| actual_report_data == expected)
{
return Err(VerificationError::invalid_custom_data(format!(
"Expected attestation report custom data: {expected_report_data:?}, \
legacy: {expected_report_data_legacy:?}, \
actual: {actual_report_data:?} \
Debug info: \
expected: {expected_custom_data:?} \
Expand Down
5 changes: 2 additions & 3 deletions rs/ic_os/guest_upgrade/client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::tls::SkipServerCertificateCheck;
use anyhow::{Context, Error, Result, anyhow, bail};
use attestation::attestation_package::generate_attestation_package;
use attestation::custom_data::DerEncodedCustomData;
use attestation::registry::get_blessed_guest_launch_measurements_from_registry;
use attestation::verification::{SevRootCertificateVerification, verify_attestation_package};
use config_types::GuestOSConfig;
Expand Down Expand Up @@ -135,12 +134,12 @@ impl DiskEncryptionKeyExchangeClientAgent {
my_public_key_der: &[u8],
server_public_key_der: &[u8],
) -> Result<()> {
let custom_data = DerEncodedCustomData(GetDiskEncryptionKeyTokenCustomData {
let custom_data = GetDiskEncryptionKeyTokenCustomData {
client_tls_public_key: OctetStringRef::new(my_public_key_der)
.expect("Could not encode public key"),
server_tls_public_key: OctetStringRef::new(server_public_key_der)
.expect("Could not encode server public key"),
});
};
let my_attestation_package = generate_attestation_package(
self.sev_firmware.as_mut(),
self.guestos_config
Expand Down
5 changes: 2 additions & 3 deletions rs/ic_os/guest_upgrade/server/src/service.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::SevFirmwareFactory;
use crate::server::ConnInfo;
use attestation::attestation_package::generate_attestation_package;
use attestation::custom_data::DerEncodedCustomData;
use attestation::verification::{SevRootCertificateVerification, verify_attestation_package};
use config_types::TrustedExecutionEnvironmentConfig;
use der::asn1::OctetStringRef;
Expand Down Expand Up @@ -111,12 +110,12 @@ impl DiskEncryptionKeyExchangeServiceImpl {

let client_public_key = Self::client_public_key_from_request(&request)?;

let custom_data = DerEncodedCustomData(GetDiskEncryptionKeyTokenCustomData {
let custom_data = GetDiskEncryptionKeyTokenCustomData {
client_tls_public_key: OctetStringRef::new(&client_public_key)
.expect("Could not encode client public key"),
server_tls_public_key: OctetStringRef::new(&self.my_public_key)
.expect("Could not encode server public key"),
});
};

let my_attestation_package = generate_attestation_package(
sev_firmware.as_mut(),
Expand Down
Loading
Loading