Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 15 additions & 1 deletion app/src/cose.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ namespace scitt::cose
static constexpr int64_t COSE_HEADER_PARAM_X5CHAIN = 33;
static constexpr int64_t COSE_HEADER_PARAM_CWT_CLAIMS = 15;

static constexpr const char* COSE_HEADER_PARAM_TSS = "msft-css-dev";
static constexpr const char* COSE_HEADER_PARAM_TSS = "attestedsvc";
static constexpr const char* COSE_HEADER_PARAM_TSS_SVC_ID = "svc_id";
static constexpr const char* COSE_HEADER_PARAM_TSS_ATTESTATION =
"attestation";
static constexpr const char* COSE_HEADER_PARAM_TSS_ATTESTATION_TYPE =
Expand Down Expand Up @@ -273,6 +274,7 @@ namespace scitt::cose
};

/**
"svc_id": tstr, ; service identifier
"attestation": bstr, ; raw hardware attestation report
"attestation_type": tstr, ; "SEV-SNP:ContainerPlat-AMD-UVM"
"cose_key": { ... }, ; canonical COSE_Key map (embedded directly)
Expand All @@ -282,6 +284,7 @@ namespace scitt::cose
*/
struct TSSMap
{
std::optional<std::string> svc_id;
std::optional<std::vector<uint8_t>> attestation;
std::optional<std::string> attestation_type;
std::optional<CoseKeyMap> cose_key;
Expand Down Expand Up @@ -482,6 +485,7 @@ namespace scitt::cose

enum
{
TSS_SVC_ID_INDEX,
TSS_ATTESTATION_INDEX,
TSS_ATTESTATION_TYPE_INDEX,
TSS_SNP_ENDORSEMENTS_INDEX,
Expand All @@ -492,6 +496,11 @@ namespace scitt::cose
};
QCBORItem tss_items[TSS_END_INDEX + 1];

tss_items[TSS_SVC_ID_INDEX].label.string =
UsefulBuf_FromSZ(COSE_HEADER_PARAM_TSS_SVC_ID);
tss_items[TSS_SVC_ID_INDEX].uLabelType = QCBOR_TYPE_TEXT_STRING;
tss_items[TSS_SVC_ID_INDEX].uDataType = QCBOR_TYPE_TEXT_STRING;

tss_items[TSS_ATTESTATION_INDEX].label.string =
UsefulBuf_FromSZ(COSE_HEADER_PARAM_TSS_ATTESTATION);
tss_items[TSS_ATTESTATION_INDEX].uLabelType = QCBOR_TYPE_TEXT_STRING;
Expand Down Expand Up @@ -701,6 +710,11 @@ namespace scitt::cose
tss_error));
}

