зеркало из https://github.com/microsoft/CCF.git
Expose SNP Attestation validation in TS (#5653)
This commit is contained in:
Родитель
fdd38c4cc7
Коммит
6e7caf0098
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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("");
|
||||
}
|
Загрузка…
Ссылка в новой задаче