Merge changes from topic "rkp_v3" am: 5ae39a28c2 am: bf3fd233cc

Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/2237125

Change-Id: I4eb6b3f8e8c987e30a14206fab9612e6c44f6699
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Tri Vo 2022-10-20 03:42:02 +00:00 committed by Automerger Merge Worker
commit c4e2e934ff
3 changed files with 453 additions and 21 deletions

View file

@ -46,6 +46,7 @@ using ::std::vector;
namespace {
constexpr int32_t VERSION_WITH_UNIQUE_ID_SUPPORT = 2;
constexpr int32_t VERSION_WITHOUT_TEST_MODE = 3;
#define INSTANTIATE_REM_PROV_AIDL_TEST(name) \
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(name); \
@ -180,6 +181,15 @@ class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam<std::
return params;
}
void checkMacedPubkeyVersioned(const MacedPublicKey& macedPubKey, bool testMode,
vector<uint8_t>* payload_value) {
if (rpcHardwareInfo.versionNumber >= VERSION_WITHOUT_TEST_MODE) {
check_maced_pubkey(macedPubKey, false, payload_value);
} else {
check_maced_pubkey(macedPubKey, testMode, payload_value);
}
}
protected:
std::shared_ptr<IRemotelyProvisionedComponent> provisionable_;
RpcHardwareInfo rpcHardwareInfo;
@ -256,7 +266,7 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_prodMode) {
auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
ASSERT_TRUE(status.isOk());
vector<uint8_t> coseKeyData;
check_maced_pubkey(macedPubKey, testMode, &coseKeyData);
checkMacedPubkeyVersioned(macedPubKey, testMode, &coseKeyData);
}
/**
@ -279,7 +289,7 @@ TEST_P(GenerateKeyTests, generateAndUseEcdsaP256Key_prodMode) {
auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
ASSERT_TRUE(status.isOk());
vector<uint8_t> coseKeyData;
check_maced_pubkey(macedPubKey, testMode, &coseKeyData);
checkMacedPubkeyVersioned(macedPubKey, testMode, &coseKeyData);
AttestationKey attestKey;
attestKey.keyBlob = std::move(privateKeyBlob);
@ -334,13 +344,13 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_testMode) {
bool testMode = true;
auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
ASSERT_TRUE(status.isOk());
check_maced_pubkey(macedPubKey, testMode, nullptr);
checkMacedPubkeyVersioned(macedPubKey, testMode, nullptr);
}
class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
class CertificateRequestTestBase : public VtsRemotelyProvisionedComponentTests {
protected:
CertificateRequestTest() : eekId_(string_to_bytevec("eekid")), challenge_(randomBytes(64)) {}
CertificateRequestTestBase()
: eekId_(string_to_bytevec("eekid")), challenge_(randomBytes(64)) {}
void generateTestEekChain(size_t eekLength) {
auto chain = generateEekChain(rpcHardwareInfo.supportedEekCurve, eekLength, eekId_);
@ -359,7 +369,7 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
ASSERT_TRUE(status.isOk()) << status.getMessage();
vector<uint8_t> payload_value;
check_maced_pubkey(key, testMode, &payload_value);
checkMacedPubkeyVersioned(key, testMode, &payload_value);
cborKeysToSign_.add(cppbor::EncodedItem(payload_value));
}
}
@ -372,6 +382,18 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
cppbor::Array cborKeysToSign_;
};
class CertificateRequestTest : public CertificateRequestTestBase {
protected:
void SetUp() override {
CertificateRequestTestBase::SetUp();
if (rpcHardwareInfo.versionNumber >= VERSION_WITHOUT_TEST_MODE) {
GTEST_SKIP() << "This test case only applies to RKP v1 and v2. "
<< "RKP version discovered: " << rpcHardwareInfo.versionNumber;
}
}
};
/**
* Generate an empty certificate request in test mode, and decrypt and verify the structure and
* content.
@ -638,4 +660,131 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testKeyInProdCert) {
INSTANTIATE_REM_PROV_AIDL_TEST(CertificateRequestTest);
class CertificateRequestV2Test : public CertificateRequestTestBase {
void SetUp() override {
CertificateRequestTestBase::SetUp();
if (rpcHardwareInfo.versionNumber < VERSION_WITHOUT_TEST_MODE) {
GTEST_SKIP() << "This test case only applies to RKP v3 and above. "
<< "RKP version discovered: " << rpcHardwareInfo.versionNumber;
}
}
};
/**
* Generate an empty certificate request, and decrypt and verify the structure and content.
*/
TEST_P(CertificateRequestV2Test, EmptyRequest) {
bytevec csr;
auto status =
provisionable_->generateCertificateRequestV2({} /* keysToSign */, challenge_, &csr);
ASSERT_TRUE(status.isOk()) << status.getMessage();
auto result = verifyProductionCsr(cppbor::Array(), csr, provisionable_.get(), challenge_);
ASSERT_TRUE(result) << result.message();
}
/**
* Generate a non-empty certificate request. Decrypt, parse and validate the contents.
*/
TEST_P(CertificateRequestV2Test, NonEmptyRequest) {
generateKeys(false /* testMode */, 1 /* numKeys */);
bytevec csr;
auto status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr);
ASSERT_TRUE(status.isOk()) << status.getMessage();
auto result = verifyProductionCsr(cborKeysToSign_, csr, provisionable_.get(), challenge_);
ASSERT_TRUE(result) << result.message();
}
/**
* Generate a non-empty certificate request. Make sure contents are reproducible.
*/
TEST_P(CertificateRequestV2Test, NonEmptyRequestReproducible) {
generateKeys(false /* testMode */, 1 /* numKeys */);
bytevec csr;
auto status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr);
ASSERT_TRUE(status.isOk()) << status.getMessage();
auto firstBcc = verifyProductionCsr(cborKeysToSign_, csr, provisionable_.get(), challenge_);
ASSERT_TRUE(firstBcc) << firstBcc.message();
status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr);
ASSERT_TRUE(status.isOk()) << status.getMessage();
auto secondBcc = verifyProductionCsr(cborKeysToSign_, csr, provisionable_.get(), challenge_);
ASSERT_TRUE(secondBcc) << secondBcc.message();
ASSERT_EQ(firstBcc->size(), secondBcc->size());
for (auto i = 0; i < firstBcc->size(); i++) {
ASSERT_EQ(firstBcc->at(i).pubKey, secondBcc->at(i).pubKey);
}
}
/**
* Generate a non-empty certificate request with multiple keys.
*/
TEST_P(CertificateRequestV2Test, NonEmptyRequestMultipleKeys) {
// TODO(b/254137722): define a minimum number of keys that must be supported.
generateKeys(false /* testMode */, 5 /* numKeys */);
bytevec csr;
auto status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr);
ASSERT_TRUE(status.isOk()) << status.getMessage();
auto result = verifyProductionCsr(cborKeysToSign_, csr, provisionable_.get(), challenge_);
ASSERT_TRUE(result) << result.message();
}
/**
* Generate a non-empty certificate request, but with the MAC corrupted on the keypair.
*/
TEST_P(CertificateRequestV2Test, NonEmptyRequestCorruptMac) {
generateKeys(false /* testMode */, 1 /* numKeys */);
auto result = corrupt_maced_key(keysToSign_[0]);
ASSERT_TRUE(result) << result.moveMessage();
MacedPublicKey keyWithCorruptMac = result.moveValue();
bytevec csr;
auto status =
provisionable_->generateCertificateRequestV2({keyWithCorruptMac}, challenge_, &csr);
ASSERT_FALSE(status.isOk()) << status.getMessage();
EXPECT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_MAC);
}
/**
* Generate a non-empty certificate request in prod mode, with test keys. Test mode must be
* ignored, i.e. test must pass.
*/
TEST_P(CertificateRequestV2Test, NonEmptyRequest_testKeyInProdCert) {
generateKeys(true /* testMode */, 1 /* numKeys */);
bytevec csr;
auto status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr);
ASSERT_TRUE(status.isOk()) << status.getMessage();
}
/**
* Call generateCertificateRequest(). Make sure it's removed.
*/
TEST_P(CertificateRequestV2Test, CertificateRequestV1Removed) {
generateTestEekChain(2);
bytevec keysToSignMac;
DeviceInfo deviceInfo;
ProtectedData protectedData;
auto status = provisionable_->generateCertificateRequest(
true /* testMode */, {} /* keysToSign */, testEekChain_.chain, challenge_, &deviceInfo,
&protectedData, &keysToSignMac);
ASSERT_FALSE(status.isOk()) << status.getMessage();
EXPECT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_REMOVED);
}
INSTANTIATE_REM_PROV_AIDL_TEST(CertificateRequestV2Test);
} // namespace aidl::android::hardware::security::keymint::test