if (tss_items[TSS_SVC_ID_INDEX].uDataType != QCBOR_TYPE_NONE)
{
parsed.tss_map.svc_id =
cbor::as_string(tss_items[TSS_SVC_ID_INDEX].val.string);
}
if (tss_items[TSS_ATTESTATION_INDEX].uDataType != QCBOR_TYPE_NONE)
{
parsed.tss_map.attestation =
Expand Down
6 changes: 5 additions & 1 deletion app/src/policy_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ namespace scitt
obj.set("cwt", std::move(cwt));

auto tss_map = ctx.new_obj();
if (phdr.tss_map.svc_id.has_value())
{
tss_map.set("svc_id", ctx.new_string(phdr.tss_map.svc_id.value()));
}
if (phdr.tss_map.attestation.has_value())
{
tss_map.set(
Expand Down Expand Up @@ -196,7 +200,7 @@ namespace scitt
{
tss_map.set_int64("ver", phdr.tss_map.ver.value());
}
obj.set("msft-css-dev", std::move(tss_map));
obj.set("attestedsvc", std::move(tss_map));
}

return obj;
Expand Down
51 changes: 51 additions & 0 deletions app/src/verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,57 @@ namespace scitt::verifier
std::tie(payload, details) =
process_signed_statement_with_didattestedsvc_issuer(
phdr, configuration, signed_statement);

if (!phdr.crit.has_value())
{
throw VerificationError(
"Attested Service signed statement must have a crit parameter");
}

auto& crit = phdr.crit.value();

if (crit.size() != 1)
{
throw VerificationError(
"Attested Service signed statement crit must contain a single "
"value");
}

auto& crit_val = crit[0];

if (!std::holds_alternative<std::string>(crit_val))
{
throw VerificationError(
"Attested Service signed statement crit value must be a "
"string");
}

if (std::get<std::string>(crit_val) != cose::COSE_HEADER_PARAM_TSS)
{
throw VerificationError(fmt::format(
"Attested Service signed statement crit value must be \"{}\"",
cose::COSE_HEADER_PARAM_TSS));
}

if (!phdr.tss_map.svc_id.has_value())
{
throw VerificationError(fmt::format(
"Attested service map {} must contain a service "
"identifier (svc_id)",
cose::COSE_HEADER_PARAM_TSS));
}

// Authenticate the did:attestedsvc issuer against the svc_id
// contained in the attested service map.
auto expected_did_prefix =
fmt::format("did:attestedsvc:{}:", phdr.tss_map.svc_id.value());
if (!phdr.cwt_claims.iss->starts_with(expected_did_prefix))
{
throw VerificationError(fmt::format(
"did:attestedsvc issuer does not match svc_id (expected prefix "
"{})",
expected_did_prefix));
}
}
else
{
Expand Down
40 changes: 20 additions & 20 deletions app/unit-tests/cose_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <ccf/crypto/openssl/openssl_wrappers.h>
#include <ccf/crypto/pem.h>
#include <ccf/crypto/rsa_key_pair.h>
#include <ccf/ds/hex.h>
#include <ccf/service/tables/cert_bundles.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
Expand All @@ -33,9 +34,10 @@ namespace

// add a test case to use payloads from test/payloads directory
// NOLINTBEGIN(bugprone-unchecked-optional-access)

TEST(CoseTest, DecodeTSSHeaders)
{
std::string filepath = "test_payloads/css-attested-cosesign1-20250617.cose";
std::string filepath = "test_payloads/css-attested-cosesign1-20250925.cose";
std::ifstream file(filepath, std::ios::binary);
ASSERT_TRUE(file.is_open());

Expand All @@ -56,16 +58,14 @@ namespace
{
throw std::runtime_error("Algorithm not found in protected header");
}
EXPECT_EQ(phdr.alg.value(), -35);
EXPECT_EQ(phdr.alg.value(), -7);

if (!phdr.cwt_claims.iss.has_value())
{
throw std::runtime_error("Issuer not found in protected header");
}
EXPECT_EQ(
phdr.cwt_claims.iss.value(),
"did:attestedsvc:msft-css-dev::3d7961c9-84b2-44d2-a9e0-33c040d168b3:test-"
"account1:profile1");
phdr.cwt_claims.iss.value(), "did:attestedsvc:msft-css-dev::foo:bar:baz");
EXPECT_TRUE(phdr.tss_map.attestation.has_value());
EXPECT_TRUE(phdr.tss_map.attestation_type.has_value());
EXPECT_EQ(
Expand All @@ -81,41 +81,41 @@ namespace
1: 2,
-1: 2,
-2:
h'6D2ECFA295A4FEAB4DF1715E9978B13A335AA3468013A6B1933A20205FB0943C3115EDBA2DADBC6EAC64403904347B23',
h'ED2669A17FD85DC926AD405507877F882F005194EE236A7B6443FD8A1735EC78',
-3:
h'2D0FFD0127F1C015E1F5D2BA86DE32ECC872EED7F84F9CD96145275632297903CD246D87F29912D0CE19F81C7F6CAB3A'
h'44D1BD3B99BB2A185A2FA1060FFFAB37CD25FBEF8E812D89A6BBE36E91F365D9'
*/
EXPECT_TRUE(std::holds_alternative<int64_t>(
phdr.tss_map.cose_key->crv_n_k_pub().value()));
auto crv_n_k_pub =
std::get<int64_t>(phdr.tss_map.cose_key->crv_n_k_pub().value());
EXPECT_EQ(crv_n_k_pub, 2);
EXPECT_EQ(crv_n_k_pub, 1);

EXPECT_EQ(phdr.tss_map.cose_key->x_e().has_value(), true);
EXPECT_EQ(
phdr.tss_map.cose_key->x_e().value(),
from_hex_string("6D2ECFA295A4FEAB4DF1715E9978B13A335AA3468013A6B1933A2020"
"5FB0943C3115EDBA2DADBC6EAC64403904347B23"));
from_hex_string(
"ED2669A17FD85DC926AD405507877F882F005194EE236A7B6443FD8A1735EC78"));

EXPECT_EQ(phdr.tss_map.cose_key->y().has_value(), true);
EXPECT_EQ(
phdr.tss_map.cose_key->y().value(),
from_hex_string("2D0FFD0127F1C015E1F5D2BA86DE32ECC872EED7F84F9CD961452756"
"32297903CD246D87F29912D0CE19F81C7F6CAB3A"));
from_hex_string(
"44D1BD3B99BB2A185A2FA1060FFFAB37CD25FBEF8E812D89A6BBE36E91F365D9"));
}
// NOLINTEND(bugprone-unchecked-optional-access)

