Build Azure-specific confidential computing evidence/attestation into {attestation,env}/azuresnp

This commit is contained in:
gram-signal 2024-02-15 15:35:27 -07:00 committed by GitHub
parent 9fc13fa569
commit d03ead712f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 1194 additions and 180 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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)

View File

@ -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 ?= \

View 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

View 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__

File diff suppressed because one or more lines are too long

View File

@ -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;
}

View File

@ -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__

View File

@ -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
View 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

View File

@ -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_;
};

View File

@ -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_;
};

View File

@ -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

View File

@ -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.

View File

@ -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;
}

View File

@ -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

@ -0,0 +1 @@
Subproject commit 6089180ecb704cb2b136777798fa1be303618975

86
enclave/util/base64.cc Normal file
View 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
View 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__

View 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

View File

@ -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)

View 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;
}

View File

@ -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;
};

View File

@ -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;
}