View file

@ -177,4 +177,19 @@ ErrMsgOr<std::vector<BccEntryData>> verifyProductionProtectedData(
const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge);
/**
* Verify the CSR as if the device is still early in the factory process and may not
* have all device identifiers provisioned yet.
*/
ErrMsgOr<std::vector<BccEntryData>> verifyFactoryCsr(const cppbor::Array& keysToSign,
const std::vector<uint8_t>& csr,
IRemotelyProvisionedComponent* provisionable,
const std::vector<uint8_t>& challenge);
/**
* Verify the CSR as if the device is a final production sample.
*/
ErrMsgOr<std::vector<BccEntryData>> verifyProductionCsr(
const cppbor::Array& keysToSign, const std::vector<uint8_t>& csr,
IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge);
} // namespace aidl::android::hardware::security::keymint::remote_prov

View file

@ -32,6 +32,7 @@
#include <openssl/base64.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/x509.h>
#include <remote_prov/remote_prov_utils.h>
namespace aidl::android::hardware::security::keymint::remote_prov {
@ -45,6 +46,8 @@ constexpr int kP256AffinePointSize = 32;
using EC_KEY_Ptr = bssl::UniquePtr<EC_KEY>;
using EVP_PKEY_Ptr = bssl::UniquePtr<EVP_PKEY>;
using EVP_PKEY_CTX_Ptr = bssl::UniquePtr<EVP_PKEY_CTX>;
using X509_Ptr = bssl::UniquePtr<X509>;
using CRYPTO_BUFFER_Ptr = bssl::UniquePtr<CRYPTO_BUFFER>;
ErrMsgOr<bytevec> ecKeyGetPrivateKey(const EC_KEY* ecKey) {
// Extract private key.
@ -527,6 +530,10 @@ ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateDeviceInfo(
if (parsed->clone()->asMap()->canonicalize().encode() != deviceInfoBytes) {
return "DeviceInfo ordering is non-canonical.";
}
RpcHardwareInfo info;
provisionable->getHardwareInfo(&info);
if (info.versionNumber < 3) {
const std::unique_ptr<cppbor::Item>& version = parsed->get("version");
if (!version) {
return "Device info is missing version";
@ -534,15 +541,16 @@ ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateDeviceInfo(
if (!version->asUint()) {
return "version must be an unsigned integer";
}
RpcHardwareInfo info;
provisionable->getHardwareInfo(&info);
if (version->asUint()->value() != info.versionNumber) {
return "DeviceInfo version (" + std::to_string(version->asUint()->value()) +
") does not match the remotely provisioned component version (" +
std::to_string(info.versionNumber) + ").";
}
}
std::string error;
switch (version->asUint()->value()) {
switch (info.versionNumber) {
case 3:
case 2:
for (const auto& entry : kAttestationIdEntrySet) {
error += checkMapEntry(isFactory && !entry.alwaysValidate, *parsed, cppbor::TSTR,
@ -579,7 +587,7 @@ ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateDeviceInfo(
kValidAttIdStates);
break;
default:
return "Unrecognized version: " + std::to_string(version->asUint()->value());
return "Unrecognized version: " + std::to_string(info.versionNumber);
}
if (!error.empty()) {
@ -734,4 +742,264 @@ ErrMsgOr<std::vector<BccEntryData>> verifyProductionProtectedData(
/*isFactory=*/false);
}
ErrMsgOr<X509_Ptr> parseX509Cert(const std::vector<uint8_t>& cert) {
CRYPTO_BUFFER_Ptr certBuf(CRYPTO_BUFFER_new(cert.data(), cert.size(), nullptr));
if (!certBuf.get()) {
return "Failed to create crypto buffer.";
}
X509_Ptr result(X509_parse_from_buffer(certBuf.get()));
if (!result.get()) {
return "Failed to parse certificate.";
}
return result;
}
std::string getX509IssuerName(const X509_Ptr& cert) {
char* name = X509_NAME_oneline(X509_get_issuer_name(cert.get()), nullptr, 0);
std::string result(name);
OPENSSL_free(name);
return result;
}
std::string getX509SubjectName(const X509_Ptr& cert) {
char* name = X509_NAME_oneline(X509_get_subject_name(cert.get()), nullptr, 0);
std::string result(name);
OPENSSL_free(name);
return result;
}
// Validates the certificate chain and returns the leaf public key.
ErrMsgOr<bytevec> validateCertChain(const cppbor::Array& chain) {
uint8_t rawPubKey[64];
size_t rawPubKeySize = sizeof(rawPubKey);
for (size_t i = 0; i < chain.size(); ++i) {
// Root must be self-signed.
size_t signingCertIndex = (i > 1) ? i - 1 : i;
auto& keyCertItem = chain[i];
auto& signingCertItem = chain[signingCertIndex];
if (!keyCertItem || !keyCertItem->asBstr()) {
return "Key certificate must be a Bstr.";
}
if (!signingCertItem || !signingCertItem->asBstr()) {
return "Signing certificate must be a Bstr.";
}
auto keyCert = parseX509Cert(keyCertItem->asBstr()->value());
if (!keyCert) {
return keyCert.message();
}
auto signingCert = parseX509Cert(keyCertItem->asBstr()->value());
if (!signingCert) {
return signingCert.message();
}
EVP_PKEY_Ptr pubKey(X509_get_pubkey(keyCert->get()));
if (!pubKey.get()) {
return "Failed to get public key.";
}
EVP_PKEY_Ptr signingPubKey(X509_get_pubkey(signingCert->get()));
if (!signingPubKey.get()) {
return "Failed to get signing public key.";
}
if (!X509_verify(keyCert->get(), signingPubKey.get())) {
return "Verification of certificate " + std::to_string(i) +
" faile. OpenSSL error string: " + ERR_error_string(ERR_get_error(), NULL);
}
auto certIssuer = getX509IssuerName(*keyCert);
auto signerSubj = getX509SubjectName(*signingCert);
if (certIssuer != signerSubj) {
return "Certificate " + std::to_string(i) + " has wrong issuer. Signer subject is " +
signerSubj + " Issuer subject is " + certIssuer;
}
rawPubKeySize = sizeof(rawPubKey);
if (!EVP_PKEY_get_raw_public_key(pubKey.get(), rawPubKey, &rawPubKeySize)) {
return "Failed to get raw public key.";
}
}
return bytevec(rawPubKey, rawPubKey + rawPubKeySize);
}
std::string validateUdsCerts(const cppbor::Map& udsCerts, const bytevec& udsPub) {
for (const auto& [signerName, udsCertChain] : udsCerts) {
if (!signerName || !signerName->asTstr()) {
return "Signer Name must be a Tstr.";
}
if (!udsCertChain || !udsCertChain->asArray()) {
return "UDS certificate chain must be an Array.";
}
if (udsCertChain->asArray()->size() < 2) {
return "UDS certificate chain must have at least two entries: root and leaf.";
}
auto leafPubKey = validateCertChain(*udsCertChain->asArray());
if (!leafPubKey) {
return leafPubKey.message();
}
if (*leafPubKey != udsPub) {
return "Leaf public key in UDS certificat chain doesn't match UDS public key.";
}
}
return "";
}
ErrMsgOr<cppbor::Array> parseAndValidateCsrPayload(const cppbor::Array& keysToSign,
const std::vector<uint8_t>& csrPayload,
IRemotelyProvisionedComponent* provisionable,
const std::vector<uint8_t>& challenge,
bool isFactory) {
auto [parsedCsrPayload, _, errMsg] = cppbor::parse(csrPayload);
if (!parsedCsrPayload) {
return errMsg;
}
if (!parsedCsrPayload->asArray()) {
return "CSR payload is not a CBOR array.";
}
if (parsedCsrPayload->asArray()->size() != 5U) {
return "CSR payload must contain version, certificate type, device info, challenge, keys. "
"However, the parsed CSR payload has " +
std::to_string(parsedCsrPayload->asArray()->size()) + " entries.";
}
auto& signedVersion = parsedCsrPayload->asArray()->get(0);
auto& signedCertificateType = parsedCsrPayload->asArray()->get(1);
auto& signedDeviceInfo = parsedCsrPayload->asArray()->get(2);
auto& signedChallenge = parsedCsrPayload->asArray()->get(3);
auto& signedKeys = parsedCsrPayload->asArray()->get(4);
if (!signedVersion || !signedVersion->asUint() || signedVersion->asUint()->value() != 1U) {
return "CSR payload version must be an unsigned integer and must be equal to 1.";
}
if (!signedCertificateType || !signedCertificateType->asTstr()) {
// Certificate type is allowed to be extendend by vendor, i.e. we can't
// enforce its value.
return "Certificate type must be a Tstr.";
}
if (!signedDeviceInfo || !signedDeviceInfo->asMap()) {
return "Device info must be an Map.";
}
if (!signedChallenge || !signedChallenge->asBstr()) {
return "Challenge must be a Bstr.";
}
if (!signedKeys || !signedKeys->asArray()) {
return "Keys must be an Array.";
}
auto result = parseAndValidateDeviceInfo(signedDeviceInfo->asMap()->encode(), provisionable,
isFactory);
if (!result) {
return result.message();
}
if (challenge.size() < 32 || challenge.size() > 64) {
return "Challenge size must be between 32 and 64 bytes inclusive. "
"However, challenge is " +
std::to_string(challenge.size()) + " bytes long.";
}
auto challengeBstr = cppbor::Bstr(challenge);
if (*signedChallenge->asBstr() != challengeBstr) {
return "Signed challenge does not match."
"\n Actual: " +
cppbor::prettyPrint(signedChallenge->asBstr(), 64 /* maxBStrSize */) +
"\nExpected: " + cppbor::prettyPrint(&challengeBstr, 64 /* maxBStrSize */);
}
if (signedKeys->asArray()->encode() != keysToSign.encode()) {
return "Signed keys do not match.";
}
return std::move(*parsedCsrPayload->asArray());
}
ErrMsgOr<std::vector<BccEntryData>> verifyCsr(const cppbor::Array& keysToSign,
const std::vector<uint8_t>& csr,
IRemotelyProvisionedComponent* provisionable,
const std::vector<uint8_t>& challenge,
bool isFactory) {
auto [parsedCsr, _, csrErrMsg] = cppbor::parse(csr);
if (!parsedCsr) {
return csrErrMsg;
}
if (!parsedCsr->asArray()) {
return "CSR is not a CBOR array.";
}
if (parsedCsr->asArray()->size() != 4U) {
return "CSR must contain version, UDS certificates, DICE chain, and signed data. "
"However, the parsed CSR has " +
std::to_string(parsedCsr->asArray()->size()) + " entries.";
}
auto& version = parsedCsr->asArray()->get(0);
auto& udsCerts = parsedCsr->asArray()->get(1);
auto& diceCertChain = parsedCsr->asArray()->get(2);
auto& signedData = parsedCsr->asArray()->get(3);
if (!version || !version->asUint() || version->asUint()->value() != 3U) {
return "Version must be an unsigned integer and must be equal to 3.";
}
if (!udsCerts || !udsCerts->asMap()) {
return "UdsCerts must be an Map.";
}
if (!diceCertChain || !diceCertChain->asArray()) {
return "DiceCertChain must be an Array.";
}
if (!signedData || !signedData->asArray()) {
return "SignedData must be an Array.";
}
RpcHardwareInfo info;
provisionable->getHardwareInfo(&info);
if (version->asUint()->value() != info.versionNumber) {
return "CSR version (" + std::to_string(version->asUint()->value()) +
") does not match the remotely provisioned component version (" +
std::to_string(info.versionNumber) + ").";
}
// DICE chain is [ pubkey, + DiceChainEntry ]. Its format is the same as BCC from RKP v1-2.
auto diceContents = validateBcc(diceCertChain->asArray());
if (!diceContents) {
return diceContents.message() + "\n" + prettyPrint(diceCertChain.get());
}
if (diceContents->size() == 0U) {
return "The DICE chain is empty. It must contain at least one entry.";
}
auto& udsPub = diceContents->back().pubKey;
auto error = validateUdsCerts(*udsCerts->asMap(), udsPub);
if (!error.empty()) {
return error;
}
auto csrPayload = verifyAndParseCoseSign1(signedData->asArray(), udsPub, {} /* aad */);
if (!csrPayload) {
return csrPayload.message();
}
auto parsedCsrPayload = parseAndValidateCsrPayload(keysToSign, *csrPayload, provisionable,
challenge, isFactory);
if (!parsedCsrPayload) {
return parsedCsrPayload.message();
}
return *diceContents;
}
ErrMsgOr<std::vector<BccEntryData>> verifyFactoryCsr(const cppbor::Array& keysToSign,
const std::vector<uint8_t>& csr,
IRemotelyProvisionedComponent* provisionable,
const std::vector<uint8_t>& challenge) {
return verifyCsr(keysToSign, csr, provisionable, challenge, /*isFactory=*/true);
}
ErrMsgOr<std::vector<BccEntryData>> verifyProductionCsr(
const cppbor::Array& keysToSign, const std::vector<uint8_t>& csr,
IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge) {
return verifyCsr(keysToSign, csr, provisionable, challenge, /*isFactory=*/false);
}
} // namespace aidl::android::hardware::security::keymint::remote_prov