Skip to content

Commit

Permalink
Verify UVM endorsements signed with ECDSA (#6243)
Browse files Browse the repository at this point in the history
  • Loading branch information
achamayou authored Jun 10, 2024
1 parent a00a91b commit 8c07653
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .snpcc_canary
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
O \ o | /
/-xXx--//-----x=x--/-xXx--/---x---->>>--/
...
..
...
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added

- Added TypeScript `TypedKvSet` and `ccfapp.typedKv<K>` to facilitate set handling from application code.
- Added support for UVM endorsements signed with EC keys (#6231).

### Removed

Expand Down
12 changes: 12 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,18 @@ if(BUILD_TESTS)
http_parser.host
)

add_unit_test(
endorsements_test
${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/endorsements.cpp
)
set_property(
TEST endorsements_test
APPEND
PROPERTY
ENVIRONMENT
"TEST_ENDORSEMENTS_PATH=${CMAKE_CURRENT_SOURCE_DIR}/tests/uvm_endorsements"
)

add_unit_test(
historical_queries_test
${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/historical_queries.cpp
Expand Down
5 changes: 3 additions & 2 deletions include/ccf/crypto/cose_verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace crypto

using COSEVerifierUniquePtr = std::unique_ptr<COSEVerifier>;

COSEVerifierUniquePtr make_cose_verifier(const std::vector<uint8_t>& cert);
COSEVerifierUniquePtr make_cose_verifier(const RSAPublicKeyPtr& pubk_ptr);
COSEVerifierUniquePtr make_cose_verifier_from_cert(
const std::vector<uint8_t>& cert);
COSEVerifierUniquePtr make_cose_verifier_from_key(const Pem& public_key);
}
16 changes: 8 additions & 8 deletions src/crypto/openssl/cose_verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace crypto
{
using namespace OpenSSL;

COSEVerifier_OpenSSL::COSEVerifier_OpenSSL(
COSECertVerifier_OpenSSL::COSECertVerifier_OpenSSL(
const std::vector<uint8_t>& certificate)
{
Unique_BIO certbio(certificate);
Expand Down Expand Up @@ -53,10 +53,9 @@ namespace crypto
}
}

COSEVerifier_OpenSSL::COSEVerifier_OpenSSL(const RSAPublicKeyPtr& pubk_ptr)
COSEKeyVerifier_OpenSSL::COSEKeyVerifier_OpenSSL(const Pem& public_key_)
{
public_key =
std::make_shared<PublicKey_OpenSSL>(pubk_ptr->public_key_pem());
public_key = std::make_shared<PublicKey_OpenSSL>(public_key_);
}

COSEVerifier_OpenSSL::~COSEVerifier_OpenSSL() = default;
Expand Down Expand Up @@ -92,13 +91,14 @@ namespace crypto
return false;
}

COSEVerifierUniquePtr make_cose_verifier(const std::vector<uint8_t>& cert)
COSEVerifierUniquePtr make_cose_verifier_from_cert(
const std::vector<uint8_t>& cert)
{
return std::make_unique<COSEVerifier_OpenSSL>(cert);
return std::make_unique<COSECertVerifier_OpenSSL>(cert);
}

COSEVerifierUniquePtr make_cose_verifier(const RSAPublicKeyPtr& pubk_ptr)
COSEVerifierUniquePtr make_cose_verifier_from_key(const Pem& public_key)
{
return std::make_unique<COSEVerifier_OpenSSL>(pubk_ptr);
return std::make_unique<COSEKeyVerifier_OpenSSL>(public_key);
}
}
16 changes: 13 additions & 3 deletions src/crypto/openssl/cose_verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,25 @@ namespace crypto
{
class COSEVerifier_OpenSSL : public COSEVerifier
{
private:
protected:
std::shared_ptr<PublicKey_OpenSSL> public_key;

public:
COSEVerifier_OpenSSL(const std::vector<uint8_t>& certificate);
COSEVerifier_OpenSSL(const RSAPublicKeyPtr& pubk_ptr);
virtual ~COSEVerifier_OpenSSL() override;
virtual bool verify(
const std::span<const uint8_t>& buf,
std::span<uint8_t>& authned_content) const override;
};

class COSECertVerifier_OpenSSL : public COSEVerifier_OpenSSL
{
public:
COSECertVerifier_OpenSSL(const std::vector<uint8_t>& certificate);
};

class COSEKeyVerifier_OpenSSL : public COSEVerifier_OpenSSL
{
public:
COSEKeyVerifier_OpenSSL(const Pem& public_key);
};
}
4 changes: 2 additions & 2 deletions src/endpoints/authentication/cose_auth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ namespace ccf
auto member_cert = member_certs->get(phdr.kid);
if (member_cert.has_value())
{
auto verifier = crypto::make_cose_verifier(member_cert->raw());
auto verifier = crypto::make_cose_verifier_from_cert(member_cert->raw());

std::span<const uint8_t> body = {
ctx->get_request_body().data(), ctx->get_request_body().size()};
Expand Down Expand Up @@ -441,7 +441,7 @@ namespace ccf
auto user_cert = user_certs->get(phdr.kid);
if (user_cert.has_value())
{
auto verifier = crypto::make_cose_verifier(user_cert->raw());
auto verifier = crypto::make_cose_verifier_from_cert(user_cert->raw());

std::span<const uint8_t> body = {
ctx->get_request_body().data(), ctx->get_request_body().size()};
Expand Down
3 changes: 1 addition & 2 deletions src/node/did.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ namespace ccf::did
std::string id;
std::string type;
std::string controller;
std::optional<crypto::JsonWebKeyRSAPublic> public_key_jwk =
std::nullopt; // Note: Only supports RSA for now
std::optional<nlohmann::json> public_key_jwk = std::nullopt;

bool operator==(const DIDDocumentVerificationMethod&) const = default;
};
Expand Down
79 changes: 79 additions & 0 deletions src/node/test/endorsements.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.

#include "ccf/pal/measurement.h"
#include "crypto/openssl/hash.h"
#include "ds/files.h"
#include "node/uvm_endorsements.h"

#define DOCTEST_CONFIG_IMPLEMENT
#include <cstdlib>
#include <doctest/doctest.h>

TEST_CASE("Check RSA Production endorsement")
{
char* end_path = std::getenv("TEST_ENDORSEMENTS_PATH");
REQUIRE(end_path != nullptr);

auto endorsement = files::slurp(fmt::format("{}/rsa_test1.cose", end_path));
REQUIRE(!endorsement.empty());

ccf::pal::SnpAttestationMeasurement measurement(
"02c3b0d5bf1d256fa4e3b5deefc07b55ff2f7029085ed350f60959140a1a51f1310753ba5a"
"b2c03a0536b1c0c193af47");
ccf::pal::PlatformAttestationMeasurement uvm_measurement(measurement);
auto endorsements =
ccf::verify_uvm_endorsements(endorsement, uvm_measurement);
REQUIRE(
endorsements ==
ccf::UVMEndorsements{
"did:x509:0:sha256:I__iuL25oXEVFdTP_aBLx_eT1RPHbCQ_ECBQfYZpt9s::eku:1.3."
"6.1.4.1.311.76.59.1.2",
"ContainerPlat-AMD-UVM",
"100"});
}

TEST_CASE("Check ECDSA Test endorsement")
{
char* end_path = std::getenv("TEST_ENDORSEMENTS_PATH");
REQUIRE(end_path != nullptr);

auto endorsement = files::slurp(fmt::format("{}/ecdsa_test1.cose", end_path));
REQUIRE(!endorsement.empty());

ccf::pal::SnpAttestationMeasurement measurement(
"5a84c66e9c8dd1a991e6d8b43a8aaae488940f87ce25ef6a62ad180cc3c73554ed7e4ccd10"
"13456602758778d9d65c48");
ccf::pal::PlatformAttestationMeasurement uvm_measurement(measurement);
REQUIRE_THROWS_WITH_AS(
ccf::verify_uvm_endorsements(endorsement, uvm_measurement),
"UVM endorsements did "
"did:x509:0:sha256:VFsRLNBh5Zy1HRtVl2IIXAl0lUs-xobEbskZ3XRDpCY::subject:CN:"
"Test%20Leaf%20%28DO%20NOT%20TRUST%29, feed ConfAKS-AMD-UVM-Test, svn 0 do "
"not match any of the known UVM roots of trust",
std::logic_error);

std::vector<ccf::UVMEndorsements> custom_roots_of_trust = {
ccf::UVMEndorsements{
"did:x509:0:sha256:VFsRLNBh5Zy1HRtVl2IIXAl0lUs-xobEbskZ3XRDpCY::subject:"
"CN:Test%20Leaf%20%28DO%20NOT%20TRUST%29",
"ConfAKS-AMD-UVM-Test",
"0"}};

auto endorsements = ccf::verify_uvm_endorsements(
endorsement, uvm_measurement, custom_roots_of_trust);
REQUIRE(endorsements == custom_roots_of_trust[0]);
}

int main(int argc, char** argv)
{
logger::config::default_init();
crypto::openssl_sha256_init();
doctest::Context context;
context.applyCommandLine(argc, argv);
int res = context.run();
crypto::openssl_sha256_shutdown();
if (context.shouldExit())
return res;
return res;
}
53 changes: 39 additions & 14 deletions src/node/uvm_endorsements.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "ccf/crypto/base64.h"
#include "ccf/ds/json.h"
#include "ccf/pal/measurement.h"
#include "ccf/service/tables/uvm_endorsements.h"
#include "crypto/openssl/cose_verifier.h"
#include "node/cose_common.h"
Expand Down Expand Up @@ -52,19 +53,21 @@ namespace ccf
};

// Roots of trust for UVM endorsements/measurement in AMD SEV-SNP attestations
static std::vector<UVMEndorsements> uvm_roots_of_trust = {
static std::vector<UVMEndorsements> default_uvm_roots_of_trust = {
// Confidential Azure Kubertnetes Service (AKS)
{"did:x509:0:sha256:I__iuL25oXEVFdTP_aBLx_eT1RPHbCQ_ECBQfYZpt9s::eku:1.3.6."
"1.4.1.311.76.59.1.2",
"ContainerPlat-AMD-UVM",
"0"},
"100"},
// Confidential Azure Container Instances (ACI)
{"did:x509:0:sha256:I__iuL25oXEVFdTP_aBLx_eT1RPHbCQ_ECBQfYZpt9s::eku:1.3.6."
"1.4.1.311.76.59.1.5",
"ConfAKS-AMD-UVM",
"0"}};

bool inline matches_uvm_roots_of_trust(const UVMEndorsements& endorsements)
bool inline matches_uvm_roots_of_trust(
const UVMEndorsements& endorsements,
const std::vector<UVMEndorsements>& uvm_roots_of_trust)
{
for (const auto& uvm_root_of_trust : uvm_roots_of_trust)
{
Expand Down Expand Up @@ -248,10 +251,10 @@ namespace ccf
}

static std::span<const uint8_t> verify_uvm_endorsements_signature(
const crypto::RSAPublicKeyPtr& leef_cert_pub_key,
const crypto::Pem& leaf_cert_pub_key,
const std::vector<uint8_t>& uvm_endorsements_raw)
{
auto verifier = crypto::make_cose_verifier(leef_cert_pub_key);
auto verifier = crypto::make_cose_verifier_from_key(leaf_cert_pub_key);

std::span<uint8_t> payload;
if (!verifier->verify(uvm_endorsements_raw, payload))
Expand All @@ -264,14 +267,16 @@ namespace ccf

static UVMEndorsements verify_uvm_endorsements(
const std::vector<uint8_t>& uvm_endorsements_raw,
const pal::PlatformAttestationMeasurement& uvm_measurement)
const pal::PlatformAttestationMeasurement& uvm_measurement,
const std::vector<UVMEndorsements>& uvm_roots_of_trust =
default_uvm_roots_of_trust)
{
auto phdr = cose::decode_protected_header(uvm_endorsements_raw);

if (!cose::is_rsa_alg(phdr.alg))
if (!(cose::is_rsa_alg(phdr.alg) || cose::is_ecdsa_alg(phdr.alg)))
{
throw std::logic_error(
fmt::format("Signature algorithm {} is not expected RSA", phdr.alg));
throw std::logic_error(fmt::format(
"Signature algorithm {} is not one of expected: RSA, ECDSA", phdr.alg));
}

std::string pem_chain;
Expand All @@ -292,17 +297,37 @@ namespace ccf
did_document_str));
}

crypto::RSAPublicKeyPtr pubk = nullptr;
crypto::Pem pubk;
for (auto const& vm : did_document.verification_method)
{
if (vm.controller == did && vm.public_key_jwk.has_value())
{
pubk = crypto::make_rsa_public_key(vm.public_key_jwk.value());
break;
auto jwk = vm.public_key_jwk.value().get<crypto::JsonWebKey>();
switch (jwk.kty)
{
case crypto::JsonWebKeyType::RSA:
{
auto rsa_jwk =
vm.public_key_jwk->get<crypto::JsonWebKeyRSAPublic>();
pubk = crypto::make_rsa_public_key(rsa_jwk)->public_key_pem();
break;
}
case crypto::JsonWebKeyType::EC:
{
auto ec_jwk = vm.public_key_jwk->get<crypto::JsonWebKeyECPublic>();
pubk = crypto::make_public_key(ec_jwk)->public_key_pem();
break;
}
default:
{
throw std::logic_error(fmt::format(
"Unsupported public key type ({}) for DID {}", jwk.kty, did));
}
}
}
}

if (pubk == nullptr)
if (pubk.empty())
{
throw std::logic_error(fmt::format(
"Could not find matching public key for DID {} in {}",
Expand Down Expand Up @@ -341,7 +366,7 @@ namespace ccf

UVMEndorsements end{did, phdr.feed, payload.sevsnpvm_guest_svn};

if (!matches_uvm_roots_of_trust(end))
if (!matches_uvm_roots_of_trust(end, uvm_roots_of_trust))
{
throw std::logic_error(fmt::format(
"UVM endorsements did {}, feed {}, svn {} "
Expand Down
Binary file added tests/uvm_endorsements/ecdsa_test1.cose
Binary file not shown.
Binary file added tests/uvm_endorsements/rsa_test1.cose
Binary file not shown.

0 comments on commit 8c07653

Please sign in to comment.