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:
commit
c4e2e934ff
3 changed files with 453 additions and 21 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,22 +530,27 @@ ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateDeviceInfo(
|
|||
if (parsed->clone()->asMap()->canonicalize().encode() != deviceInfoBytes) {
|
||||
return "DeviceInfo ordering is non-canonical.";
|
||||
}
|
||||
const std::unique_ptr<cppbor::Item>& version = parsed->get("version");
|
||||
if (!version) {
|
||||
return "Device info is missing version";
|
||||
}
|
||||
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) + ").";
|
||||
if (info.versionNumber < 3) {
|
||||
const std::unique_ptr<cppbor::Item>& version = parsed->get("version");
|
||||
if (!version) {
|
||||
return "Device info is missing version";
|
||||
}
|
||||
if (!version->asUint()) {
|
||||
return "version must be an unsigned integer";
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
} // namespace aidl::android::hardware::security::keymint::remote_prov
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue