Expose SNP Attestation validation in TS (#5653)

This commit is contained in:
Dominic Ayre 2023-09-26 08:39:56 +01:00 коммит произвёл GitHub
Родитель fdd38c4cc7
Коммит 6e7caf0098
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 965 добавлений и 8 удалений

Просмотреть файл

@ -1,4 +1,4 @@
___ ___ ___
(. =) Y (0 0) (* *) Y
O \ . | /
O \ . \- | -/ /
/-xXx--//-----x=x--/-xXx--/---x---->

Просмотреть файл

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Converted SNP attestation UVM endorsements from integer to arbitrary string.
- Updated Intel SGX PSW from 2.17 to 2.20 (#5616)
- Path to the enclave file should now be passed as `--enclave-file` CLI argument to `cchost`, rather than `enclave.file` entry within configuration file. This is to ensure the path to the application file is attested on Confidential Containers/SNP, even if the configuration itself is provided from un-attested storage. The configuration entry is deprecated, and will be removed in a future release.
- Added `ccf.SnpAttestation.verifySnpAttestation()` endpoint for TypeScript apps. (#5653)
- Secret sharing used for ledger recovery now relies on a much simpler implementation that requires no external dependencies. Note that while the code still accepts shares generated by the old code for now, it only generates shares with the new implementation. As a result, a DR attempt that would downgrade the code to a version that pre-dates this change, after having previously picked it up, would not succeed if a reshare had already taken place (#5655).
## [5.0.0-dev1]

Просмотреть файл

@ -456,6 +456,17 @@ if(COMPILE_TARGET STREQUAL "sgx")
EXPORT ccf
DESTINATION lib
)
add_enclave_library(
js_snp_attestation.enclave ${CCF_DIR}/src/js/snp_attestation.cpp
)
target_link_libraries(js_snp_attestation.enclave PUBLIC ccf.enclave)
add_lvi_mitigations(js_snp_attestation.enclave)
install(
TARGETS js_snp_attestation.enclave
EXPORT ccf
DESTINATION lib
)
elseif(COMPILE_TARGET STREQUAL "snp" AND REQUIRE_OPENENCLAVE)
add_library(js_openenclave.snp STATIC ${CCF_DIR}/src/js/openenclave.cpp)
add_san(js_openenclave.snp)
@ -493,6 +504,49 @@ elseif(COMPILE_TARGET STREQUAL "virtual" AND REQUIRE_OPENENCLAVE)
set(JS_OPENENCLAVE_VIRTUAL js_openenclave.virtual)
endif()
if(COMPILE_TARGET STREQUAL "snp")
add_library(
js_snp_attestation.snp STATIC ${CCF_DIR}/src/js/snp_attestation.cpp
)
add_san(js_snp_attestation.snp)
target_link_libraries(js_snp_attestation.snp PUBLIC ccf.snp)
target_compile_options(js_snp_attestation.snp PRIVATE ${COMPILE_LIBCXX})
target_compile_definitions(
js_snp_attestation.snp PUBLIC INSIDE_ENCLAVE VIRTUAL_ENCLAVE
_LIBCPP_HAS_THREAD_API_PTHREAD PLATFORM_SNP
)
set_property(
TARGET js_snp_attestation.snp PROPERTY POSITION_INDEPENDENT_CODE ON
)
install(
TARGETS js_snp_attestation.snp
EXPORT ccf
DESTINATION lib
)
set(JS_SNP_ATTESTATION_SNP js_snp_attestation.snp)
elseif(COMPILE_TARGET STREQUAL "virtual")
add_library(
js_snp_attestation.virtual STATIC ${CCF_DIR}/src/js/snp_attestation.cpp
)
add_san(js_snp_attestation.virtual)
target_link_libraries(js_snp_attestation.virtual PUBLIC ccf.virtual)
target_compile_options(js_snp_attestation.virtual PRIVATE ${COMPILE_LIBCXX})
target_compile_definitions(
js_snp_attestation.virtual
PUBLIC INSIDE_ENCLAVE VIRTUAL_ENCLAVE _LIBCPP_HAS_THREAD_API_PTHREAD
PLATFORM_VIRTUAL
)
set_property(
TARGET js_snp_attestation.virtual PROPERTY POSITION_INDEPENDENT_CODE ON
)
install(
TARGETS js_snp_attestation.virtual
EXPORT ccf
DESTINATION lib
)
set(JS_SNP_ATTESTATION_VIRTUAL js_snp_attestation.virtual)
endif()
if(COMPILE_TARGET STREQUAL "sgx")
add_enclave_library(
js_generic_base.enclave ${CCF_DIR}/src/apps/js_generic/js_generic_base.cpp
@ -539,6 +593,11 @@ elseif(COMPILE_TARGET STREQUAL "virtual")
PLATFORM_VIRTUAL
)
endif()
target_compile_definitions(
js_snp_attestation.virtual
PUBLIC INSIDE_ENCLAVE VIRTUAL_ENCLAVE _LIBCPP_HAS_THREAD_API_PTHREAD
PLATFORM_VIRTUAL
)
set_property(
TARGET js_generic_base.virtual PROPERTY POSITION_INDEPENDENT_CODE ON
)
@ -553,8 +612,11 @@ add_ccf_app(
js_generic
SRCS ${CCF_DIR}/src/apps/js_generic/js_generic.cpp
LINK_LIBS_ENCLAVE js_generic_base.enclave js_openenclave.enclave
js_snp_attestation.enclave
LINK_LIBS_VIRTUAL js_generic_base.virtual ${JS_OPENENCLAVE_VIRTUAL}
LINK_LIBS_SNP js_generic_base.snp ${JS_OPENENCLAVE_SNP} INSTALL_LIBS ON
${JS_SNP_ATTESTATION_VIRTUAL}
LINK_LIBS_SNP js_generic_base.snp ${JS_OPENENCLAVE_SNP}
${JS_SNP_ATTESTATION_SNP} INSTALL_LIBS ON
)
sign_app_library(
js_generic.enclave ${CCF_DIR}/src/apps/js_generic/oe_sign.conf

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "ccf/js_plugin.h"
namespace ccf::js
{
extern FFIPlugin snp_attestation_plugin;
}

Просмотреть файл

@ -62,6 +62,11 @@ namespace ccf::pal
data(report_data.report_data.begin(), report_data.report_data.end())
{}
std::string hex_str() const
{
return ds::to_hex(data);
}
crypto::Sha256Hash to_sha256_hash() const
{
std::span<const uint8_t, crypto::Sha256Hash::SIZE> s(

Просмотреть файл

@ -739,3 +739,77 @@ export interface OpenEnclave {
endorsements?: ArrayBuffer,
): EvidenceClaims;
}
export interface TcbVersion {
boot_loader: number;
tee: number;
snp: number;
microcode: number;
}
export interface SnpAttestationResult {
attestation: {
version: number;
guest_svn: number;
policy: {
abi_minor: number;
abi_major: number;
smt: number;
migrate_ma: number;
debug: number;
single_socket: number;
};
family_id: ArrayBuffer;
image_id: ArrayBuffer;
vmpl: number;
signature_algo: number;
platform_version: TcbVersion;
platform_info: {
smt_en: number;
tsme_en: number;
};
flags: {
author_key_en: number;
mask_chip_key: number;
signing_key: number;
};
report_data: ArrayBuffer;
measurement: ArrayBuffer;
host_data: ArrayBuffer;
id_key_digest: ArrayBuffer;
author_key_digest: ArrayBuffer;
report_id: ArrayBuffer;
report_id_ma: ArrayBuffer;
reported_tcb: TcbVersion;
chip_id: ArrayBuffer;
committed_tcb: TcbVersion;
current_minor: number;
current_build: number;
current_major: number;
committed_build: number;
committed_minor: number;
committed_major: number;
launch_tcb: TcbVersion;
signature: {
r: ArrayBuffer;
s: ArrayBuffer;
};
};
uvm_endorsements?: {
did: string;
feed: string;
svn: string;
};
}
export const snp_attestation: SnpAttestation = (<any>globalThis)
.snp_attestation;
export interface SnpAttestation {
verifySnpAttestation(
evidence: ArrayBuffer,
endorsements: ArrayBuffer,
uvm_endorsements?: ArrayBuffer,
endorsed_tcb?: string,
): SnpAttestationResult;
}

Просмотреть файл

@ -31,6 +31,8 @@ import {
DigestAlgorithm,
EvidenceClaims,
OpenEnclave,
SnpAttestation,
SnpAttestationResult,
SigningAlgorithm,
JsonWebKeyECPublic,
JsonWebKeyECPrivate,
@ -566,6 +568,19 @@ class OpenEnclavePolyfill implements OpenEnclave {
(<any>globalThis).openenclave = new OpenEnclavePolyfill();
class SnpAttestationPolyfill implements SnpAttestation {
verifySnpAttestation(
evidence: ArrayBuffer,
endorsements: ArrayBuffer,
uvm_endorsements?: ArrayBuffer,
endorsed_tcb?: string,
): SnpAttestationResult {
throw new Error("Method not implemented.");
}
}
(<any>globalThis).snp_attestation = new SnpAttestationPolyfill();
function nodeBufToArrBuf(buf: Buffer): ArrayBuffer {
// Note: buf.buffer is not safe, see docs.
const arrBuf = new ArrayBuffer(buf.byteLength);

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
/**
* The `snp_attestation` module provides SNP Attestation Validation.
*
* @module
*/
import { snp_attestation } from "./global";
/**
* @inheritDoc global!SnpAttestation.verifySnpAttestation
*/
export const verifySnpAttestation = snp_attestation.verifySnpAttestation;

Просмотреть файл

@ -9,7 +9,8 @@
"src/historical.ts",
"src/kv.ts",
"src/polyfill.ts",
"src/openenclave.ts"
"src/openenclave.ts",
"src/snp_attestation.ts"
],
"out": "html",
"theme": "default",

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the Apache 2.0 License.
#include "ccf/app_interface.h"
#include "ccf/js_openenclave_plugin.h"
#include "ccf/js_snp_attestation_plugin.h"
#include "js_generic_base.h"
namespace ccfapp
@ -15,9 +16,9 @@ namespace ccfapp
std::vector<ccf::js::FFIPlugin> get_js_plugins()
{
#ifdef SGX_ATTESTATION_VERIFICATION
return {ccf::js::openenclave_plugin};
return {ccf::js::openenclave_plugin, ccf::js::snp_attestation_plugin};
#else
return {};
return {ccf::js::snp_attestation_plugin};
#endif
}

319
src/js/snp_attestation.cpp Normal file
Просмотреть файл

@ -0,0 +1,319 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#include "ccf/js_plugin.h"
#include "ccf/js_snp_attestation_plugin.h"
#include "ccf/pal/attestation.h"
#include "ccf/version.h"
#include "js/wrap.h"
#include "node/uvm_endorsements.h"
#include <algorithm>
#include <quickjs/quickjs.h>
#include <regex>
#include <vector>
namespace ccf::js
{
#pragma clang diagnostic push
static JSValue make_js_tcb_version(JSContext* ctx, pal::snp::TcbVersion tcb)
{
auto js_tcb = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx, js_tcb, "boot_loader", JS_NewUint32(ctx, tcb.boot_loader));
JS_SetPropertyStr(ctx, js_tcb, "tee", JS_NewUint32(ctx, tcb.tee));
JS_SetPropertyStr(ctx, js_tcb, "snp", JS_NewUint32(ctx, tcb.snp));
JS_SetPropertyStr(
ctx, js_tcb, "microcode", JS_NewUint32(ctx, tcb.microcode));
return js_tcb;
}
static JSValue JS_NewArrayBuffer2(
JSContext* ctx, std::span<const uint8_t> data)
{
return JS_NewArrayBufferCopy(ctx, data.data(), data.size());
}
static JSValue js_verify_snp_attestation(
JSContext* ctx, JSValueConst, int argc, JSValueConst* argv)
{
if (argc < 2 && argc > 4)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected between 2 and 4", argc);
js::Context& jsctx = *(js::Context*)JS_GetContextOpaque(ctx);
size_t evidence_size;
uint8_t* evidence = JS_GetArrayBuffer(ctx, &evidence_size, argv[0]);
if (!evidence)
{
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
size_t endorsements_size;
uint8_t* endorsements = JS_GetArrayBuffer(ctx, &endorsements_size, argv[1]);
if (!endorsements)
{
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
std::optional<std::vector<uint8_t>> uvm_endorsements;
if (!JS_IsUndefined(argv[2]))
{
size_t uvm_endorsements_size;
uint8_t* uvm_endorsements_array =
JS_GetArrayBuffer(ctx, &uvm_endorsements_size, argv[2]);
uvm_endorsements = std::vector<uint8_t>(
uvm_endorsements_array, uvm_endorsements_array + uvm_endorsements_size);
}
std::optional<std::string> endorsed_tcb;
if (!JS_IsUndefined(argv[3]))
{
endorsed_tcb = jsctx.to_str(argv[3]);
}
QuoteInfo quote_info = {};
quote_info.format = QuoteFormat::amd_sev_snp_v1;
quote_info.quote = std::vector<uint8_t>(evidence, evidence + evidence_size);
quote_info.endorsements =
std::vector<uint8_t>(endorsements, endorsements + endorsements_size);
if (endorsed_tcb.has_value())
{
quote_info.endorsed_tcb = endorsed_tcb.value();
}
pal::PlatformAttestationMeasurement measurement = {};
pal::PlatformAttestationReportData report_data = {};
std::optional<UVMEndorsements> parsed_uvm_endorsements;
try
{
pal::verify_snp_attestation_report(quote_info, measurement, report_data);
if (uvm_endorsements.has_value())
{
parsed_uvm_endorsements =
verify_uvm_endorsements(uvm_endorsements.value(), measurement);
}
}
catch (const std::exception& e)
{
auto e_ = JS_ThrowRangeError(ctx, "%s", e.what());
js::js_dump_error(ctx);
return e_;
}
auto attestation =
*reinterpret_cast<const pal::snp::Attestation*>(quote_info.quote.data());
auto r = JS_NewObject(ctx);
auto a = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx, a, "version", JS_NewUint32(ctx, attestation.version));
JS_SetPropertyStr(
ctx, a, "guest_svn", JS_NewUint32(ctx, attestation.guest_svn));
auto policy = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx,
policy,
"abi_minor",
JS_NewUint32(ctx, attestation.policy.abi_minor));
JS_SetPropertyStr(
ctx,
policy,
"abi_major",
JS_NewUint32(ctx, attestation.policy.abi_major));
JS_SetPropertyStr(
ctx, policy, "smt", JS_NewUint32(ctx, attestation.policy.smt));
JS_SetPropertyStr(
ctx,
policy,
"migrate_ma",
JS_NewUint32(ctx, attestation.policy.migrate_ma));
JS_SetPropertyStr(
ctx, policy, "debug", JS_NewUint32(ctx, attestation.policy.debug));
JS_SetPropertyStr(
ctx,
policy,
"single_socket",
JS_NewUint32(ctx, attestation.policy.single_socket));
JS_SetProperty(ctx, a, JS_NewAtom(ctx, "policy"), policy);
JS_SetPropertyStr(
ctx, a, "family_id", JS_NewArrayBuffer2(ctx, attestation.family_id));
JS_SetPropertyStr(
ctx, a, "image_id", JS_NewArrayBuffer2(ctx, attestation.image_id));
JS_SetPropertyStr(ctx, a, "vmpl", JS_NewUint32(ctx, attestation.vmpl));
JS_SetPropertyStr(
ctx,
a,
"signature_algo",
JS_NewUint32(ctx, static_cast<uint32_t>(attestation.signature_algo)));
JS_SetProperty(
ctx,
a,
JS_NewAtom(ctx, "platform_version"),
make_js_tcb_version(ctx, attestation.platform_version));
auto platform_info = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx,
platform_info,
"smt_en",
JS_NewUint32(ctx, attestation.platform_info.smt_en));
JS_SetPropertyStr(
ctx,
platform_info,
"tsme_en",
JS_NewUint32(ctx, attestation.platform_info.tsme_en));
JS_SetProperty(ctx, a, JS_NewAtom(ctx, "platform_info"), platform_info);
auto flags = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx,
flags,
"author_key_en",
JS_NewUint32(ctx, attestation.flags.author_key_en));
JS_SetPropertyStr(
ctx,
flags,
"mask_chip_key",
JS_NewUint32(ctx, attestation.flags.mask_chip_key));
JS_SetPropertyStr(
ctx,
flags,
"signing_key",
JS_NewUint32(ctx, attestation.flags.signing_key));
JS_SetProperty(ctx, a, JS_NewAtom(ctx, "flags"), flags);
JS_SetPropertyStr(
ctx, a, "report_data", JS_NewArrayBuffer2(ctx, attestation.report_data));
JS_SetPropertyStr(
ctx, a, "measurement", JS_NewArrayBuffer2(ctx, attestation.measurement));
JS_SetPropertyStr(
ctx, a, "host_data", JS_NewArrayBuffer2(ctx, attestation.host_data));
JS_SetPropertyStr(
ctx,
a,
"id_key_digest",
JS_NewArrayBuffer2(ctx, attestation.id_key_digest));
JS_SetPropertyStr(
ctx,
a,
"author_key_digest",
JS_NewArrayBuffer2(ctx, attestation.author_key_digest));
JS_SetPropertyStr(
ctx, a, "report_id", JS_NewArrayBuffer2(ctx, attestation.report_id));
JS_SetPropertyStr(
ctx,
a,
"report_id_ma",
JS_NewArrayBuffer2(ctx, attestation.report_id_ma));
JS_SetProperty(
ctx,
a,
JS_NewAtom(ctx, "reported_tcb"),
make_js_tcb_version(ctx, attestation.reported_tcb));
JS_SetPropertyStr(
ctx, a, "chip_id", JS_NewArrayBuffer2(ctx, attestation.chip_id));
JS_SetProperty(
ctx,
a,
JS_NewAtom(ctx, "committed_tcb"),
make_js_tcb_version(ctx, attestation.committed_tcb));
JS_SetPropertyStr(
ctx, a, "current_minor", JS_NewUint32(ctx, attestation.current_minor));
JS_SetPropertyStr(
ctx, a, "current_build", JS_NewUint32(ctx, attestation.current_build));
JS_SetPropertyStr(
ctx, a, "current_major", JS_NewUint32(ctx, attestation.current_major));
JS_SetPropertyStr(
ctx,
a,
"committed_build",
JS_NewUint32(ctx, attestation.committed_build));
JS_SetPropertyStr(
ctx,
a,
"committed_minor",
JS_NewUint32(ctx, attestation.committed_minor));
JS_SetPropertyStr(
ctx,
a,
"committed_major",
JS_NewUint32(ctx, attestation.committed_major));
JS_SetProperty(
ctx,
a,
JS_NewAtom(ctx, "launch_tcb"),
make_js_tcb_version(ctx, attestation.launch_tcb));
auto signature = JS_NewObject(ctx);
JS_SetProperty(
ctx,
signature,
JS_NewAtom(ctx, "r"),
JS_NewArrayBuffer2(ctx, attestation.signature.r));
JS_SetProperty(
ctx,
signature,
JS_NewAtom(ctx, "s"),
JS_NewArrayBuffer2(ctx, attestation.signature.s));
JS_SetProperty(ctx, a, JS_NewAtom(ctx, "signature"), signature);
JS_SetProperty(ctx, r, JS_NewAtom(ctx, "attestation"), a);
if (parsed_uvm_endorsements.has_value())
{
auto u = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx,
u,
"did",
JS_NewString(ctx, parsed_uvm_endorsements.value().did.c_str()));
JS_SetPropertyStr(
ctx,
u,
"feed",
JS_NewString(ctx, parsed_uvm_endorsements.value().feed.c_str()));
JS_SetPropertyStr(
ctx,
u,
"svn",
JS_NewString(ctx, parsed_uvm_endorsements.value().svn.c_str()));
JS_SetProperty(ctx, r, JS_NewAtom(ctx, "uvm_endorsements"), u);
}
return r;
}
#pragma clang diagnostic pop
static JSValue create_snp_attestation_obj(JSContext* ctx)
{
auto snp_attestation = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx,
snp_attestation,
"verifySnpAttestation",
JS_NewCFunction(
ctx, js_verify_snp_attestation, "verifySnpAttestation", 4));
return snp_attestation;
}
static void populate_global_snp_attestation(Context& ctx)
{
auto global_obj = ctx.get_global_obj();
global_obj.set("snp_attestation", create_snp_attestation_obj(ctx));
}
FFIPlugin snp_attestation_plugin = {
.name = "SNP Attestation",
.ccf_version = ccf::ccf_version,
.extend = populate_global_snp_attestation};
}

Просмотреть файл

@ -918,9 +918,8 @@ def test_npm_app(network, args):
r = c.get("/node/quotes/self")
primary_quote_info = r.body.json()
if args.enclave_platform != "sgx":
LOG.info("Skipping /app/verifyOpenEnclaveEvidence test, non-sgx node")
else:
if args.enclave_platform == "sgx":
LOG.info("SGX: Test verifyOpenEnclaveEvidence")
# See /opt/openenclave/include/openenclave/attestation/sgx/evidence.h
OE_FORMAT_UUID_SGX_ECDSA = "a3a21e87-1b4d-4014-b70a-a125d2fbcd8c"
r = c.post(
@ -948,6 +947,166 @@ def test_npm_app(network, args):
body = r.body.json()
assert body["claims"]["unique_id"] == primary_quote_info["mrenclave"], body
assert "sgx_report_data" in body["customClaims"], body
elif args.enclave_platform == "snp":
LOG.info("SNP: Test verifySnpAttestation")
def corrupt_value(value: str):
return value[len(value) // 2 :] + value[: len(value) // 2]
# Test without UVM endorsements
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"],
"endorsements": primary_quote_info["endorsements"],
},
)
assert r.status_code == http.HTTPStatus.OK, r.status_code
assert "uvm_endorsements" not in r.body.json()
for key, value in r.body.json().items():
LOG.info(f"{key} : {value}")
# Test with UVM endorsements
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"],
"endorsements": primary_quote_info["endorsements"],
"uvm_endorsements": primary_quote_info["uvm_endorsements"],
},
)
assert r.status_code == http.HTTPStatus.OK, r.status_code
assert "uvm_endorsements" in r.body.json()
for key, value in r.body.json().items():
LOG.info(f"{key} : {value}")
# Test endorsed TCB too small
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"],
"endorsements": primary_quote_info["endorsements"],
"uvm_endorsements": primary_quote_info["uvm_endorsements"],
"endorsed_tcb": "0000000000000000",
},
)
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
assert "does not match reported TCB" in r.body.json()["error"]["message"]
# Test too short a quote
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"][:-10],
"endorsements": primary_quote_info["endorsements"],
"uvm_endorsements": primary_quote_info["uvm_endorsements"],
},
)
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
assert (
"attestation report is not of expected size"
in r.body.json()["error"]["message"]
)
# Test too long a quote
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"] + "1",
"endorsements": primary_quote_info["endorsements"],
"uvm_endorsements": primary_quote_info["uvm_endorsements"],
},
)
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
assert (
"attestation report is not of expected size"
in r.body.json()["error"]["message"]
)
# Test corrupted quote
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": corrupt_value(primary_quote_info["raw"]),
"endorsements": primary_quote_info["endorsements"],
"uvm_endorsements": primary_quote_info["uvm_endorsements"],
},
)
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
# Test too short an endorsement
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"],
"endorsements": primary_quote_info["endorsements"][:-10],
"uvm_endorsements": primary_quote_info["uvm_endorsements"],
},
)
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
assert (
"Expected 3 endorsement certificates but got 2"
in r.body.json()["error"]["message"]
)
# Test too long an endorsement
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"],
"endorsements": primary_quote_info["endorsements"] + "1",
"uvm_endorsements": primary_quote_info["uvm_endorsements"],
},
)
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
# Test corrupted endorsements
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"],
"endorsements": corrupt_value(primary_quote_info["endorsements"]),
"uvm_endorsements": primary_quote_info["uvm_endorsements"],
},
)
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
# Test too short a uvm endorsement
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"],
"endorsements": primary_quote_info["endorsements"],
"uvm_endorsements": primary_quote_info["uvm_endorsements"][:-10],
},
)
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
# Test too long a uvm endorsement
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"],
"endorsements": primary_quote_info["endorsements"],
"uvm_endorsements": primary_quote_info["uvm_endorsements"] + "1",
},
)
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
# Test corrupted uvm endorsements
r = c.post(
"/app/verifySnpAttestation",
{
"evidence": primary_quote_info["raw"],
"endorsements": primary_quote_info["endorsements"],
"uvm_endorsements": corrupt_value(
primary_quote_info["uvm_endorsements"]
),
},
)
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
else:
LOG.info("Virtual: No attestation code to verify")
validate_openapi(c)
generate_and_verify_jwk(c)

Просмотреть файл

@ -623,6 +623,156 @@
}
}
},
"/verifySnpAttestation": {
"post": {
"js_module": "endpoints/snp_attestation.js",
"js_function": "verifySnpAttestation",
"forwarding_required": "sometimes",
"authn_policies": ["user_cert"],
"mode": "readonly",
"openapi": {
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"properties": {
"evidence": {
"type": "string"
},
"endorsements": {
"type": "string"
},
"uvm_endorsements": {
"type": "string"
},
"endorsed_tcb": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"properties": {
"attestation": {
"type": "object",
"properties": {
"version": { "type": "number" },
"guest_svn": { "type": "number" },
"policy": {
"type": "object",
"properties": {
"abi_minor": { "type": "number" },
"abi_major": { "type": "number" },
"smt": { "type": "number" },
"migrate_ma": { "type": "number" },
"debug": { "type": "number" },
"single_socket": { "type": "number" }
}
},
"family_id": { "type": "string" },
"image_id": { "type": "string" },
"vmpl": { "type": "number" },
"signature_algo": { "type": "number" },
"platform_version": {
"type": "object",
"properties": {
"boot_loader": { "type": "number" },
"tee": { "type": "number" },
"snp": { "type": "number" },
"microcode": { "type": "number" }
}
},
"platform_info": {
"type": "object",
"properties": {
"smt_en": { "type": "number" },
"tsme_en": { "type": "number" }
}
},
"flags": {
"type": "object",
"properties": {
"author_key_en": { "type": "number" },
"mask_chip_key": { "type": "number" },
"signing_key": { "type": "number" }
}
},
"report_data": { "type": "string" },
"measurement": { "type": "string" },
"host_data": { "type": "string" },
"id_key_digest": { "type": "string" },
"author_key_digest": { "type": "string" },
"report_id": { "type": "string" },
"report_id_ma": { "type": "string" },
"reported_tcb": {
"type": "object",
"properties": {
"boot_loader": { "type": "number" },
"tee": { "type": "number" },
"snp": { "type": "number" },
"microcode": { "type": "number" }
}
},
"chip_id": { "type": "string" },
"committed_tcb": {
"type": "object",
"properties": {
"boot_loader": { "type": "number" },
"tee": { "type": "number" },
"snp": { "type": "number" },
"microcode": { "type": "number" }
}
},
"current_minor": { "type": "number" },
"current_build": { "type": "number" },
"current_major": { "type": "number" },
"committed_build": { "type": "number" },
"committed_minor": { "type": "number" },
"committed_major": { "type": "number" },
"launch_tcb": {
"type": "object",
"properties": {
"boot_loader": { "type": "number" },
"tee": { "type": "number" },
"snp": { "type": "number" },
"microcode": { "type": "number" }
}
},
"signature": {
"type": "object",
"properties": {
"r": { "type": "string" },
"s": { "type": "string" }
}
}
}
},
"uvm_endorsements": {
"type": "object",
"properties": {
"did": { "type": "string" },
"feed": { "type": "string" },
"svn": { "type": "string" }
}
}
}
}
}
}
}
}
}
}
},
"/log": {
"get": {
"js_module": "endpoints/log.js",

Просмотреть файл

@ -1,6 +1,7 @@
export * from "./jwt";
export * from "./crypto";
export * from "./oe";
export * from "./snp_attestation";
export * from "./partition";
export * from "./proto";
export * from "./log";

Просмотреть файл

@ -0,0 +1,144 @@
import { Base64 } from "js-base64";
import * as ccfapp from "@microsoft/ccf-app";
import * as ccfsnp from "@microsoft/ccf-app/snp_attestation";
interface ErrorResponse {
error: {
message: string;
};
}
interface SnpEvidence {
evidence: string;
endorsements: string;
uvm_endorsements: string;
endorsed_tcb?: string;
}
export interface TcbVersion {
boot_loader: number;
tee: number;
snp: number;
microcode: number;
}
interface SnpAttestationResult {
attestation: {
version: number;
guest_svn: number;
policy: {
abi_minor: number;
abi_major: number;
smt: number;
migrate_ma: number;
debug: number;
single_socket: number;
};
family_id: string;
image_id: string;
vmpl: number;
signature_algo: number;
platform_version: TcbVersion;
platform_info: {
smt_en: number;
tsme_en: number;
};
flags: {
author_key_en: number;
mask_chip_key: number;
signing_key: number;
};
report_data: string;
measurement: string;
host_data: string;
id_key_digest: string;
author_key_digest: string;
report_id: string;
report_id_ma: string;
reported_tcb: TcbVersion;
chip_id: string;
committed_tcb: TcbVersion;
current_minor: number;
current_build: number;
current_major: number;
committed_build: number;
committed_minor: number;
committed_major: number;
launch_tcb: TcbVersion;
signature: {
r: string;
s: string;
};
};
uvm_endorsements?: {
did: string;
feed: string;
svn: string;
};
}
export function verifySnpAttestation(
request: ccfapp.Request<SnpEvidence>,
): ccfapp.Response<SnpAttestationResult | ErrorResponse> {
try {
const body = request.body.json();
const evidence = ccfapp
.typedArray(Uint8Array)
.encode(Base64.toUint8Array(body.evidence));
const endorsements = ccfapp
.typedArray(Uint8Array)
.encode(Base64.toUint8Array(body.endorsements));
const uvm_endorsements =
body.uvm_endorsements !== undefined
? ccfapp
.typedArray(Uint8Array)
.encode(Base64.toUint8Array(body.uvm_endorsements))
: undefined;
const r = ccfsnp.verifySnpAttestation(
evidence,
endorsements,
uvm_endorsements,
body.endorsed_tcb,
);
return {
body: {
attestation: {
...r.attestation,
family_id: hex(r.attestation.family_id),
image_id: hex(r.attestation.image_id),
report_data: hex(r.attestation.report_data),
measurement: hex(r.attestation.measurement),
host_data: hex(r.attestation.host_data),
id_key_digest: hex(r.attestation.id_key_digest),
author_key_digest: hex(r.attestation.author_key_digest),
report_id: hex(r.attestation.report_id),
report_id_ma: hex(r.attestation.report_id_ma),
chip_id: hex(r.attestation.chip_id),
signature: {
r: hex(r.attestation.signature.r),
s: hex(r.attestation.signature.s),
},
},
uvm_endorsements: r.uvm_endorsements,
},
};
} catch (e) {
return {
statusCode: 400,
body: {
error: {
message: e.message,
},
},
};
}
}
function hex(buf: ArrayBuffer) {
return Array.from(new Uint8Array(buf))
.map((n) => n.toString(16).padStart(2, "0"))
.join("");
}