Build Azure-specific confidential computing evidence/attestation into {attestation,env}/azuresnp
This commit is contained in:
parent
9fc13fa569
commit
d03ead712f
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -25,3 +25,6 @@
|
||||
[submodule "enclave/sev-guest"]
|
||||
path = enclave/sev-guest
|
||||
url = https://github.com/AMDESE/sev-guest/
|
||||
[submodule "enclave/rapidjson"]
|
||||
path = enclave/rapidjson
|
||||
url = https://github.com/Tencent/rapidjson
|
||||
|
||||
@ -10,7 +10,7 @@ include .testdepends
|
||||
|
||||
.PHONY: build
|
||||
|
||||
build: build/enclave.bin build/enclave.nsm build/enclave.sev
|
||||
build: build/enclave.bin build/enclave.nsm build/enclave.sev build/enclave.azuresnp build/attest.azuresnp
|
||||
|
||||
sign: build/enclave.signed build/enclave.test build/enclave.small
|
||||
|
||||
@ -221,7 +221,6 @@ build/enclave.nsm: $(patsubst %,build/%/X86.a,$(NSM_LIBRARIES))
|
||||
$(QUIET) $(CXX) -o $@ $(X86_LDFLAGS) -Wl,--start-group $^ -Wl,--end-group $(X86_LDFLAGS)
|
||||
|
||||
SEV_LIBRARIES = \
|
||||
socketmain \
|
||||
$(CORE_LIBRARIES_PRE_ENV) \
|
||||
env/sev \
|
||||
env/socket \
|
||||
@ -232,24 +231,32 @@ SEV_LIBRARIES = \
|
||||
boringssl \
|
||||
## SEV_LIBRARIES
|
||||
|
||||
build/enclave.sev: $(patsubst %,build/%/X86.a,$(SEV_LIBRARIES))
|
||||
build/enclave.sev: build/socketmain/X86.a $(patsubst %,build/%/X86.a,$(SEV_LIBRARIES))
|
||||
$(QUIET) echo -e "BUILD\t$@"
|
||||
$(QUIET) $(CXX) -o $@ $(X86_LDFLAGS) -Wl,--start-group $^ -Wl,--end-group $(X86_LDFLAGS)
|
||||
|
||||
SEVATTEST_LIBRARIES = \
|
||||
initmain \
|
||||
build/attest.sev: build/initmain/X86.a $(patsubst %,build/%/X86.a,$(SEV_LIBRARIES))
|
||||
$(QUIET) echo -e "BUILD\t$@"
|
||||
$(QUIET) $(CXX) -o $@ $(X86_LDFLAGS) -Wl,--start-group $^ -Wl,--end-group $(X86_LDFLAGS)
|
||||
|
||||
AZURESNP_LIBRARIES = \
|
||||
$(CORE_LIBRARIES_PRE_ENV) \
|
||||
env \
|
||||
env/sev \
|
||||
env/azuresnp \
|
||||
env/socket \
|
||||
attestation/sev \
|
||||
attestation/tpm2 \
|
||||
attestation/azuresnp \
|
||||
socketwrap \
|
||||
context \
|
||||
util \
|
||||
$(CORE_LIBRARIES_POST_ENV) \
|
||||
boringssl \
|
||||
## SEVATTEST_LIBRARIES
|
||||
## AZURESNP_LIBRARIES
|
||||
|
||||
build/attest.sev: $(patsubst %,build/%/X86.a,$(SEVATTEST_LIBRARIES))
|
||||
build/enclave.azuresnp: build/socketmain/X86.a $(patsubst %,build/%/X86.a,$(AZURESNP_LIBRARIES))
|
||||
$(QUIET) echo -e "BUILD\t$@"
|
||||
$(QUIET) $(CXX) -o $@ $(X86_LDFLAGS) -Wl,--start-group $^ -Wl,--end-group $(X86_LDFLAGS)
|
||||
|
||||
build/attest.azuresnp: build/initmain/X86.a $(patsubst %,build/%/X86.a,$(AZURESNP_LIBRARIES))
|
||||
$(QUIET) echo -e "BUILD\t$@"
|
||||
$(QUIET) $(CXX) -o $@ $(X86_LDFLAGS) -Wl,--start-group $^ -Wl,--end-group $(X86_LDFLAGS)
|
||||
|
||||
|
||||
@ -57,6 +57,7 @@ LIBRARY_CFLAGS = \
|
||||
-I$(CURDIR)/tinycbor/src \
|
||||
-I$(CURDIR)/boringssl/include \
|
||||
-I$(CURDIR)/sev-guest/include \
|
||||
-I$(CURDIR)/rapidjson/include \
|
||||
## LIBRARY_CFLAGS
|
||||
|
||||
TEST_CFLAGS ?= \
|
||||
|
||||
238
enclave/attestation/azuresnp/azuresnp.cc
Normal file
238
enclave/attestation/azuresnp/azuresnp.cc
Normal file
@ -0,0 +1,238 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
#include "attestation/azuresnp/azuresnp.h"
|
||||
#include "attestation/sev/sev.h"
|
||||
#include "util/macros.h"
|
||||
#include "util/log.h"
|
||||
#include "hmac/hmac.h"
|
||||
#include "util/constant.h"
|
||||
#include "util/hex.h"
|
||||
#include "util/base64.h"
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/base.h>
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
namespace svr2::attestation::azuresnp {
|
||||
namespace {
|
||||
|
||||
const char* MSFT_AKCERT_ROOT = R"EOF(
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFsDCCA5igAwIBAgIQUfQx2iySCIpOKeDZKd5KpzANBgkqhkiG9w0BAQwFADBp
|
||||
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTow
|
||||
OAYDVQQDEzFBenVyZSBWaXJ0dWFsIFRQTSBSb290IENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eSAyMDIzMB4XDTIzMDYwMTE4MDg1M1oXDTQ4MDYwMTE4MTU0MVowaTELMAkG
|
||||
A1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE6MDgGA1UE
|
||||
AxMxQXp1cmUgVmlydHVhbCBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkg
|
||||
MjAyMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALoMMwvdRJ7+bW00
|
||||
adKE1VemNqJS+268Ure8QcfZXVOsVO22+PL9WRoPnWo0r5dVoomYGbobh4HC72s9
|
||||
sGY6BGRe+Ui2LMwuWnirBtOjaJ34r1ZieNMcVNJT/dXW5HN/HLlm/gSKlWzqCEx6
|
||||
gFFAQTvyYl/5jYI4Oe05zJ7ojgjK/6ZHXpFysXnyUITJ9qgjn546IJh/G5OMC3mD
|
||||
fFU7A/GAi+LYaOHSzXj69Lk1vCftNq9DcQHtB7otO0VxFkRLaULcfu/AYHM7FC/S
|
||||
q6cJb9Au8K/IUhw/5lJSXZawLJwHpcEYzETm2blad0VHsACaLNucZL5wBi8GEusQ
|
||||
9Wo8W1p1rUCMp89pufxa3Ar9sYZvWeJlvKggWcQVUlhvvIZEnT+fteEvwTdoajl5
|
||||
qSvZbDPGCPjb91rSznoiLq8XqgQBBFjnEiTL+ViaZmyZPYUsBvBY3lKXB1l2hgga
|
||||
hfBIag4j0wcgqlL82SL7pAdGjq0Fou6SKgHnkkrV5CNxUBBVMNCwUoj5mvEjd5mF
|
||||
7XPgfM98qNABb2Aqtfl+VuCkU/G1XvFoTqS9AkwbLTGFMS9+jCEU2rw6wnKuGv1T
|
||||
x9iuSdNvsXt8stx4fkVeJvnFpJeAIwBZVgKRSTa3w3099k0mW8qGiMnwCI5SfdZ2
|
||||
SJyD4uEmszsnieE6wAWd1tLLg1jvAgMBAAGjVDBSMA4GA1UdDwEB/wQEAwIBhjAP
|
||||
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRL/iZalMH2M8ODSCbd8+WwZLKqlTAQ
|
||||
BgkrBgEEAYI3FQEEAwIBADANBgkqhkiG9w0BAQwFAAOCAgEALgNAyg8I0ANNO/8I
|
||||
2BhpTOsbywN2YSmShAmig5h4sCtaJSM1dRXwA+keY6PCXQEt/PRAQAiHNcOF5zbu
|
||||
OU1Bw/Z5Z7k9okt04eu8CsS2Bpc+POg9js6lBtmigM5LWJCH1goMD0kJYpzkaCzx
|
||||
1TdD3yjo0xSxgGhabk5Iu1soD3OxhUyIFcxaluhwkiVINt3Jhy7G7VJTlEwkk21A
|
||||
oOrQxUsJH0f2GXjYShS1r9qLPzLf7ykcOm62jHGmLZVZujBzLIdNk1bljP9VuGW+
|
||||
cISBwzkNeEMMFufcL2xh6s/oiUnXicFWvG7E6ioPnayYXrHy3Rh68XLnhfpzeCzv
|
||||
bz/I4yMV38qGo/cAY2OJpXUuuD/ZbI5rT+lRBEkDW1kxHP8cpwkRwGopV8+gX2KS
|
||||
UucIIN4l8/rrNDEX8T0b5U+BUqiO7Z5YnxCya/H0ZIwmQnTlLRTU2fW+OGG+xyIr
|
||||
jMi/0l6/yWPUkIAkNtvS/yO7USRVLPbtGVk3Qre6HcqacCXzEjINcJhGEVg83Y8n
|
||||
M+Y+a9J0lUnHytMSFZE85h88OseRS2QwqjozUo2j1DowmhSSUv9Na5Ae22ycciBk
|
||||
EZSq8a4rSlwqthaELNpeoTLUk6iVoUkK/iLvaMvrkdj9yJY1O/gvlfN2aiNTST/2
|
||||
bd+PA4RBToG9rXn6vNkUWdbLibU=
|
||||
-----END CERTIFICATE-----
|
||||
)EOF";
|
||||
|
||||
bssl::UniquePtr<STACK_OF(X509)> RootsOfTrust() {
|
||||
bssl::UniquePtr<STACK_OF(X509)> root_stack(sk_X509_new_null());
|
||||
CHECK(root_stack.get() != nullptr);
|
||||
for (auto root : {MSFT_AKCERT_ROOT}) {
|
||||
bssl::UniquePtr<BIO> root_bio(BIO_new_mem_buf(root, -1));
|
||||
CHECK(root_bio.get() != nullptr);
|
||||
bssl::UniquePtr<X509> root_x509(PEM_read_bio_X509(root_bio.get(), nullptr, nullptr, nullptr));
|
||||
CHECK(root_x509.get() != nullptr);
|
||||
CHECK(0 != sk_X509_push(root_stack.get(), root_x509.get()));
|
||||
root_x509.release(); // owned by root_stack
|
||||
}
|
||||
return root_stack;
|
||||
}
|
||||
|
||||
const bssl::UniquePtr<STACK_OF(X509)> roots_of_trust = RootsOfTrust();
|
||||
|
||||
} // namespace
|
||||
|
||||
std::pair<std::string, error::Error> SNPReportBufferFromAzureBuffer(const std::string& tpm2_buffer) {
|
||||
if (tpm2_buffer.size() < 1236) {
|
||||
return std::make_pair("", COUNTED_ERROR(AzureSNP_AzureBufferTooSmall));
|
||||
}
|
||||
return std::make_pair(tpm2_buffer.substr(32, 1184), error::OK);
|
||||
}
|
||||
|
||||
std::pair<std::string, error::Error> RuntimeDataBufferFromAzureBuffer(const std::string& tpm2_buffer) {
|
||||
if (tpm2_buffer.size() < 1236) {
|
||||
return std::make_pair("", COUNTED_ERROR(AzureSNP_AzureBufferTooSmall));
|
||||
}
|
||||
// The runtime data buffer may be suffixed with any number of \0 bytes.
|
||||
// These bytes are ignored for the purpose of hashing, etc, so we strip
|
||||
// them off with strnlen.
|
||||
const char* start = tpm2_buffer.data() + 1236;
|
||||
size_t size = strnlen(start, tpm2_buffer.size() - 1236);
|
||||
return std::make_pair(std::string(start, size), error::OK);
|
||||
}
|
||||
|
||||
error::Error VerifyAKCert(context::Context* ctx, const ASNPEvidence& evidence, const ASNPEndorsements& endorsements, util::UnixSecs now) {
|
||||
LOG(DEBUG) << "Parsing AKCert DER";
|
||||
auto akcert_start = reinterpret_cast<const uint8_t*>(evidence.akcert_der().data());
|
||||
bssl::UniquePtr<X509> akcert(d2i_X509(nullptr, &akcert_start, evidence.akcert_der().size()));
|
||||
if (!akcert) {
|
||||
return COUNTED_ERROR(AzureSNP_InvalidAKCert);
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Parsing AKCert intermediate DER";
|
||||
auto intermediate_start = reinterpret_cast<const uint8_t*>(endorsements.intermediate_der().data());
|
||||
bssl::UniquePtr<X509> intermediate(d2i_X509(nullptr, &intermediate_start, endorsements.intermediate_der().size()));
|
||||
if (!intermediate) {
|
||||
return COUNTED_ERROR(AzureSNP_InvalidAKCertIntermediate);
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Building X509 context to verify AKCert";
|
||||
bssl::UniquePtr<X509_STORE_CTX> x509ctx(X509_STORE_CTX_new());
|
||||
bssl::UniquePtr<X509_STORE> store(X509_STORE_new());
|
||||
bssl::UniquePtr<STACK_OF(X509)> intermediates(sk_X509_new_null());
|
||||
if (!x509ctx || !store || !intermediates ||
|
||||
0 == sk_X509_push(intermediates.get(), intermediate.get())) {
|
||||
return COUNTED_ERROR(AzureSNP_CryptoAllocate);
|
||||
}
|
||||
intermediate.release(); // now owned by [intermediates]
|
||||
if (!X509_STORE_CTX_init(x509ctx.get(), store.get(), akcert.get(), intermediates.get())) {
|
||||
return COUNTED_ERROR(AzureSNP_CryptoStoreInit);
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Verifying AKCert against MSFT root-of-trust";
|
||||
X509_STORE_CTX_set0_trusted_stack(x509ctx.get(), roots_of_trust.get());
|
||||
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(x509ctx.get());
|
||||
X509_VERIFY_PARAM_set_time_posix(param, now);
|
||||
if (1 != X509_verify_cert(x509ctx.get())) {
|
||||
auto err = X509_STORE_CTX_get_error(x509ctx.get());
|
||||
LOG(ERROR) << "SEV attestation verify_cert err=" << err << ": " << X509_verify_cert_error_string(err);
|
||||
return COUNTED_ERROR(AzureSNP_AKCertificateChainVerify);
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Verifying that SNP report is valid";
|
||||
auto sevsnp_endorsements = ctx->Protobuf<attestation::sev::SevSnpEndorsements>();
|
||||
sevsnp_endorsements->set_vcek_der(endorsements.vcek_der());
|
||||
sevsnp_endorsements->set_ask_der(endorsements.ask_der());
|
||||
auto [snp_report_buf, srberr] = SNPReportBufferFromAzureBuffer(evidence.azure_report());
|
||||
RETURN_IF_ERROR(srberr);
|
||||
auto [snp_report, snperr] = ReportFromVerifiedBuffer(snp_report_buf, *sevsnp_endorsements, now);
|
||||
RETURN_IF_ERROR(snperr);
|
||||
LOG(INFO) << snp_report;
|
||||
|
||||
LOG(DEBUG) << "Verifying that runtime data is verified by SNP report";
|
||||
auto [runtimedata, rtderr] = RuntimeDataBufferFromAzureBuffer(evidence.azure_report());
|
||||
RETURN_IF_ERROR(rtderr);
|
||||
auto runtimedata_sha256 = hmac::Sha256(runtimedata);
|
||||
LOG(DEBUG) << "Runtime data: " << reinterpret_cast<const char*>(runtimedata.data());
|
||||
if (!util::ConstantTimeEqualsBytes(runtimedata_sha256.data(), snp_report.report_data, runtimedata_sha256.size())) {
|
||||
return COUNTED_ERROR(AzureSNP_ReportDataMismatch);
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Parsing runtime JSON to pull out and verify HCLAkPub";
|
||||
rapidjson::Document runtime_doc;
|
||||
if (runtime_doc.Parse(runtimedata.c_str()).HasParseError() ||
|
||||
!runtime_doc.HasMember("keys") ||
|
||||
!runtime_doc["keys"].IsArray() ||
|
||||
runtime_doc["keys"].Size() < 1) {
|
||||
return COUNTED_ERROR(AzureSNP_RuntimeDataJSON);
|
||||
}
|
||||
const auto& runtime_keys = runtime_doc["keys"];
|
||||
size_t key_idx = 0;
|
||||
for (; key_idx < runtime_keys.Size(); key_idx++) {
|
||||
const auto& runtime_key = runtime_keys[key_idx];
|
||||
if (runtime_key.HasMember("kid") &&
|
||||
runtime_key["kid"].IsString() &&
|
||||
0 == strcmp(runtime_key["kid"].GetString(), "HCLAkPub") &&
|
||||
runtime_key.HasMember("kty") &&
|
||||
runtime_key["kty"].IsString() &&
|
||||
0 == strcmp(runtime_key["kty"].GetString(), "RSA")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (key_idx >= runtime_keys.Size()) {
|
||||
return COUNTED_ERROR(AzureSNP_RuntimeDataJSON);
|
||||
}
|
||||
const auto& runtime_key = runtime_keys[key_idx];
|
||||
if (!runtime_key.HasMember("e") ||
|
||||
!runtime_key["e"].IsString() ||
|
||||
!runtime_key.HasMember("n") ||
|
||||
!runtime_key["n"].IsString()) {
|
||||
return COUNTED_ERROR(AzureSNP_RuntimeDataJSON);
|
||||
}
|
||||
std::string rte(runtime_key["e"].GetString());
|
||||
std::string rtn(runtime_key["n"].GetString());
|
||||
RETURN_IF_ERROR(util::B64DecodeInline(&rte, util::B64URL));
|
||||
RETURN_IF_ERROR(util::B64DecodeInline(&rtn, util::B64URL));
|
||||
|
||||
LOG(DEBUG) << "Making sure HCLAkPub key matches public key certified by AKCert";
|
||||
bssl::UniquePtr<EVP_PKEY> akcert_pk(X509_get_pubkey(akcert.get()));
|
||||
if (!akcert_pk) { return COUNTED_ERROR(AzureSNP_AKCertPubKey); }
|
||||
auto* akcert_rsa = EVP_PKEY_get0_RSA(akcert_pk.get()); // non-owning reference
|
||||
if (!akcert_rsa) { return COUNTED_ERROR(AzureSNP_AKCertPubKey); }
|
||||
auto* akcert_e = RSA_get0_e(akcert_rsa); // non-owning reference
|
||||
auto* akcert_n = RSA_get0_n(akcert_rsa); // non-owning reference
|
||||
if (!akcert_e || !akcert_n) { return COUNTED_ERROR(AzureSNP_AKCertPubKey); }
|
||||
std::string ake(BN_num_bytes(akcert_e), '\0');
|
||||
ake.resize(BN_bn2bin(akcert_e, reinterpret_cast<uint8_t*>(ake.data())));
|
||||
std::string akn(BN_num_bytes(akcert_n), '\0');
|
||||
akn.resize(BN_bn2bin(akcert_n, reinterpret_cast<uint8_t*>(akn.data())));
|
||||
if (!util::ConstantTimeEquals(rte, ake) || !util::ConstantTimeEquals(rtn, akn)) {
|
||||
return COUNTED_ERROR(AzureSNP_AKCertMismatch);
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "AKCert verified successfully";
|
||||
return error::OK;
|
||||
}
|
||||
|
||||
error::Error CheckRemotePCRs(context::Context* ctx, const attestation::tpm2::PCRs& local, const attestation::tpm2::PCRs& remote) {
|
||||
bool equals = 1;
|
||||
// See:
|
||||
// - https://wiki.archlinux.org/title/Trusted_Platform_Module
|
||||
// - https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/
|
||||
// Note: we verify UEFI by checking AKCert against MSFT root-of-trust, NOT by checking PCRs here.
|
||||
for (size_t i : {
|
||||
// 0, // Core system firmware executable code (UEFI)
|
||||
// 1, // Core system firmware data/host platform configuration (serial/model #s)
|
||||
2, // Extended/pluggable executable code (option ROMs on pluggable hardware)
|
||||
3, // Extended/pluggable firmware data, set during UEFI boot select
|
||||
4, // Boot loader and additional drivers, binaries and extensions loaded by boot loader
|
||||
5, // config to bootloaders, GPT partition table
|
||||
6, // resume from S4/S5 power state events
|
||||
7, // secure boot state, including PK/KEK/db, specific certificates used
|
||||
8, // Kernel command line (grub and systemd-boot only)
|
||||
9, // Kernel image, initrd, and EFI load options (kernel command line)
|
||||
// 10, // reserved for future use
|
||||
11, // hash of unified kernel image (systemd-stub)
|
||||
12, // Kernel command line, system credentials, system configuration images
|
||||
13, // System extension images for the initrd
|
||||
14, // MOK certificates and hashes
|
||||
// 15, // Userspace stuff
|
||||
}) {
|
||||
equals &= util::ConstantTimeEquals(local[i], remote[i]);
|
||||
}
|
||||
if (!equals) {
|
||||
return COUNTED_ERROR(AzureSNP_PCRMismatch);
|
||||
}
|
||||
return error::OK;
|
||||
}
|
||||
|
||||
} // namespace svr2::attestation::azuresnp
|
||||
76
enclave/attestation/azuresnp/azuresnp.h
Normal file
76
enclave/attestation/azuresnp/azuresnp.h
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
#ifndef __SVR2_ATTESTATION_AZURESNP_AZURESNP_H__
|
||||
#define __SVR2_ATTESTATION_AZURESNP_AZURESNP_H__
|
||||
|
||||
#include "proto/azuresnp.pb.h"
|
||||
#include "proto/error.pb.h"
|
||||
#include "attestation/tpm2/tpm2.h"
|
||||
#include "context/context.h"
|
||||
#include "util/ticks.h"
|
||||
|
||||
namespace svr2::attestation::azuresnp {
|
||||
|
||||
// Azure confidential computing allows for the use of AMD SEV-SNP (herafter
|
||||
// AMDSNP) protection for its computation. AMDSNP measures the boot state
|
||||
// of a (running) container and provides a cryptographic proof of that
|
||||
// state rooted at AMD (via an X509 chain). An AMDSNP report specifically
|
||||
// measures the firmware used to run a container. However, Azure does not
|
||||
// open-source its firmware, and may regularly or without notice update the
|
||||
// firmware across its fleet. Therefore, it uses a combination of AMDSNP
|
||||
// and a TPM2 set of PCRs to provide trust that's rooted at MSFT+AMD, not
|
||||
// just AMD (herafter AzSNP). It does this by:
|
||||
//
|
||||
// - running firmware vetted by MSFT, and after vetting providing an
|
||||
// AKCert to that firmware's TPM2 module
|
||||
// - generating, on boot and after firmware vetting, an AMDSNP report that
|
||||
// contains a `report_data` including the publick key of that AKCert
|
||||
// - using TPM2 PCRs in the normal manner to measure the boot process,
|
||||
// kernel, kernel cmd line, etc.
|
||||
//
|
||||
// To use this AzSNP verification, then, we must:
|
||||
//
|
||||
// - verify the SNP report is valid (but not look at its measurement,
|
||||
// since we have no basis for checking its validity)
|
||||
// - check that the TPM2 has an AKCert that roots to MSFT's root-of-trust
|
||||
// - checking that the SNP report contains a guarantee of the public key
|
||||
// used by the AKCert
|
||||
// - getting a TPM2 quote of PCRs using the AKCert
|
||||
//
|
||||
// This, in short, gives us verification of the AMD chip/microcode
|
||||
// (via the SNP report), firmware (via rooting of the AKCert in a MSFT
|
||||
// trust root), and kernel/cmd-line/boot-process (via TPM2 PCRs).
|
||||
// To verify up to the application layer, we must go further to tie
|
||||
// the kernel/cmdline to the appication layer, which we initially do
|
||||
// via dm-verity, allowing us to verify the disk image hash within the
|
||||
// kernel cmdline.
|
||||
|
||||
// Given the azure TPM2 buffer, extract the SNP report buffer from it.
|
||||
std::pair<std::string, error::Error> SNPReportBufferFromAzureBuffer(const std::string& tpm2_buffer);
|
||||
// Given the azure TPM2 buffer, extract the runtime report that's hashed
|
||||
// into snp_report.report_data from it.
|
||||
std::pair<std::string, error::Error> RuntimeDataBufferFromAzureBuffer(const std::string& tpm2_buffer);
|
||||
|
||||
// Verifies that the AK certificate in `evidence` is rooted in the
|
||||
// known MSFT root-of-trust and is correctly contained by the SNP
|
||||
// report in `evidence`. This verification includes:
|
||||
// - verifying that evidence.akcert_der is valid
|
||||
// - verifying that endorsements.intermediate_der is valid
|
||||
// - verifying that evidence.akcert_der and endorsements.intermediate_der are
|
||||
// part of a currently-valid certificate chain up to the MSFT root-of-trust
|
||||
// - verifying that evidence.azure_report's SNP report is valid
|
||||
// - verifying that evidence.azure_report's SNP report's report_data contains
|
||||
// a SHA256 of evidence.azure_report's runtime data
|
||||
// - verifying that evidence.azure_report's runtime data contains the public
|
||||
// key contained within evidence.akcert_der (RSA `n` and `e` match)
|
||||
error::Error VerifyAKCert(context::Context* ctx, const ASNPEvidence& evidence, const ASNPEndorsements& endorsements, util::UnixSecs now);
|
||||
|
||||
// Given a set of PCRs for the local machine and a set of PCRs for
|
||||
// a potential remote peer, verify that the potential peer's PCRs are
|
||||
// allowable and we should move forward with the trusted connection.
|
||||
error::Error CheckRemotePCRs(context::Context* ctx, const attestation::tpm2::PCRs& local, const attestation::tpm2::PCRs& remote);
|
||||
|
||||
} // namespace svr2::attestation::azuresnp
|
||||
|
||||
#endif // __SVR2_ATTESTATION_AZURESNP_AZURESNP_H__
|
||||
85
enclave/attestation/azuresnp/tests/azuresnp.cc
Normal file
85
enclave/attestation/azuresnp/tests/azuresnp.cc
Normal file
File diff suppressed because one or more lines are too long
@ -45,14 +45,6 @@ constexpr UUID UUID_ARK = {0xc0, 0xb4, 0x06, 0xa4, 0xa8, 0x03, 0x49, 0x52, 0x97
|
||||
constexpr UUID UUID_VLEK = {0xa8, 0x07, 0x4b, 0xc2, 0xa2, 0x5a, 0x48, 0x3e, 0xaa, 0xe6, 0x39, 0xc0, 0x45, 0xa0, 0xb8, 0xa1};
|
||||
constexpr UUID UUID_CRL = {0x92, 0xf8, 0x1b, 0xc3, 0x58, 0x11, 0x4d, 0x3d, 0x97, 0xff, 0xd1, 0x9f, 0x88, 0xdc, 0x67, 0xea};
|
||||
|
||||
std::pair<const attestation_report*, error::Error> ReportFromUnverifiedEvidence(const std::string& evidence) {
|
||||
if (evidence.size() < sizeof(attestation_report)) {
|
||||
return std::make_pair(nullptr, COUNTED_ERROR(AttestationSEV_EvidenceTooSmallForReport));
|
||||
}
|
||||
auto report = reinterpret_cast<const attestation_report*>(evidence.data());
|
||||
return std::make_pair(report, error::OK);
|
||||
}
|
||||
|
||||
// AMD Root Key (ARK) for Milan processors.
|
||||
const char* ARK_MILAN_PEM = R"EOF(
|
||||
-----BEGIN CERTIFICATE-----
|
||||
@ -177,6 +169,15 @@ error::Error AllowRemote(const attestation_report& local, const attestation_repo
|
||||
return error::OK;
|
||||
}
|
||||
|
||||
std::pair<attestation_report, error::Error> ReportFromUnverifiedBuffer(const std::string& evidence) {
|
||||
attestation_report out;
|
||||
if (evidence.size() < sizeof(attestation_report)) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_EvidenceTooSmallForReport));
|
||||
}
|
||||
out = *reinterpret_cast<const attestation_report*>(evidence.data());
|
||||
return std::make_pair(out, error::OK);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
minimums::MinimumValues MinimumsFromReport(const attestation_report& report) {
|
||||
@ -253,16 +254,16 @@ error::Error CertificatesToEndorsements(const uint8_t* certs, uint32_t certs_siz
|
||||
LOG(INFO) << "Certificate: " << util::ToHex(uuid);
|
||||
switch (uuid[0]) {
|
||||
case UUID_VCEK[0]:
|
||||
if (uuid == UUID_VCEK) { endorsements->set_vcek(util::ByteVectorToString(cert)); }
|
||||
if (uuid == UUID_VCEK) { endorsements->set_vcek_der(util::ByteVectorToString(cert)); }
|
||||
break;
|
||||
case UUID_ASK[0]:
|
||||
if (uuid == UUID_ASK) { endorsements->set_ask(util::ByteVectorToString(cert)); }
|
||||
if (uuid == UUID_ASK) { endorsements->set_ask_der(util::ByteVectorToString(cert)); }
|
||||
break;
|
||||
case UUID_ARK[0]:
|
||||
if (uuid == UUID_ARK) { endorsements->set_ark(util::ByteVectorToString(cert)); }
|
||||
if (uuid == UUID_ARK) { endorsements->set_ark_der(util::ByteVectorToString(cert)); }
|
||||
break;
|
||||
case UUID_VLEK[0]:
|
||||
if (uuid == UUID_VLEK) { endorsements->set_vlek(util::ByteVectorToString(cert)); }
|
||||
if (uuid == UUID_VLEK) { endorsements->set_vlek_der(util::ByteVectorToString(cert)); }
|
||||
break;
|
||||
case UUID_CRL[0]:
|
||||
if (uuid == UUID_CRL) { endorsements->set_crl(util::ByteVectorToString(cert)); }
|
||||
@ -277,40 +278,26 @@ error::Error CertificatesToEndorsements(const uint8_t* certs, uint32_t certs_siz
|
||||
}
|
||||
|
||||
std::pair<attestation_report, error::Error> ReportFromUnverifiedAttestation(const e2e::Attestation& attestation) {
|
||||
auto [report, err] = ReportFromUnverifiedEvidence(attestation.evidence());
|
||||
attestation_report out;
|
||||
if (err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
}
|
||||
memcpy(&out, report, sizeof(out));
|
||||
return std::make_pair(out, ValidateReport(out));
|
||||
return ReportFromUnverifiedBuffer(attestation.evidence());
|
||||
}
|
||||
|
||||
std::pair<attestation::AttestationData, error::Error> DataFromVerifiedAttestation(const attestation_report& local, const e2e::Attestation& attestation, util::UnixSecs now) {
|
||||
auto [report, err] = ReportFromUnverifiedEvidence(attestation.evidence());
|
||||
attestation::AttestationData out;
|
||||
std::pair<attestation_report, error::Error> ReportFromVerifiedBuffer(const std::string& buffer, const SevSnpEndorsements& endorsements, util::UnixSecs now) {
|
||||
auto [report, err] = ReportFromUnverifiedBuffer(buffer);
|
||||
if (err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
}
|
||||
if (auto err = AllowRemote(local, *report); err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
}
|
||||
SevSnpEndorsements endorsements;
|
||||
if (!endorsements.ParseFromString(attestation.endorsements())) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_ParseEndorsements));
|
||||
return std::make_pair(report, err);
|
||||
}
|
||||
|
||||
// Get VCEK and ASK from endorsements.
|
||||
auto vcek_start = reinterpret_cast<const uint8_t*>(endorsements.vcek().data());
|
||||
auto ask_start = reinterpret_cast<const uint8_t*>(endorsements.ask().data());
|
||||
bssl::UniquePtr<X509> vcek(d2i_X509(nullptr, &vcek_start, endorsements.vcek().size()));
|
||||
bssl::UniquePtr<X509> ask(d2i_X509(nullptr, &ask_start, endorsements.ask().size()));
|
||||
auto vcek_start = reinterpret_cast<const uint8_t*>(endorsements.vcek_der().data());
|
||||
auto ask_start = reinterpret_cast<const uint8_t*>(endorsements.ask_der().data());
|
||||
bssl::UniquePtr<X509> vcek(d2i_X509(nullptr, &vcek_start, endorsements.vcek_der().size()));
|
||||
bssl::UniquePtr<X509> ask(d2i_X509(nullptr, &ask_start, endorsements.ask_der().size()));
|
||||
if (!vcek || !ask) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_EndorsementBadCert));
|
||||
return std::make_pair(report, COUNTED_ERROR(AttestationSEV_EndorsementBadCert));
|
||||
}
|
||||
bssl::UniquePtr<EVP_PKEY> vcek_pub(X509_get_pubkey(vcek.get()));
|
||||
if (!vcek_pub) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_CryptoAllocate));
|
||||
return std::make_pair(report, COUNTED_ERROR(AttestationSEV_CryptoAllocate));
|
||||
}
|
||||
|
||||
// Verify VCEK.
|
||||
@ -319,11 +306,11 @@ std::pair<attestation::AttestationData, error::Error> DataFromVerifiedAttestatio
|
||||
bssl::UniquePtr<STACK_OF(X509)> intermediates(sk_X509_new_null());
|
||||
if (!ctx || !store || !intermediates ||
|
||||
0 == sk_X509_push(intermediates.get(), ask.get())) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_CryptoAllocate));
|
||||
return std::make_pair(report, COUNTED_ERROR(AttestationSEV_CryptoAllocate));
|
||||
}
|
||||
ask.release(); // now owned by [intermediates]
|
||||
if (!X509_STORE_CTX_init(ctx.get(), store.get(), vcek.get(), intermediates.get())) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_CryptoStoreInit));
|
||||
return std::make_pair(report, COUNTED_ERROR(AttestationSEV_CryptoStoreInit));
|
||||
}
|
||||
// X509_STORE_CTX_set0_trusted_stack does not take ownership of roots_of_trust stack.
|
||||
X509_STORE_CTX_set0_trusted_stack(ctx.get(), roots_of_trust.get());
|
||||
@ -332,18 +319,18 @@ std::pair<attestation::AttestationData, error::Error> DataFromVerifiedAttestatio
|
||||
if (1 != X509_verify_cert(ctx.get())) {
|
||||
auto err = X509_STORE_CTX_get_error(ctx.get());
|
||||
LOG(ERROR) << "SEV attestation verify_cert err=" << err << ": " << X509_verify_cert_error_string(err);
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_CertificateChainVerify));
|
||||
return std::make_pair(report, COUNTED_ERROR(AttestationSEV_CertificateChainVerify));
|
||||
}
|
||||
|
||||
// Extract ECDSA signature from report.
|
||||
bssl::UniquePtr<ECDSA_SIG> sig(ECDSA_SIG_new());
|
||||
bssl::UniquePtr<BIGNUM> r(BN_le2bn(report->signature.r, 48, nullptr));
|
||||
bssl::UniquePtr<BIGNUM> s(BN_le2bn(report->signature.s, 48, nullptr));
|
||||
bssl::UniquePtr<BIGNUM> r(BN_le2bn(report.signature.r, 48, nullptr));
|
||||
bssl::UniquePtr<BIGNUM> s(BN_le2bn(report.signature.s, 48, nullptr));
|
||||
if (!sig || !r || !s) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_CryptoAllocate));
|
||||
return std::make_pair(report, COUNTED_ERROR(AttestationSEV_CryptoAllocate));
|
||||
}
|
||||
if (1 != ECDSA_SIG_set0(sig.get(), r.get(), s.get())) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_CryptoAllocate));
|
||||
return std::make_pair(report, COUNTED_ERROR(AttestationSEV_CryptoAllocate));
|
||||
}
|
||||
r.release(); // now owned by sig
|
||||
s.release(); // now owned by sig
|
||||
@ -351,32 +338,76 @@ std::pair<attestation::AttestationData, error::Error> DataFromVerifiedAttestatio
|
||||
// Compute message digest.
|
||||
bssl::UniquePtr<EVP_MD_CTX> md_ctx(EVP_MD_CTX_new());
|
||||
if (!md_ctx) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_CryptoAllocate));
|
||||
return std::make_pair(report, COUNTED_ERROR(AttestationSEV_CryptoAllocate));
|
||||
}
|
||||
EVP_MD_CTX_init(md_ctx.get());
|
||||
uint8_t md[48];
|
||||
unsigned int md_size = sizeof(md);
|
||||
auto verify_from = reinterpret_cast<const uint8_t*>(report);
|
||||
auto verify_to = reinterpret_cast<const uint8_t*>(&report->signature);
|
||||
auto verify_from = reinterpret_cast<const uint8_t*>(&report);
|
||||
auto verify_to = reinterpret_cast<const uint8_t*>(&report.signature);
|
||||
if (1 != EVP_DigestInit(md_ctx.get(), EVP_sha384()) ||
|
||||
1 != EVP_DigestUpdate(md_ctx.get(), verify_from, verify_to - verify_from) ||
|
||||
1 != EVP_DigestFinal(md_ctx.get(), md, &md_size) ||
|
||||
md_size != sizeof(md)) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_CryptoMessageDigest));
|
||||
return std::make_pair(report, COUNTED_ERROR(AttestationSEV_CryptoMessageDigest));
|
||||
}
|
||||
|
||||
// Use VCEK to verify signature.
|
||||
EC_KEY* ec_key_not_owned = EVP_PKEY_get0_EC_KEY(vcek_pub.get());
|
||||
if (1 != ECDSA_do_verify(md, md_size, sig.get(), ec_key_not_owned)) {
|
||||
LOG(ERROR) << "SEV attestation signature verification failed";
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_SignatureVerify));
|
||||
return std::make_pair(report, COUNTED_ERROR(AttestationSEV_SignatureVerify));
|
||||
}
|
||||
|
||||
return std::make_pair(report, error::OK);
|
||||
}
|
||||
|
||||
std::pair<attestation::AttestationData, error::Error> DataFromVerifiedAttestation(const attestation_report& local, const e2e::Attestation& attestation, util::UnixSecs now) {
|
||||
attestation::AttestationData out;
|
||||
SevSnpEndorsements endorsements;
|
||||
if (!endorsements.ParseFromString(attestation.endorsements())) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AttestationSEV_ParseEndorsements));
|
||||
}
|
||||
auto [report, err] = ReportFromVerifiedBuffer(attestation.evidence(), endorsements, now);
|
||||
if (err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
}
|
||||
if (auto err = AllowRemote(local, report); err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
}
|
||||
|
||||
out.mutable_public_key()->resize(sizeof(env::PublicKey));
|
||||
memcpy(out.mutable_public_key()->data(), report->report_data, sizeof(env::PublicKey));
|
||||
minimums::MinimumValues mins = minimums::Minimums::CombineValues(out.minimum_values(), MinimumsFromReport(*report));
|
||||
memcpy(out.mutable_public_key()->data(), report.report_data, sizeof(env::PublicKey));
|
||||
minimums::MinimumValues mins = minimums::Minimums::CombineValues(out.minimum_values(), MinimumsFromReport(report));
|
||||
*out.mutable_minimum_values() = std::move(mins);
|
||||
return std::make_pair(out, error::OK);
|
||||
}
|
||||
|
||||
} // namespace svr2::attestation::sev
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const ::svr2::attestation::sev::attestation_report& report) {
|
||||
os << "SEV REPORT{"
|
||||
<< " version:" << ::svr2::util::ValueToHex(report.version)
|
||||
<< " guest_svn:" << ::svr2::util::ValueToHex(report.guest_svn)
|
||||
<< " policy:" << ::svr2::util::ValueToHex(report.policy)
|
||||
<< " family_id:" << ::svr2::util::BytesToHex(report.family_id, sizeof(report.family_id))
|
||||
<< " image_id:" << ::svr2::util::BytesToHex(report.image_id, sizeof(report.image_id))
|
||||
<< " vmpl:" << ::svr2::util::ValueToHex(report.vmpl)
|
||||
<< " signature_algo:" << ::svr2::util::ValueToHex(report.signature_algo)
|
||||
<< " platform_version:" << ::svr2::util::ValueToHex(report.platform_version.raw)
|
||||
<< " platform_info:" << ::svr2::util::ValueToHex(report.platform_info)
|
||||
<< " flags:" << ::svr2::util::ValueToHex(report.flags)
|
||||
<< " report_data:" << ::svr2::util::BytesToHex(report.report_data, sizeof(report.report_data))
|
||||
<< " measurement:" << ::svr2::util::BytesToHex(report.measurement, sizeof(report.measurement))
|
||||
<< " host_data:" << ::svr2::util::BytesToHex(report.host_data, sizeof(report.host_data))
|
||||
<< " id_key_digest:" << ::svr2::util::BytesToHex(report.id_key_digest, sizeof(report.id_key_digest))
|
||||
<< " author_key_digest:" << ::svr2::util::BytesToHex(report.author_key_digest, sizeof(report.author_key_digest))
|
||||
<< " report_id:" << ::svr2::util::BytesToHex(report.report_id, sizeof(report.report_id))
|
||||
<< " report_id_ma:" << ::svr2::util::BytesToHex(report.report_id_ma, sizeof(report.report_id_ma))
|
||||
<< " reported_tcb:" << ::svr2::util::ValueToHex(report.reported_tcb.raw)
|
||||
<< " chip_id:" << ::svr2::util::BytesToHex(report.chip_id, sizeof(report.chip_id))
|
||||
<< " signature.r:" << ::svr2::util::BytesToHex(report.signature.r, sizeof(report.signature.r))
|
||||
<< " signature.s:" << ::svr2::util::BytesToHex(report.signature.s, sizeof(report.signature.s))
|
||||
<< " }";
|
||||
return os;
|
||||
}
|
||||
|
||||
@ -28,6 +28,8 @@ error::Error CertificatesToEndorsements(const uint8_t* certs, uint32_t certs_siz
|
||||
// without doing any crypto verification.
|
||||
std::pair<attestation_report, error::Error> ReportFromUnverifiedAttestation(const e2e::Attestation& attestation);
|
||||
|
||||
std::pair<attestation_report, error::Error> ReportFromVerifiedBuffer(const std::string& buffer, const SevSnpEndorsements& endorsements, util::UnixSecs now);
|
||||
|
||||
// Pulls a public key from the given attestation, verifying that attestation
|
||||
// as much as possible in the process. Will return an error if the attestation
|
||||
// is invalid, does not match the given local attestation in the necessary ways,
|
||||
@ -38,4 +40,6 @@ minimums::MinimumValues MinimumsFromReport(const attestation_report& report);
|
||||
|
||||
} // namespace svr2::attestation::sev
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const ::svr2::attestation::sev::attestation_report& report);
|
||||
|
||||
#endif // __SVR2_ATTESTATION_SEV_SEV_H__
|
||||
|
||||
@ -75,7 +75,7 @@ TEST_F(AttestSEVTest, SigningPastTimestamp) {
|
||||
TEST_F(AttestSEVTest, InvalidASKSignature) {
|
||||
SevSnpEndorsements sse;
|
||||
ASSERT_TRUE(sse.ParseFromString(attestation_.endorsements()));
|
||||
(*sse.mutable_ask())[sse.ask().size() - 1] ^= 1; // mess up signature on ASK
|
||||
(*sse.mutable_ask_der())[sse.ask_der().size() - 1] ^= 1; // mess up signature on ASK
|
||||
ASSERT_TRUE(sse.SerializeToString(attestation_.mutable_endorsements()));
|
||||
auto [report, err1] = ReportFromUnverifiedAttestation(attestation_);
|
||||
ASSERT_EQ(error::OK, err1);
|
||||
@ -86,7 +86,7 @@ TEST_F(AttestSEVTest, InvalidASKSignature) {
|
||||
TEST_F(AttestSEVTest, InvalidVCEKSignature) {
|
||||
SevSnpEndorsements sse;
|
||||
ASSERT_TRUE(sse.ParseFromString(attestation_.endorsements()));
|
||||
(*sse.mutable_vcek())[sse.vcek().size() - 1] ^= 1; // mess up signature on VCEK
|
||||
(*sse.mutable_vcek_der())[sse.vcek_der().size() - 1] ^= 1; // mess up signature on VCEK
|
||||
ASSERT_TRUE(sse.SerializeToString(attestation_.mutable_endorsements()));
|
||||
auto [report, err1] = ReportFromUnverifiedAttestation(attestation_);
|
||||
ASSERT_EQ(error::OK, err1);
|
||||
|
||||
335
enclave/env/azuresnp/azuresnp.cc
vendored
Normal file
335
enclave/env/azuresnp/azuresnp.cc
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/random.h>
|
||||
#include <sstream>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <fts.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "attestation/sev/sev.h"
|
||||
#include "attestation/tpm2/tpm2.h"
|
||||
#include "env/env.h"
|
||||
#include "env/socket/socket.h"
|
||||
#include "sevtypes/sevtypes.h"
|
||||
#include "util/macros.h"
|
||||
#include "util/constant.h"
|
||||
#include "context/context.h"
|
||||
#include "socketwrap/socket.h"
|
||||
#include "proto/socketmain.pb.h"
|
||||
#include "proto/sev.pb.h"
|
||||
#include "proto/azuresnp.pb.h"
|
||||
#include "queue/queue.h"
|
||||
#include "util/bytes.h"
|
||||
#include "util/hex.h"
|
||||
#include "util/mutex.h"
|
||||
#include "util/endian.h"
|
||||
#include "util/log.h"
|
||||
#include "attestation/azuresnp/azuresnp.h"
|
||||
#include "hmac/hmac.h"
|
||||
|
||||
namespace svr2::env {
|
||||
namespace azuresnp {
|
||||
namespace {
|
||||
|
||||
class Environment : public ::svr2::env::socket::Environment {
|
||||
public:
|
||||
DELETE_COPY_AND_ASSIGN(Environment);
|
||||
Environment(bool simulated) : ::svr2::env::socket::Environment(simulated) {}
|
||||
virtual ~Environment() {
|
||||
}
|
||||
// Attestation.evidence will be a serialization of attestation::azuresnp::ASNPEvidence.
|
||||
// Attestation.endorsements will be a serialization of attestation::azuresnp::ASNPEndorsements.
|
||||
virtual std::pair<e2e::Attestation, error::Error> Evidence(
|
||||
context::Context* ctx,
|
||||
const attestation::AttestationData& data) const {
|
||||
if (simulated()) {
|
||||
return SimulatedEvidence(ctx, data);
|
||||
}
|
||||
e2e::Attestation out;
|
||||
auto evidence = ctx->Protobuf<attestation::azuresnp::ASNPEvidence>();
|
||||
if (auto err = CurrentEvidence(ctx, data, evidence); err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
}
|
||||
if (!evidence->SerializeToString(out.mutable_evidence())) {
|
||||
LOG(ERROR) << "Failed to serialize evidence";
|
||||
return std::make_pair(std::move(out), COUNTED_ERROR(Env_GetEvidence));
|
||||
}
|
||||
if (!endorsements_.SerializeToString(out.mutable_endorsements())) {
|
||||
LOG(ERROR) << "Failed to serialize endorsements";
|
||||
return std::make_pair(std::move(out), COUNTED_ERROR(Env_GetEvidence));
|
||||
}
|
||||
return std::make_pair(std::move(out), error::OK);
|
||||
}
|
||||
|
||||
// Given evidence and endorsements, extract the key.
|
||||
virtual std::pair<attestation::AttestationData, error::Error> Attest(
|
||||
context::Context* ctx,
|
||||
util::UnixSecs now,
|
||||
const e2e::Attestation& attestation) const {
|
||||
if (simulated()) {
|
||||
return SimulatedAttest(ctx, now, attestation);
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Parsing out azuresnp-specific data from AttestationData";
|
||||
attestation::AttestationData out;
|
||||
auto evidence = ctx->Protobuf<attestation::azuresnp::ASNPEvidence>();
|
||||
auto endorsements = ctx->Protobuf<attestation::azuresnp::ASNPEndorsements>();
|
||||
if (!evidence->ParseFromString(attestation.evidence())) {
|
||||
return std::make_pair(out, COUNTED_ERROR(Env_ParseEvidence));
|
||||
}
|
||||
if (!endorsements->ParseFromString(attestation.endorsements())) {
|
||||
return std::make_pair(out, COUNTED_ERROR(Env_ParseEndorsements));
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Verifing that the provided AKCert is valid and verified by the SNP report and MSFT root of trust";
|
||||
if (auto err = attestation::azuresnp::VerifyAKCert(ctx, *evidence, *endorsements, now); err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Verifing TPM2 quote";
|
||||
auto [sig, sigerr] = attestation::tpm2::Signature::FromString(evidence->sig());
|
||||
if (sigerr != error::OK) {
|
||||
return std::make_pair(out, sigerr);
|
||||
}
|
||||
auto [msg, msgerr] = attestation::tpm2::Report::FromString(evidence->msg());
|
||||
if (msgerr != error::OK) {
|
||||
return std::make_pair(out, msgerr);
|
||||
}
|
||||
attestation::tpm2::PCRs pcrs;
|
||||
if (auto err = attestation::tpm2::PCRsFromString(evidence->pcrs(), &pcrs); err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
}
|
||||
auto akcert_start = reinterpret_cast<const uint8_t*>(evidence->akcert_der().data());
|
||||
bssl::UniquePtr<X509> akcert(d2i_X509(nullptr, &akcert_start, evidence->akcert_der().size()));
|
||||
if (!akcert) {
|
||||
return std::make_pair(out, COUNTED_ERROR(Env_ParseEvidence));
|
||||
} else if (auto err = sig.VerifyReport(msg, akcert.get()); err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
} else if (auto err = msg.VerifyPCRs(pcrs); err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Verifying remote PCRs against local ones";
|
||||
if (auto err = attestation::azuresnp::CheckRemotePCRs(ctx, local_pcrs_, pcrs); err != error::OK) {
|
||||
return std::make_pair(out, err);
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "Verifying that attestation data matches hash in TPM2 quote";
|
||||
if (auto ad_sha256 = hmac::Sha256(evidence->attestation_data()); !util::ConstantTimeEquals(ad_sha256, msg.nonce())) {
|
||||
return std::make_pair(out, COUNTED_ERROR(AzureSNP_AttestationDataHashMismatch));
|
||||
}
|
||||
|
||||
if (!out.ParseFromString(evidence->attestation_data())) {
|
||||
return std::make_pair(std::move(out), COUNTED_ERROR(Env_ParseEvidence));
|
||||
}
|
||||
return std::make_pair(std::move(out), error::OK);
|
||||
}
|
||||
|
||||
virtual error::Error UpdateEnvStats() const {
|
||||
return error::General_Unimplemented;
|
||||
}
|
||||
|
||||
virtual void Init() {
|
||||
::svr2::env::socket::Environment::Init();
|
||||
if (simulated()) { return; }
|
||||
auto [dir, direrr] = TempDir();
|
||||
if (direrr != error::OK) { LOG(FATAL) << "Creating temporary directory: " << direrr; }
|
||||
DirCleanup dc(dir);
|
||||
|
||||
// Gather some information from the system by running various commands,
|
||||
// and apply it to `evidence_` and `endorsements_`. These are all
|
||||
// information that will not change through the lifetime of the process.
|
||||
for (std::string cmd : {
|
||||
// Read in Azure-specific report (containing SNP report and runtime data)
|
||||
"/usr/bin/tpm2_nvread -C o 0x01400001 --output " + dir + "/azure_report.bin",
|
||||
// Read in the TPM2 AK key certificate (key is at 0x81000003, which is used when we tpm2_quote)
|
||||
"/usr/bin/tpm2_nvread -C o 0x1C101D0 -o " + dir + "/akcert.der",
|
||||
// Read in intermediate cert between TPM2 AK cert and MSFT root-of-trust (which we compile in).
|
||||
// TODO: grab the intermediate cert locally, rather than from MSFT. The plan to do this
|
||||
// is to serve them from a local nginx service within the replica's region
|
||||
"/usr/bin/curl --silent 'https://learn.microsoft.com/en-us/azure/virtual-machines/trusted-launch-faq?tabs=cli%2Cdebianbased#certificates' | grep -A32 'intermediate CA' | grep -v '<' > " + dir + "/intermediate.pem",
|
||||
// Grab instance-specific VCEK and chain.
|
||||
"/usr/bin/curl --silent -H Metadata:true http://169.254.169.254/metadata/THIM/amd/certification -o " + dir + "/vcek",
|
||||
"/usr/bin/jq -r .vcekCert " + dir + "/vcek > " + dir + "/vcek.pem",
|
||||
"/usr/bin/jq -r .certificateChain " + dir + "/vcek > " + dir + "/vcek_chain.pem",
|
||||
// Convert VCEK certificate's PEM to DER
|
||||
"/usr/bin/openssl x509 -in " + dir + "/vcek.pem -inform PEM -out " + dir + "/vcek.der -outform DER",
|
||||
// Convert VCEK_CHAIN first certificate's PEM to DER. The first cert in the chain is the ASK, which is what we want.
|
||||
"/usr/bin/openssl x509 -in " + dir + "/vcek_chain.pem -inform PEM -out " + dir + "/ask.der -outform DER",
|
||||
// Convert the intermediate certificate's PEM to DER
|
||||
"/usr/bin/openssl x509 -in " + dir + "/intermediate.pem -inform PEM -out " + dir + "/intermediate.der -outform DER",
|
||||
}) {
|
||||
LOG(INFO) << "Running command: " << cmd;
|
||||
int ret = system(cmd.c_str());
|
||||
if (ret != 0) {
|
||||
LOG(FATAL) << "Command failed. Return value: " << ret;
|
||||
}
|
||||
}
|
||||
auto [azure_report, err1] = FileContents(dir + "/azure_report.bin");
|
||||
if (err1 != error::OK) { LOG(FATAL) << "File read failed"; }
|
||||
auto [akcert_der, err2] = FileContents(dir + "/akcert.der");
|
||||
if (err2 != error::OK) { LOG(FATAL) << "File read failed"; }
|
||||
auto [intermediate_der, err3] = FileContents(dir + "/intermediate.der");
|
||||
if (err3 != error::OK) { LOG(FATAL) << "File read failed"; }
|
||||
auto [vcek_der, err4] = FileContents(dir + "/vcek.der");
|
||||
if (err4 != error::OK) { LOG(FATAL) << "File read failed"; }
|
||||
auto [ask_der, err5] = FileContents(dir + "/ask.der");
|
||||
if (err5 != error::OK) { LOG(FATAL) << "File read failed"; }
|
||||
|
||||
evidence_.set_azure_report(azure_report);
|
||||
evidence_.set_akcert_der(akcert_der);
|
||||
endorsements_.set_intermediate_der(intermediate_der);
|
||||
endorsements_.set_vcek_der(vcek_der);
|
||||
endorsements_.set_ask_der(ask_der);
|
||||
|
||||
context::Context ctx;
|
||||
auto attestation_data = ctx.Protobuf<attestation::AttestationData>();
|
||||
auto tmp_evidence = ctx.Protobuf<attestation::azuresnp::ASNPEvidence>();
|
||||
if (auto err = CurrentEvidence(&ctx, *attestation_data, tmp_evidence)) {
|
||||
LOG(FATAL) << "Getting current evidence in Init: " << err;
|
||||
} else if (auto err = attestation::tpm2::PCRsFromString(tmp_evidence->pcrs(), &local_pcrs_)) {
|
||||
LOG(FATAL) << "Loading local PCRs: " << err;
|
||||
}
|
||||
for (size_t i = 0; i < local_pcrs_.size(); i++) {
|
||||
LOG(DEBUG) << "PCRS[" << i << "]: " << util::ToHex(local_pcrs_[i]);
|
||||
}
|
||||
|
||||
if (auto [attestation, err] = Evidence(&ctx, *attestation_data); err != error::OK) {
|
||||
LOG(FATAL) << "Failure to get evidence in Init: " << err;
|
||||
} else if (auto [data, err] = Attest(&ctx, time(nullptr), attestation); err != error::OK) {
|
||||
LOG(FATAL) << "Failure to attest evidence in Init: " << err;
|
||||
}
|
||||
LOG(INFO) << "Successfully retrieved and attested evidence";
|
||||
}
|
||||
|
||||
private:
|
||||
class DirCleanup {
|
||||
public:
|
||||
DirCleanup(const std::string& name) : name_(name) {}
|
||||
~DirCleanup() {
|
||||
LOG(DEBUG) << "Recursively deleting directory " << name_;
|
||||
const char* files[] = {name_.c_str(), nullptr};
|
||||
FTS* fts = fts_open(const_cast<char *const *>(files), FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
|
||||
if (!fts) {
|
||||
LOG(ERROR) << "Error recursively deleting '" << name_ << "'";
|
||||
return;
|
||||
}
|
||||
FTSENT* curr;
|
||||
while (nullptr != (curr = fts_read(fts))) {
|
||||
switch (curr->fts_info) {
|
||||
case FTS_D: // directory, in pre-order
|
||||
break;
|
||||
case FTS_DP: // directory, in post-order
|
||||
case FTS_F: // normal file
|
||||
LOG(DEBUG) << " rm " << curr->fts_accpath;
|
||||
if (int ret = remove(curr->fts_accpath); ret != 0) {
|
||||
LOG(ERROR) << "Error deleting file '" << curr->fts_accpath << "' in temp directory '" << name_ << "': " << strerror(errno);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR) << "Unable to handle deletion of file '" << curr->fts_accpath << "' in temp directory '" << name_ << "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
std::pair<std::string, error::Error> TempDir() const {
|
||||
std::array<uint8_t, 8> bytes;
|
||||
if (auto err = RandomBytes(bytes.data(), bytes.size()); err != error::OK) {
|
||||
return std::make_pair("", err);
|
||||
}
|
||||
std::string name = "/tmp/svr." + util::ToHex(bytes);
|
||||
if (int ret = mkdir(name.c_str(), 0700); ret != 0) {
|
||||
LOG(ERROR) << "Making temp directory failed: " << strerror(errno);
|
||||
return std::make_pair("", COUNTED_ERROR(AzureSNP_Mkdir));
|
||||
}
|
||||
LOG(DEBUG) << "New temp directory: " << name;
|
||||
return std::make_pair(name, error::OK);
|
||||
}
|
||||
|
||||
std::pair<std::string, error::Error> FileContents(const std::string& filename) const {
|
||||
int fd = open(filename.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (fd <= 0) {
|
||||
LOG(ERROR) << "Opening file '" << filename << "' for read: " << strerror(errno);
|
||||
return std::make_pair("", COUNTED_ERROR(AzureSNP_OpenFile));
|
||||
}
|
||||
char buf[64];
|
||||
ssize_t ret = -1;
|
||||
std::string out;
|
||||
while (0 < (ret = read(fd, buf, sizeof(buf)))) {
|
||||
out.append(buf, static_cast<size_t>(ret));
|
||||
}
|
||||
if (ret < 0) {
|
||||
LOG(ERROR) << "Reading file '" << filename << "': " << strerror(errno);
|
||||
close(fd);
|
||||
return std::make_pair("", COUNTED_ERROR(AzureSNP_OpenFile));
|
||||
}
|
||||
close(fd);
|
||||
return std::make_pair(std::move(out), error::OK);
|
||||
}
|
||||
|
||||
error::Error CurrentEvidence(context::Context* ctx, const attestation::AttestationData& data, attestation::azuresnp::ASNPEvidence* evidence) const {
|
||||
std::string serialized = data.SerializeAsString();
|
||||
auto attestation_data_sha256 = hmac::Sha256(serialized);
|
||||
|
||||
auto [dir, direrr] = TempDir();
|
||||
RETURN_IF_ERROR(direrr);
|
||||
DirCleanup dc(dir);
|
||||
|
||||
std::string cmd = (
|
||||
"/usr/bin/tpm2_quote"
|
||||
// Quote using the TPM's AK key
|
||||
" -c 0x81000003"
|
||||
// Add the SHA256 of the attestation data's serialized form into the quote
|
||||
" -q " + util::ToHex(attestation_data_sha256) +
|
||||
// Output msg, sig, and PCRs to our temporary directory
|
||||
" -m " + dir + "/msg"
|
||||
" -s " + dir + "/sig"
|
||||
" -o " + dir + "/pcrs"
|
||||
// Output all PCRs as concatenated SHA256 (24 PCRs * 32 bytes = 768 bytes total)
|
||||
" -l sha256:all"
|
||||
" --pcrs_format values"
|
||||
// Ignore STDOUT
|
||||
" >/dev/null");
|
||||
LOG(DEBUG) << "Running command: " << cmd;
|
||||
if (int ret = system(cmd.c_str()); ret != 0) {
|
||||
LOG(ERROR) << "Command to get attestation data (" << cmd << ") failed: " << ret;
|
||||
return COUNTED_ERROR(Env_GetEvidence);
|
||||
}
|
||||
auto [msg, err1] = FileContents(dir + "/msg");
|
||||
RETURN_IF_ERROR(err1);
|
||||
auto [sig, err2] = FileContents(dir + "/sig");
|
||||
RETURN_IF_ERROR(err2);
|
||||
auto [pcrs, err3] = FileContents(dir + "/pcrs");
|
||||
RETURN_IF_ERROR(err3);
|
||||
|
||||
evidence->MergeFrom(evidence_);
|
||||
evidence->set_attestation_data(serialized);
|
||||
evidence->set_pcrs(pcrs);
|
||||
evidence->set_msg(msg);
|
||||
evidence->set_sig(sig);
|
||||
return error::OK;
|
||||
}
|
||||
|
||||
attestation::azuresnp::ASNPEvidence evidence_;
|
||||
attestation::azuresnp::ASNPEndorsements endorsements_;
|
||||
attestation::tpm2::PCRs local_pcrs_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace azuresnp
|
||||
|
||||
void Init(bool is_simulated) {
|
||||
environment = std::make_unique<::svr2::env::azuresnp::Environment>(is_simulated);
|
||||
environment->Init();
|
||||
}
|
||||
|
||||
} // namespace svr2::env
|
||||
29
enclave/env/nsm/nsm.cc
vendored
29
enclave/env/nsm/nsm.cc
vendored
@ -23,12 +23,10 @@ namespace svr2::env {
|
||||
namespace nsm {
|
||||
namespace {
|
||||
|
||||
static const char* SIMULATED_REPORT_PREFIX = "NITRO_SIMULATED_REPORT:";
|
||||
|
||||
class Environment : public ::svr2::env::socket::Environment {
|
||||
public:
|
||||
DELETE_COPY_AND_ASSIGN(Environment);
|
||||
Environment(bool simulated) : nsm_fd_(0), simulated_(simulated) {
|
||||
Environment(bool simulated) : ::svr2::env::socket::Environment(simulated), nsm_fd_(0) {
|
||||
nsm_fd_ = nsm_lib_init();
|
||||
}
|
||||
virtual ~Environment() {
|
||||
@ -38,11 +36,10 @@ class Environment : public ::svr2::env::socket::Environment {
|
||||
context::Context* ctx,
|
||||
const attestation::AttestationData& data) const {
|
||||
MEASURE_CPU(ctx, cpu_env_evidence);
|
||||
e2e::Attestation out;
|
||||
if (simulated_) {
|
||||
out.set_evidence(SIMULATED_REPORT_PREFIX + data.SerializeAsString());
|
||||
return std::make_pair(out, error::OK);
|
||||
if (simulated()) {
|
||||
return SimulatedEvidence(ctx, data);
|
||||
}
|
||||
e2e::Attestation out;
|
||||
out.mutable_evidence()->resize(102400);
|
||||
uint32_t evidence_len = out.evidence().size();
|
||||
std::string data_serialized;
|
||||
@ -71,17 +68,10 @@ class Environment : public ::svr2::env::socket::Environment {
|
||||
util::UnixSecs now,
|
||||
const e2e::Attestation& attestation) const {
|
||||
MEASURE_CPU(ctx, cpu_env_attest);
|
||||
attestation::AttestationData out;
|
||||
if (simulated_) {
|
||||
if (attestation.evidence().rfind(SIMULATED_REPORT_PREFIX, 0) != 0) {
|
||||
return std::make_pair(out, error::Env_AttestationFailure);
|
||||
} else if (!out.ParseFromArray(
|
||||
attestation.evidence().data() + strlen(SIMULATED_REPORT_PREFIX),
|
||||
attestation.evidence().size() - strlen(SIMULATED_REPORT_PREFIX))) {
|
||||
return std::make_pair(out, COUNTED_ERROR(Env_AttestationFailure));
|
||||
}
|
||||
return std::make_pair(out, error::OK);
|
||||
if (simulated()) {
|
||||
return SimulatedAttest(ctx, now, attestation);
|
||||
}
|
||||
attestation::AttestationData out;
|
||||
attestation::nitro::CoseSign1 cose_sign_1;
|
||||
attestation::nitro::AttestationDoc attestation_doc;
|
||||
|
||||
@ -103,7 +93,7 @@ class Environment : public ::svr2::env::socket::Environment {
|
||||
// Given a string of size N, rewrite all bytes in that string with
|
||||
// random bytes.
|
||||
virtual error::Error RandomBytes(void* bytes, size_t size) const {
|
||||
if (simulated_) {
|
||||
if (simulated()) {
|
||||
while (size) {
|
||||
size_t got = getrandom(bytes, size, 0);
|
||||
if (got <= 0) { return error::Env_RandomBytes; }
|
||||
@ -130,7 +120,7 @@ class Environment : public ::svr2::env::socket::Environment {
|
||||
|
||||
virtual void Init() {
|
||||
::svr2::env::socket::Environment::Init();
|
||||
if (simulated_) return;
|
||||
if (simulated()) return;
|
||||
// Get an initial set of evidence to pull our PCRs from.
|
||||
attestation::AttestationData data;
|
||||
data.mutable_public_key()->resize(sizeof(env::PublicKey));
|
||||
@ -176,7 +166,6 @@ class Environment : public ::svr2::env::socket::Environment {
|
||||
}
|
||||
|
||||
int32_t nsm_fd_;
|
||||
bool simulated_;
|
||||
std::map<int, attestation::nitro::ByteString> pcrs_;
|
||||
};
|
||||
|
||||
|
||||
94
enclave/env/sev/sev.cc
vendored
94
enclave/env/sev/sev.cc
vendored
@ -26,17 +26,16 @@
|
||||
#include "util/mutex.h"
|
||||
#include "util/endian.h"
|
||||
#include "util/log.h"
|
||||
#include "hmac/hmac.h"
|
||||
|
||||
namespace svr2::env {
|
||||
namespace sev {
|
||||
namespace {
|
||||
|
||||
static const char* SIMULATED_REPORT_PREFIX = "SEV_SIMULATED_REPORT:";
|
||||
|
||||
class Environment : public ::svr2::env::socket::Environment {
|
||||
public:
|
||||
DELETE_COPY_AND_ASSIGN(Environment);
|
||||
Environment(bool simulated) : sev_fd_(0), simulated_(simulated) {
|
||||
Environment(bool simulated) : ::svr2::env::socket::Environment(simulated), sev_fd_(0) {
|
||||
for (const char* devname : {"/dev/sev-guest", "/dev/sev"}) {
|
||||
sev_fd_ = open(devname, O_RDWR | O_CLOEXEC);
|
||||
if (sev_fd_ > 0) return;
|
||||
@ -59,12 +58,12 @@ class Environment : public ::svr2::env::socket::Environment {
|
||||
context::Context* ctx,
|
||||
const attestation::AttestationData& data) const {
|
||||
MEASURE_CPU(ctx, cpu_env_evidence);
|
||||
if (simulated()) {
|
||||
return SimulatedEvidence(ctx, data);
|
||||
}
|
||||
|
||||
e2e::Attestation out;
|
||||
std::string serialized = data.SerializeAsString();
|
||||
if (simulated_) {
|
||||
out.set_evidence(SIMULATED_REPORT_PREFIX + serialized);
|
||||
return std::make_pair(out, error::OK);
|
||||
}
|
||||
|
||||
snp_ext_report_req req;
|
||||
snp_report_resp resp;
|
||||
@ -78,11 +77,9 @@ class Environment : public ::svr2::env::socket::Environment {
|
||||
req.certs_address = reinterpret_cast<__u64>(&certs[0]);
|
||||
req.certs_len = sizeof(certs);
|
||||
|
||||
CHECK(sizeof(req.data.user_data) >= crypto_hash_sha256_BYTES);
|
||||
crypto_hash_sha256_state sha256;
|
||||
crypto_hash_sha256_init(&sha256);
|
||||
crypto_hash_sha256_update(&sha256, reinterpret_cast<const uint8_t*>(serialized.data()), serialized.size());
|
||||
crypto_hash_sha256_final(&sha256, req.data.user_data);
|
||||
CHECK(sizeof(req.data.user_data) >= hmac::Sha256SumBytes);
|
||||
auto hash = hmac::Sha256(serialized);
|
||||
memcpy(req.data.user_data, hash.data(), hash.size());
|
||||
|
||||
guest_req.msg_version = 1;
|
||||
guest_req.req_data = reinterpret_cast<__u64>(&req);
|
||||
@ -128,62 +125,19 @@ class Environment : public ::svr2::env::socket::Environment {
|
||||
util::UnixSecs now,
|
||||
const e2e::Attestation& attestation) const {
|
||||
MEASURE_CPU(ctx, cpu_env_attest);
|
||||
attestation::AttestationData out;
|
||||
if (simulated_) {
|
||||
if (attestation.evidence().rfind(SIMULATED_REPORT_PREFIX, 0) != 0) {
|
||||
return std::make_pair(out, error::Env_AttestationFailure);
|
||||
}
|
||||
size_t prefix_len = strlen(SIMULATED_REPORT_PREFIX);
|
||||
if (!out.ParseFromArray(attestation.evidence().data() + prefix_len, attestation.evidence().size() - prefix_len)) {
|
||||
return std::make_pair(out, error::Env_AttestationFailure);
|
||||
}
|
||||
return std::make_pair(out, error::OK);
|
||||
if (simulated()) {
|
||||
return SimulatedAttest(ctx, now, attestation);
|
||||
}
|
||||
return attestation::sev::DataFromVerifiedAttestation(report_, attestation, now);
|
||||
}
|
||||
|
||||
// Given a string of size N, rewrite all bytes in that string with
|
||||
// random bytes.
|
||||
virtual error::Error RandomBytes(void* bytes, size_t size) const {
|
||||
uint8_t* u8ptr = reinterpret_cast<uint8_t*>(bytes);
|
||||
if (simulated_) {
|
||||
while (size) {
|
||||
auto out = getrandom(u8ptr, size, 0);
|
||||
if (out < 0) {
|
||||
return error::Env_RandomBytes;
|
||||
}
|
||||
size -= out;
|
||||
u8ptr += out;
|
||||
}
|
||||
} else {
|
||||
// This may be slow but uses direct CPU instructions to get randomness.
|
||||
// We're not sure we can trust syscalls, as they might be sent up
|
||||
// to the hypervisor or the host OS, both of which we may not have fully
|
||||
// verified.
|
||||
unsigned long long r;
|
||||
uint8_t buf[8];
|
||||
CHECK(sizeof(r) == sizeof(buf));
|
||||
while (size) {
|
||||
if (1 != __builtin_ia32_rdrand64_step(&r)) {
|
||||
return error::Env_RandomBytes;
|
||||
}
|
||||
util::BigEndian64Bytes(r, buf);
|
||||
for (size_t i = 0; i < sizeof(buf) && size; i++) {
|
||||
*u8ptr++ = buf[i];
|
||||
size--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return error::OK;
|
||||
}
|
||||
|
||||
virtual error::Error UpdateEnvStats() const {
|
||||
return error::General_Unimplemented;
|
||||
}
|
||||
|
||||
virtual void Init() {
|
||||
::svr2::env::Environment::Init();
|
||||
if (simulated_) return;
|
||||
if (simulated()) return;
|
||||
|
||||
const char* base_endorsements_file = "endorsements.pb";
|
||||
if (attestation::sev::EndorsementsFromFile(base_endorsements_file, &base_endorsements_)) {
|
||||
@ -198,33 +152,11 @@ class Environment : public ::svr2::env::socket::Environment {
|
||||
auto [report, err2] = attestation::sev::ReportFromUnverifiedAttestation(attest);
|
||||
CHECK(err2 == error::OK);
|
||||
report_ = report;
|
||||
LOG(INFO) << "SEV REPORT:"
|
||||
<< " version:" << util::ValueToHex(report.version)
|
||||
<< " guest_svn:" << util::ValueToHex(report.guest_svn)
|
||||
<< " policy:" << util::ValueToHex(report.policy)
|
||||
<< " family_id:" << util::BytesToHex(report.family_id, sizeof(report.family_id))
|
||||
<< " image_id:" << util::BytesToHex(report.image_id, sizeof(report.image_id))
|
||||
<< " vmpl:" << util::ValueToHex(report.vmpl)
|
||||
<< " signature_algo:" << util::ValueToHex(report.signature_algo)
|
||||
<< " platform_version:" << util::ValueToHex(report.platform_version.raw)
|
||||
<< " platform_info:" << util::ValueToHex(report.platform_info)
|
||||
<< " flags:" << util::ValueToHex(report.flags)
|
||||
<< " report_data:" << util::BytesToHex(report.report_data, sizeof(report.report_data))
|
||||
<< " measurement:" << util::BytesToHex(report.measurement, sizeof(report.measurement))
|
||||
<< " host_data:" << util::BytesToHex(report.host_data, sizeof(report.host_data))
|
||||
<< " id_key_digest:" << util::BytesToHex(report.id_key_digest, sizeof(report.id_key_digest))
|
||||
<< " author_key_digest:" << util::BytesToHex(report.author_key_digest, sizeof(report.author_key_digest))
|
||||
<< " report_id:" << util::BytesToHex(report.report_id, sizeof(report.report_id))
|
||||
<< " report_id_ma:" << util::BytesToHex(report.report_id_ma, sizeof(report.report_id_ma))
|
||||
<< " reported_tcb:" << util::ValueToHex(report.reported_tcb.raw)
|
||||
<< " chip_id:" << util::BytesToHex(report.chip_id, sizeof(report.chip_id))
|
||||
<< " signature.r:" << util::BytesToHex(report.signature.r, sizeof(report.signature.r))
|
||||
<< " signature.s:" << util::BytesToHex(report.signature.s, sizeof(report.signature.s));
|
||||
LOG(INFO) << report;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t sev_fd_;
|
||||
bool simulated_;
|
||||
attestation::sev::SevSnpEndorsements base_endorsements_;
|
||||
attestation::sev::attestation_report report_;
|
||||
};
|
||||
|
||||
60
enclave/env/socket/socket.cc
vendored
60
enclave/env/socket/socket.cc
vendored
@ -8,6 +8,7 @@
|
||||
#include <signal.h>
|
||||
|
||||
#include "util/macros.h"
|
||||
#include "util/endian.h"
|
||||
#include "context/context.h"
|
||||
#include "socketwrap/socket.h"
|
||||
#include "proto/socketmain.pb.h"
|
||||
@ -17,6 +18,7 @@
|
||||
|
||||
namespace svr2::env::socket {
|
||||
namespace {
|
||||
static const char* SIMULATED_REPORT_PREFIX = "SEV_SIMULATED_REPORT:";
|
||||
|
||||
queue::Queue<socketmain::OutboundMessage> output_messages(256);
|
||||
|
||||
@ -61,7 +63,7 @@ SignalsCauseFatalLogs signals_cause_fatal_logs;
|
||||
|
||||
} // namespace
|
||||
|
||||
Environment::Environment() {}
|
||||
Environment::Environment(bool simulated) : simulated_(simulated) {}
|
||||
|
||||
Environment::~Environment() {}
|
||||
|
||||
@ -102,4 +104,60 @@ error::Error SendSocketMessages(socketwrap::Socket* sock) {
|
||||
}
|
||||
}
|
||||
|
||||
error::Error Environment::RandomBytes(void* bytes, size_t size) const {
|
||||
uint8_t* u8ptr = reinterpret_cast<uint8_t*>(bytes);
|
||||
if (simulated_) {
|
||||
while (size) {
|
||||
auto out = getrandom(u8ptr, size, 0);
|
||||
if (out < 0) {
|
||||
return error::Env_RandomBytes;
|
||||
}
|
||||
size -= out;
|
||||
u8ptr += out;
|
||||
}
|
||||
} else {
|
||||
// This may be slow but uses direct CPU instructions to get randomness.
|
||||
// We're not sure we can trust syscalls, as they might be sent up
|
||||
// to the hypervisor or the host OS, both of which we may not have fully
|
||||
// verified.
|
||||
unsigned long long r;
|
||||
uint8_t buf[8];
|
||||
CHECK(sizeof(r) == sizeof(buf));
|
||||
while (size) {
|
||||
if (1 != __builtin_ia32_rdrand64_step(&r)) {
|
||||
return error::Env_RandomBytes;
|
||||
}
|
||||
util::BigEndian64Bytes(r, buf);
|
||||
for (size_t i = 0; i < sizeof(buf) && size; i++) {
|
||||
*u8ptr++ = buf[i];
|
||||
size--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return error::OK;
|
||||
}
|
||||
|
||||
std::pair<e2e::Attestation, error::Error> Environment::SimulatedEvidence(
|
||||
context::Context* ctx,
|
||||
const attestation::AttestationData& data) const {
|
||||
e2e::Attestation out;
|
||||
out.set_evidence(SIMULATED_REPORT_PREFIX + data.SerializeAsString());
|
||||
return std::make_pair(out, error::OK);
|
||||
}
|
||||
|
||||
std::pair<attestation::AttestationData, error::Error> Environment::SimulatedAttest(
|
||||
context::Context* ctx,
|
||||
util::UnixSecs now,
|
||||
const e2e::Attestation& attestation) const {
|
||||
attestation::AttestationData out;
|
||||
if (attestation.evidence().rfind(SIMULATED_REPORT_PREFIX, 0) != 0) {
|
||||
return std::make_pair(out, error::Env_AttestationFailure);
|
||||
}
|
||||
size_t prefix_len = strlen(SIMULATED_REPORT_PREFIX);
|
||||
if (!out.ParseFromArray(attestation.evidence().data() + prefix_len, attestation.evidence().size() - prefix_len)) {
|
||||
return std::make_pair(out, error::Env_AttestationFailure);
|
||||
}
|
||||
return std::make_pair(out, error::OK);
|
||||
}
|
||||
|
||||
} // namespace svr2::env::socket
|
||||
|
||||
17
enclave/env/socket/socket.h
vendored
17
enclave/env/socket/socket.h
vendored
@ -13,11 +13,26 @@ namespace svr2::env::socket {
|
||||
class Environment : public ::svr2::env::Environment {
|
||||
public:
|
||||
DELETE_COPY_AND_ASSIGN(Environment);
|
||||
Environment();
|
||||
Environment(bool simulated);
|
||||
virtual ~Environment();
|
||||
virtual error::Error SendMessage(context::Context* ctx, const std::string& msg) const;
|
||||
virtual void Log(int level, const std::string& msg) const;
|
||||
virtual void FlushAllLogsIfAble() const;
|
||||
virtual error::Error RandomBytes(void* bytes, size_t size) const;
|
||||
|
||||
protected:
|
||||
bool simulated() const { return simulated_; }
|
||||
std::pair<e2e::Attestation, error::Error> SimulatedEvidence(
|
||||
context::Context* ctx,
|
||||
const attestation::AttestationData& data) const;
|
||||
std::pair<attestation::AttestationData, error::Error> SimulatedAttest(
|
||||
context::Context* ctx,
|
||||
util::UnixSecs now,
|
||||
const e2e::Attestation& attestation) const;
|
||||
|
||||
|
||||
private:
|
||||
bool simulated_;
|
||||
};
|
||||
|
||||
// Send all outstanding messages, in order, up to the host.
|
||||
|
||||
@ -7,18 +7,18 @@
|
||||
|
||||
namespace svr2::hmac {
|
||||
|
||||
std::array<uint8_t, 32> Sha256(const std::string& input) {
|
||||
Sha256Sum Sha256(const uint8_t* data_start, size_t data_size) {
|
||||
crypto_hash_sha256_state sha;
|
||||
crypto_hash_sha256_init(&sha);
|
||||
crypto_hash_sha256_update(&sha, reinterpret_cast<const unsigned char*>(input.data()), input.size());
|
||||
std::array<uint8_t, 32> out;
|
||||
crypto_hash_sha256_update(&sha, data_start, data_size);
|
||||
Sha256Sum out;
|
||||
crypto_hash_sha256_final(&sha, out.data());
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 32> HmacSha256(const std::array<uint8_t, 32>& key, const std::string& input) {
|
||||
std::array<uint8_t, 32> out;
|
||||
crypto_auth_hmacsha256(out.data(), reinterpret_cast<const unsigned char*>(input.data()), input.size(), key.data());
|
||||
Sha256Sum HmacSha256(const HmacSha256Key& key, const uint8_t* data_start, size_t data_size) {
|
||||
Sha256Sum out;
|
||||
crypto_auth_hmacsha256(out.data(), data_start, data_size, key.data());
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@ -6,11 +6,33 @@
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
|
||||
namespace svr2::hmac {
|
||||
|
||||
std::array<uint8_t, 32> Sha256(const std::string& input);
|
||||
std::array<uint8_t, 32> HmacSha256(const std::array<uint8_t, 32>& key, const std::string& input);
|
||||
const size_t Sha256SumBytes = 32;
|
||||
|
||||
typedef std::array<uint8_t, Sha256SumBytes> Sha256Sum;
|
||||
typedef std::array<uint8_t, Sha256SumBytes> HmacSha256Key;
|
||||
|
||||
Sha256Sum Sha256(const uint8_t* data_start, size_t data_size);
|
||||
Sha256Sum HmacSha256(const HmacSha256Key& key, const uint8_t* data_start, size_t data_size);
|
||||
|
||||
inline Sha256Sum Sha256(const char* c_str) {
|
||||
return Sha256(reinterpret_cast<const uint8_t*>(c_str), strlen(c_str));
|
||||
}
|
||||
template <class T>
|
||||
Sha256Sum Sha256(const T& data) {
|
||||
return Sha256(reinterpret_cast<const uint8_t*>(data.data()), data.size());
|
||||
}
|
||||
|
||||
inline Sha256Sum HmacSha256(const HmacSha256Key& key, const char* c_str) {
|
||||
return HmacSha256(key, reinterpret_cast<const uint8_t*>(c_str), strlen(c_str));
|
||||
}
|
||||
template <class T>
|
||||
Sha256Sum HmacSha256(const HmacSha256Key& key, const T& data) {
|
||||
return HmacSha256(key, reinterpret_cast<const uint8_t*>(data.data()), data.size());
|
||||
}
|
||||
|
||||
} // namespace svr2::hmac
|
||||
|
||||
|
||||
1
enclave/rapidjson
Submodule
1
enclave/rapidjson
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 6089180ecb704cb2b136777798fa1be303618975
|
||||
86
enclave/util/base64.cc
Normal file
86
enclave/util/base64.cc
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
#include "util/base64.h"
|
||||
#include <stdint.h>
|
||||
#include "util/macros.h"
|
||||
#include "metrics/metrics.h"
|
||||
|
||||
namespace svr2::util {
|
||||
|
||||
const char B64STD[256] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
};
|
||||
|
||||
const char B64URL[256] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
};
|
||||
|
||||
static const char padding = '=';
|
||||
|
||||
error::Error B64DecodeInline(std::string* inout, const char type[256]) {
|
||||
size_t j = 0;
|
||||
uint16_t accumulator = 0;
|
||||
uint16_t bits = 0;
|
||||
bool found_padding = false;
|
||||
for (size_t i = 0; i < inout->size(); i++) {
|
||||
char next = inout->at(i);
|
||||
if (next == padding) {
|
||||
// We're very permissive with padding. We allow no padding,
|
||||
// or any number of padding characters at the end of a string.
|
||||
found_padding = true;
|
||||
for (i++; i < inout->size(); i++) {
|
||||
if (inout->at(i) != padding) {
|
||||
return COUNTED_ERROR(Util_Base64InvalidPadding);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
char c = type[(size_t) next];
|
||||
if (c == -1) {
|
||||
LOG(DEBUG) << "Invalid character: " << ((int) next);
|
||||
return COUNTED_ERROR(Util_Base64InvalidChar);
|
||||
}
|
||||
accumulator = (accumulator << 6) | c;
|
||||
bits += 6;
|
||||
if (bits >= 8) {
|
||||
CHECK(j <= i);
|
||||
(*inout)[j++] = accumulator >> (bits - 8);
|
||||
bits -= 8;
|
||||
}
|
||||
}
|
||||
inout->resize(j);
|
||||
return error::OK;
|
||||
}
|
||||
|
||||
} // namespace svr2::util
|
||||
23
enclave/util/base64.h
Normal file
23
enclave/util/base64.h
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
#ifndef __SVR2_ATTESTATION_BASE64_BASE64_H__
|
||||
#define __SVR2_ATTESTATION_BASE64_BASE64_H__
|
||||
|
||||
#include <string>
|
||||
#include "proto/error.pb.h"
|
||||
|
||||
namespace svr2::util {
|
||||
|
||||
extern const char B64URL[256];
|
||||
extern const char B64STD[256];
|
||||
|
||||
// B64DecodeInline takes in a string containing base64 and modifies
|
||||
// it to contain the base64-decoded data. `*inout` will be modified,
|
||||
// and should an error be returned, it will most likely not contain
|
||||
// the same data as it had when this function was initially called.
|
||||
error::Error B64DecodeInline(std::string* inout, const char type[256]);
|
||||
|
||||
} // namespace svr2::util
|
||||
|
||||
#endif // __SVR2_ATTESTATION_BASE64_BASE64_H__
|
||||
57
enclave/util/tests/base64.cc
Normal file
57
enclave/util/tests/base64.cc
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
//TESTDEP gtest
|
||||
//TESTDEP proto
|
||||
//TESTDEP protobuf-lite
|
||||
//TESTDEP metrics
|
||||
//TESTDEP util
|
||||
//TESTDEP env
|
||||
//TESTDEP env/test
|
||||
//TESTDEP context
|
||||
//TESTDEP libsodium
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "util/base64.h"
|
||||
#include "env/env.h"
|
||||
|
||||
namespace svr2::util {
|
||||
|
||||
class Base64Test : public ::testing::Test {
|
||||
protected:
|
||||
static void SetUpTestSuite() {
|
||||
env::Init(env::SIMULATED);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(Base64Test, Decode) {
|
||||
std::string in("TWFu");
|
||||
ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD));
|
||||
ASSERT_EQ("Man", in);
|
||||
in = "TWE";
|
||||
ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD));
|
||||
ASSERT_EQ("Ma", in);
|
||||
in = "TWE=";
|
||||
ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD));
|
||||
ASSERT_EQ("Ma", in);
|
||||
in = "TQ";
|
||||
ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD));
|
||||
ASSERT_EQ("M", in);
|
||||
in = "TQ=";
|
||||
ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD));
|
||||
ASSERT_EQ("M", in);
|
||||
in = "TQ==";
|
||||
ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD));
|
||||
ASSERT_EQ("M", in);
|
||||
in = "TQ=====";
|
||||
ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD));
|
||||
ASSERT_EQ("M", in);
|
||||
in = "TQ==x";
|
||||
ASSERT_EQ(error::Util_Base64InvalidPadding, B64DecodeInline(&in, B64STD));
|
||||
}
|
||||
|
||||
} // namespace svr2::util
|
||||
@ -43,9 +43,9 @@ func main() {
|
||||
log.Fatalf("GetAttestationFromReport: %v", err)
|
||||
}
|
||||
out := &pb.SevSnpEndorsements{}
|
||||
out.Vcek = attestation.CertificateChain.VcekCert
|
||||
out.Ask = attestation.CertificateChain.AskCert
|
||||
out.Ark = attestation.CertificateChain.ArkCert
|
||||
out.VcekDer = attestation.CertificateChain.VcekCert
|
||||
out.AskDer = attestation.CertificateChain.AskCert
|
||||
out.ArkDer = attestation.CertificateChain.ArkCert
|
||||
data, err := proto.Marshal(out)
|
||||
if err != nil {
|
||||
log.Fatalf("proto.Marshal: %v", err)
|
||||
|
||||
31
shared/proto/azuresnp.proto
Normal file
31
shared/proto/azuresnp.proto
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package svr2.attestation.azuresnp;
|
||||
option go_package = "github.com/signalapp/svr2/proto";
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
message ASNPEvidence {
|
||||
// Serialized AttestationData
|
||||
bytes attestation_data = 1;
|
||||
|
||||
// tpm2_quote -c 0x81000003 -l sha256:all -q "<sha256 of attestation_data>" -m msg -s sig -o pcrs --pcrs_format values
|
||||
bytes pcrs = 2;
|
||||
bytes msg = 3;
|
||||
bytes sig = 4;
|
||||
|
||||
// tpm2_nvread -C o 0x01400001
|
||||
bytes azure_report = 5; // contains snp_report and runtimedata
|
||||
// tpm2_nvread -C o 0x1C101D0
|
||||
bytes akcert_der = 6;
|
||||
}
|
||||
|
||||
message ASNPEndorsements {
|
||||
// From https://learn.microsoft.com/en-us/azure/virtual-machines/trusted-launch-faq?tabs=cli%2Cdebianbased#certificates
|
||||
bytes intermediate_der = 1;
|
||||
// From http://169.254.169.254/metadata/THIM/amd/certification
|
||||
bytes vcek_der = 2;
|
||||
bytes ask_der = 3;
|
||||
}
|
||||
@ -74,6 +74,8 @@ enum Error {
|
||||
Env_SerializeConfigForEvidence = 313;
|
||||
Env_MallinfoFailure = 314;
|
||||
Env_SerializeMinimumsForEvidence = 315;
|
||||
Env_ParseEvidence = 316;
|
||||
Env_ParseEndorsements = 317;
|
||||
|
||||
Peers_NS = 400;
|
||||
Peers_SendBeforeConnect = 401;
|
||||
@ -195,6 +197,8 @@ enum Error {
|
||||
Util_ArrayCopyTooBig = 1101;
|
||||
Util_HexCharInvalid = 1102;
|
||||
Util_HexBytesSize = 1103;
|
||||
Util_Base64InvalidChar = 1104;
|
||||
Util_Base64InvalidPadding = 1105;
|
||||
|
||||
DB3_NS = 1200;
|
||||
DB3_ScalarMultFailure = 1201;
|
||||
@ -287,4 +291,20 @@ enum Error {
|
||||
AttestationTPM2_SignatureDigest = 2007;
|
||||
AttestationTPM2_PCRDigest = 2008;
|
||||
AttestationTPM2_PCRVerify = 2009;
|
||||
|
||||
AzureSNP_NS = 2100;
|
||||
AzureSNP_OpenFile = 2101;
|
||||
AzureSNP_Mkdir = 2102;
|
||||
AzureSNP_InvalidAKCert = 2103;
|
||||
AzureSNP_InvalidAKCertIntermediate = 2104;
|
||||
AzureSNP_CryptoAllocate = 2105;
|
||||
AzureSNP_AKCertificateChainVerify = 2106;
|
||||
AzureSNP_CryptoStoreInit = 2107;
|
||||
AzureSNP_AzureBufferTooSmall = 2108;
|
||||
AzureSNP_ReportDataMismatch = 2109;
|
||||
AzureSNP_RuntimeDataJSON = 2110;
|
||||
AzureSNP_AKCertPubKey = 2111;
|
||||
AzureSNP_AKCertMismatch = 2112;
|
||||
AzureSNP_PCRMismatch = 2113;
|
||||
AzureSNP_AttestationDataHashMismatch = 2114;
|
||||
};
|
||||
|
||||
@ -8,9 +8,9 @@ option go_package = "github.com/signalapp/svr2/proto";
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
message SevSnpEndorsements {
|
||||
bytes vcek = 1;
|
||||
bytes ask = 2;
|
||||
bytes ark = 3;
|
||||
bytes vlek = 4;
|
||||
bytes vcek_der = 1;
|
||||
bytes ask_der = 2;
|
||||
bytes ark_der = 3;
|
||||
bytes vlek_der = 4;
|
||||
bytes crl = 5;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user