TEST(CoseTest, DecodeTSSHeadersFailsDueToInvalidMap)
{
const std::vector<uint8_t>& signed_statement = from_hex_string(
"D284590103A801382202816C6D7366742D6373732D646576045820A3FC5DF291C866D1AE"
"7FE90519384EEE2B84D412ED4ABE22C71395B6FDE3057D0FA40178596469643A61747465"
"737465647376633A6D7366742D6373732D6465763A3A33643739363163392D383462322D"
"343464322D613965302D3333633034306431363862333A746573742D6163636F756E7431"
"3A70726F66696C653102716578706572696D656E74616C2F7465737406C11A6852FBB263"
"73766E001901022F190103706170706C69636174696F6E2F6A736F6E1901047768747470"
"3A2F2F706174682D746F2D636F6E74656E742F6C6D7366742D6373732D6465766F73686F"
"756C642062652061206D6170A0A0A0");
"D284590111A801382202816B6174746573746564737663045820A3FC5DF291C866D1AE7F"
"E90519384EEE2B84D412ED4ABE22C71395B6FDE3057D0FA40178596469643A6174746573"
"7465647376633A6D7366742D6373732D6465763A3A33643739363163392D383462322D34"
"3464322D613965302D3333633034306431363862333A746573742D6163636F756E74313A"
"70726F66696C653102716578706572696D656E74616C2F7465737406C074323032352D30"
"362D31385431373A34373A33305A6373766E001901022F190103706170706C6963617469"
"6F6E2F6A736F6E19010477687474703A2F2F706174682D746F2D636F6E74656E742F6B61"
"747465737465647376636F73686F756C642062652061206D6170A0A0A0");

cose::ProtectedHeader phdr;
cose::UnprotectedHeader uhdr;
Expand Down
7 changes: 5 additions & 2 deletions app/unit-tests/testutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ namespace testutils
cbor::from_string("application/attestedsvc+json"));
// crit
QCBOREncode_OpenArrayInMapN(&ectx, cose::COSE_HEADER_PARAM_CRIT);
QCBOREncode_AddInt64(&ectx, cose::COSE_HEADER_PARAM_ALG);
QCBOREncode_AddInt64(&ectx, cose::COSE_HEADER_PARAM_KID);
QCBOREncode_AddText(&ectx, cbor::from_string(cose::COSE_HEADER_PARAM_TSS));
QCBOREncode_CloseArray(&ectx);

// X5Chain
Expand Down Expand Up @@ -133,6 +132,10 @@ namespace testutils
// TSS map
// ----------------------
QCBOREncode_OpenMapInMap(&ectx, cose::COSE_HEADER_PARAM_TSS);
QCBOREncode_AddTextToMap(
&ectx,
cose::COSE_HEADER_PARAM_TSS_SVC_ID,
cbor::from_string("msft-css-dev"));
QCBOREncode_AddBytesToMap(
&ectx,
cose::COSE_HEADER_PARAM_TSS_ATTESTATION,
Expand Down
6 changes: 3 additions & 3 deletions app/unit-tests/verifier_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace
// NOLINTBEGIN(bugprone-unchecked-optional-access)
TEST(VerifierTest, VerifyTSSStatement)
{
std::string filepath = "test_payloads/css-attested-cosesign1-20250617.cose";
std::string filepath = "test_payloads/css-attested-cosesign1-20250925.cose";
std::ifstream file(filepath, std::ios::binary);
ASSERT_TRUE(file.is_open());

Expand Down Expand Up @@ -66,7 +66,7 @@ namespace
"920ab2fa0096903a0c23fca1");
EXPECT_EQ(
details->get_report_data().hex_str(),
"a3fc5df291c866d1ae7fe90519384eee2b84d412ed4abe22c71395b6fde3057d00000000"
"460dce07b03a0a4f3a42cf93f2010595e7a8da0677b3a01a1bf08821a48bdfaf00000000"
"00000000000000000000000000000000000000000000000000000000");
EXPECT_TRUE(details->get_uvm_endorsements().has_value());
EXPECT_EQ(
Expand All @@ -80,7 +80,7 @@ namespace
auto host_data_str = ccf::ds::to_hex(host_data);
EXPECT_EQ(
host_data_str,
"953e208258fc57d814c44a0b083dbe4e8f2e734a2fde32f1049a78890d98b730");
"73973b78d70cc68353426de188db5dfc57e5b766e399935fb73a61127ea26d20");
EXPECT_EQ(details->get_product_name(), ccf::pal::snp::ProductName::Milan);
EXPECT_EQ(details->get_tcb_version_policy().microcode, 219);
EXPECT_EQ(details->get_tcb_version_policy().snp, 24);
Expand Down
5 changes: 3 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ Function arguments:
svn?: number // Software version number
},

