diff --git a/src/node/node_state.h b/src/node/node_state.h index 7d9cbc7d9..85785099e 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -238,7 +238,8 @@ namespace ccf #ifdef GET_QUOTE if (network.consensus_type != ConsensusType::BFT) { - auto quote_opt = QuoteGenerator::get_quote(node_cert); + auto quote_opt = + QuoteGenerator::get_quote(node_sign_kp->public_key_pem()); if (!quote_opt.has_value()) { return Fail("Quote could not be retrieved"); diff --git a/src/node/quote.h b/src/node/quote.h index 3c87716e2..0a0eb0e09 100644 --- a/src/node/quote.h +++ b/src/node/quote.h @@ -51,7 +51,7 @@ namespace ccf static std::optional> get_quote(const tls::Pem& cert) { std::vector raw_quote; - crypto::Sha256Hash h{cert.raw()}; + crypto::Sha256Hash h{cert.contents()}; uint8_t* quote; size_t quote_len = 0; @@ -127,7 +127,7 @@ namespace ccf static QuoteVerificationResult verify_quoted_certificate( const tls::Pem& cert, const oe_report_t& parsed_quote) { - crypto::Sha256Hash hash{cert.raw()}; + crypto::Sha256Hash hash{cert.contents()}; if ( parsed_quote.report_data_size != OE_REPORT_DATA_SIZE || diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index 3980f5515..792c31c34 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -89,9 +89,11 @@ namespace ccf #ifdef GET_QUOTE if (network.consensus_type != ConsensusType::BFT) { + auto pk_pem = public_key_pem_from_cert(caller_pem); + QuoteVerificationResult verify_result = QuoteVerifier::verify_quote_against_store( - tx, this->network.node_code_ids, in.quote, caller_pem); + tx, this->network.node_code_ids, in.quote, pk_pem); if (verify_result != QuoteVerificationResult::VERIFIED) { diff --git a/src/tls/key_pair.h b/src/tls/key_pair.h index 286f8eaff..3a90ca794 100644 --- a/src/tls/key_pair.h +++ b/src/tls/key_pair.h @@ -856,4 +856,29 @@ namespace tls return std::make_shared(std::move(key)); } } + + static inline tls::Pem public_key_pem_from_cert(const tls::Pem& cert) + { + mbedtls_x509_crt c; + mbedtls_x509_crt_init(&c); + int rc = mbedtls_x509_crt_parse(&c, cert.data(), cert.size()); + if (rc != 0) + { + mbedtls_x509_crt_free(&c); + throw std::runtime_error(fmt::format( + "Failed to parse certificate, mbedtls_x509_crt_parse: {}", rc)); + } + uint8_t data[2048]; + rc = mbedtls_pk_write_pubkey_pem(&c.pk, data, max_pem_key_size); + if (rc != 0) + { + mbedtls_x509_crt_free(&c); + throw std::runtime_error(fmt::format( + "Failed to serialise public key, mbedtls_pk_write_pubkey_pem: {}", rc)); + } + + size_t len = strlen((char const*)data); + mbedtls_x509_crt_free(&c); + return tls::Pem(data, len); + } } diff --git a/src/tls/pem.h b/src/tls/pem.h index 5e229dff7..5aebeecfa 100644 --- a/src/tls/pem.h +++ b/src/tls/pem.h @@ -25,6 +25,8 @@ namespace tls Pem(const std::string& s_) : s(s_) {} + Pem(size_t size) : s(size, '0') {} + Pem(const uint8_t* data, size_t size) { if (size == 0) @@ -78,6 +80,12 @@ namespace tls return {data(), data() + size()}; } + // Not null-terminated + std::vector contents() const + { + return {data(), data() + s.size()}; + } + MSGPACK_DEFINE(s); }; @@ -102,4 +110,4 @@ namespace tls fmt::format("Unable to parse pem from this JSON: {}", j.dump())); } } -} \ No newline at end of file +} diff --git a/src/tls/test/main.cpp b/src/tls/test/main.cpp index 7fef3f864..fea39ac6e 100644 --- a/src/tls/test/main.cpp +++ b/src/tls/test/main.cpp @@ -388,4 +388,18 @@ TEST_CASE("Wrap, unwrap with RSAKeyPair") auto unwrapped = rsa_kp->unwrap(wrapped, label); REQUIRE(input == unwrapped); } +} + +TEST_CASE("Extract public key from cert") +{ + for (const auto curve : supported_curves) + { + INFO("With curve: " << labels[static_cast(curve) - 1]); + auto kp = tls::make_key_pair(curve); + auto pk = kp->public_key_pem(); + auto cert = kp->self_sign("CN=name"); + + auto pubk = tls::public_key_pem_from_cert(cert); + REQUIRE(pk == pubk); + } } \ No newline at end of file diff --git a/tests/governance.py b/tests/governance.py index 12e1b12a9..b53fca7eb 100644 --- a/tests/governance.py +++ b/tests/governance.py @@ -11,7 +11,12 @@ import infra.net import infra.e2e_args import suite.test_requirements as reqs import infra.logging_app as app +import ssl +import hashlib +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization from loguru import logger as LOG @@ -49,9 +54,42 @@ def test_quote(network, args, verify=True): r = c.get("/node/quotes") quotes = r.body.json()["quotes"] assert len(quotes) == len(network.find_nodes()) + for quote in quotes: mrenclave = quote["mrenclave"] assert mrenclave == expected_mrenclave, (mrenclave, expected_mrenclave) + qpath = os.path.join(network.common_dir, f"quote{quote['node_id']}") + + with open(qpath, "wb") as q: + q.write(bytes.fromhex(quote["raw"])) + oed = subprocess.run( + [ + os.path.join(args.oe_binary, "oeverify"), + "-r", + qpath, + "-f", + "LEGACY_REPORT_REMOTE", + ], + capture_output=True, + check=True, + ) + out = oed.stdout.decode().split(os.linesep) + for line in out: + if line.startswith("Enclave sgx_report_data:"): + report_digest = line.split(" ")[-1][2:] + assert "Evidence verification succeeded (0)." in out + + node = network.nodes[quote["node_id"]] + node_cert = ssl.get_server_certificate((node.pubhost, node.rpc_port)) + public_key = x509.load_pem_x509_certificate( + node_cert.encode(), default_backend() + ).public_key() + pub_key = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + key_digest = hashlib.sha256(pub_key).hexdigest() + assert report_digest[: len(key_digest)] == key_digest return network