// Microsoft CSS Dev TSS Map
"msft-css-dev": {
// Microsoft Attested Service Map
"attestedsvc": {
svc_id?: string,
attestation?: ArrayBuffer,
attestation_type?: string,

Expand Down
Binary file not shown.
3 changes: 1 addition & 2 deletions test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,7 @@ def test_extract_payload_from_cose(run, tmp_path: Path):
[
"test/payloads/cts-hashv-cwtclaims-b64url.cose",
"test/payloads/manifest.spdx.json.sha384.digest.cose",
"test/payloads/css-attested-cosesign1-20250617.cose",
"test/payloads/css-attested-cosesign1-20250812.cose",
"test/payloads/css-attested-cosesign1-20250925.cose",
],
)
def test_submit_and_validate(
Expand Down
32 changes: 19 additions & 13 deletions test/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,12 @@ def didx509_signed_statement_with_attestation(
{"foo": "bar"},
cwt=True,
additional_phdr={
"msft-css-dev": {
"attestedsvc": {
"svc_id": "msft-css-dev",
"attestation": b"testAttestation",
"snp_endorsements": b"testSnpEndorsements",
"uvm_endorsements": b"testUvmEndorsements",
}
},
},
)

Expand All @@ -422,14 +423,17 @@ def test_tss_map(
):
policy_script = """
export function apply(phdr, uhdr, payload) {
if (ccf.bufToStr(phdr["msft-css-dev"].attestation) !== "testAttestation") {
return `Invalid msft-css-dev.attestation`;
if (phdr["attestedsvc"].svc_id !== "msft-css-dev") {
return `Invalid attestedsvc.svc_id`;
}
if (ccf.bufToStr(phdr["attestedsvc"].attestation) !== "testAttestation") {
return `Invalid attestedsvc.attestation`;
}
if (ccf.bufToStr(phdr["msft-css-dev"].snp_endorsements) !== "testSnpEndorsements") {
return `Invalid msft-css-dev.snp_endorsements`;
if (ccf.bufToStr(phdr["attestedsvc"].snp_endorsements) !== "testSnpEndorsements") {
return `Invalid attestedsvc.snp_endorsements`;
}
if (ccf.bufToStr(phdr["msft-css-dev"].uvm_endorsements) !== "testUvmEndorsements") {
return `Invalid msft-css-dev.uvm_endorsements`;
if (ccf.bufToStr(phdr["attestedsvc"].uvm_endorsements) !== "testUvmEndorsements") {
return `Invalid attestedsvc.uvm_endorsements`;
}
return true;
}
Expand Down Expand Up @@ -509,14 +513,16 @@ def signed_statement_with_attestation(self):
{"foo": "bar"},
cwt=True,
additional_phdr={
"msft-css-dev": {
2: ["attestedsvc"],
"attestedsvc": {
"svc_id": "msft-css-dev",
"attestation": snp_r,
"attestation_type": "SEV-SNP:ContainerPlat-AMD-UVM",
"snp_endorsements": snp_e,
"uvm_endorsements": uvm_e,
"cose_key": cbor2.loads(cose_key_cbor),
"ver": 0,
}
},
},
)

Expand All @@ -526,9 +532,9 @@ def test_valid_attestedsvc_signature_and_also_verify_attestation_in_policy(
policy_script = """
export function apply(phdr, uhdr, payload) {
var claims = snp_attestation.verifySnpAttestation(
phdr["msft-css-dev"].attestation,
phdr["msft-css-dev"].snp_endorsements,
phdr["msft-css-dev"].uvm_endorsements
phdr["attestedsvc"].attestation,
phdr["attestedsvc"].snp_endorsements,
phdr["attestedsvc"].uvm_endorsements
);

function toHexStr(arrayBuffer) {
Expand Down
4 changes: 2 additions & 2 deletions test/test_perf.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
if (details.uvm_endorsements.svn < "101") {{ return "Invalid uvm_endorsements svn"; }}

// Check host_data is the expected digest of the CCE policy for the issuing service
if (details.host_data !== "c71e9f286127f4327a7ddcfb4c6f6f1274a2858172549e10b9e506a19206fd57") {{ return "Invalid host data"; }}
if (details.host_data !== "73973b78d70cc68353426de188db5dfc57e5b766e399935fb73a61127ea26d20") {{ return "Invalid host data"; }}

// Check issuer is valid
if (!phdr.cwt.iss.startsWith("did:attestedsvc:msft-css-dev:")) {{ return "Invalid issuer"; }}
Expand All @@ -53,7 +53,7 @@

TEST_VECTORS = [
("test/payloads/cts-hashv-cwtclaims-b64url.cose", "x509_hashv"),
("test/payloads/css-attested-cosesign1-20250812.cose", "attested_svc"),
("test/payloads/css-attested-cosesign1-20250925.cose", "attested_svc"),
]


Expand Down
Loading