mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 11:36:00 +00:00
Merge changes from topic "check-device-info"
* changes: Move verifyProtectedData into remote_prov_utils Move the device info validation to a helper library
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#define LOG_TAG "VtsRemotelyProvisionableComponentTests"
|
||||
|
||||
#include <AndroidRemotelyProvisionedComponentDevice.h>
|
||||
@@ -58,26 +60,6 @@ using testing::MatchesRegex;
|
||||
using namespace remote_prov;
|
||||
using namespace keymaster;
|
||||
|
||||
std::set<std::string> getAllowedVbStates() {
|
||||
return {"green", "yellow", "orange"};
|
||||
}
|
||||
|
||||
std::set<std::string> getAllowedBootloaderStates() {
|
||||
return {"locked", "unlocked"};
|
||||
}
|
||||
|
||||
std::set<std::string> getAllowedSecurityLevels() {
|
||||
return {"tee", "strongbox"};
|
||||
}
|
||||
|
||||
std::set<std::string> getAllowedAttIdStates() {
|
||||
return {"locked", "open"};
|
||||
}
|
||||
|
||||
std::set<std::string> getAttestationIdEntrySet() {
|
||||
return {"brand", "manufacturer", "product", "model", "device"};
|
||||
}
|
||||
|
||||
bytevec string_to_bytevec(const char* s) {
|
||||
const uint8_t* p = reinterpret_cast<const uint8_t*>(s);
|
||||
return bytevec(p, p + strlen(s));
|
||||
@@ -387,177 +369,6 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
|
||||
}
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec> getSessionKey(ErrMsgOr<std::pair<bytevec, bytevec>>& senderPubkey) {
|
||||
if (rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_25519 ||
|
||||
rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_NONE) {
|
||||
return x25519_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey,
|
||||
senderPubkey->first, false /* senderIsA */);
|
||||
} else {
|
||||
return ECDH_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey,
|
||||
senderPubkey->first, false /* senderIsA */);
|
||||
}
|
||||
}
|
||||
|
||||
void checkProtectedData(const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
|
||||
const bytevec& keysToSignMac, const ProtectedData& protectedData,
|
||||
std::vector<BccEntryData>* bccOutput = nullptr) {
|
||||
auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
|
||||
ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
|
||||
ASSERT_TRUE(parsedProtectedData->asArray());
|
||||
ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);
|
||||
|
||||
auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
|
||||
ASSERT_TRUE(senderPubkey) << senderPubkey.message();
|
||||
EXPECT_EQ(senderPubkey->second, eekId_);
|
||||
|
||||
auto sessionKey = getSessionKey(senderPubkey);
|
||||
ASSERT_TRUE(sessionKey) << sessionKey.message();
|
||||
|
||||
auto protectedDataPayload =
|
||||
decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
|
||||
ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();
|
||||
|
||||
auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
|
||||
ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
|
||||
ASSERT_TRUE(parsedPayload->asArray());
|
||||
// Strongbox may contain additional certificate chain.
|
||||
EXPECT_LE(parsedPayload->asArray()->size(), 3U);
|
||||
|
||||
auto& signedMac = parsedPayload->asArray()->get(0);
|
||||
auto& bcc = parsedPayload->asArray()->get(1);
|
||||
ASSERT_TRUE(signedMac && signedMac->asArray());
|
||||
ASSERT_TRUE(bcc && bcc->asArray());
|
||||
|
||||
// BCC is [ pubkey, + BccEntry]
|
||||
auto bccContents = validateBcc(bcc->asArray());
|
||||
ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get());
|
||||
ASSERT_GT(bccContents->size(), 0U);
|
||||
|
||||
auto [deviceInfoMap, __2, deviceInfoErrMsg] = cppbor::parse(deviceInfo.deviceInfo);
|
||||
ASSERT_TRUE(deviceInfoMap) << "Failed to parse deviceInfo: " << deviceInfoErrMsg;
|
||||
ASSERT_TRUE(deviceInfoMap->asMap());
|
||||
checkDeviceInfo(*deviceInfoMap->asMap(), deviceInfo.deviceInfo);
|
||||
auto& signingKey = bccContents->back().pubKey;
|
||||
deviceInfoMap->asMap()->canonicalize();
|
||||
auto macKey = verifyAndParseCoseSign1(signedMac->asArray(), signingKey,
|
||||
cppbor::Array() // SignedMacAad
|
||||
.add(challenge_)
|
||||
.add(std::move(deviceInfoMap))
|
||||
.add(keysToSignMac)
|
||||
.encode());
|
||||
ASSERT_TRUE(macKey) << macKey.message();
|
||||
|
||||
auto coseMac0 = cppbor::Array()
|
||||
.add(cppbor::Map() // protected
|
||||
.add(ALGORITHM, HMAC_256)
|
||||
.canonicalize()
|
||||
.encode())
|
||||
.add(cppbor::Map()) // unprotected
|
||||
.add(keysToSign.encode()) // payload (keysToSign)
|
||||
.add(keysToSignMac); // tag
|
||||
|
||||
auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
|
||||
ASSERT_TRUE(macPayload) << macPayload.message();
|
||||
|
||||
if (bccOutput) {
|
||||
*bccOutput = std::move(*bccContents);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> assertAttribute(const cppbor::Map& devInfo,
|
||||
cppbor::MajorType majorType, std::string entryName) {
|
||||
const auto& val = devInfo.get(entryName);
|
||||
if (!val) return entryName + " is missing.\n";
|
||||
if (val->type() != majorType) return entryName + " has the wrong type.\n";
|
||||
switch (majorType) {
|
||||
case cppbor::TSTR:
|
||||
if (val->asTstr()->value().size() <= 0) {
|
||||
return entryName + " is present but the value is empty.\n";
|
||||
}
|
||||
break;
|
||||
case cppbor::BSTR:
|
||||
if (val->asBstr()->value().size() <= 0) {
|
||||
return entryName + " is present but the value is empty.\n";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void checkType(const cppbor::Map& devInfo, cppbor::MajorType majorType, std::string entryName) {
|
||||
if (auto error = assertAttribute(devInfo, majorType, entryName)) {
|
||||
FAIL() << *error;
|
||||
}
|
||||
}
|
||||
|
||||
void checkDeviceInfo(const cppbor::Map& deviceInfo, bytevec deviceInfoBytes) {
|
||||
EXPECT_EQ(deviceInfo.clone()->asMap()->canonicalize().encode(), deviceInfoBytes)
|
||||
<< "DeviceInfo ordering is non-canonical.";
|
||||
const auto& version = deviceInfo.get("version");
|
||||
ASSERT_TRUE(version);
|
||||
ASSERT_TRUE(version->asUint());
|
||||
RpcHardwareInfo info;
|
||||
provisionable_->getHardwareInfo(&info);
|
||||
ASSERT_EQ(version->asUint()->value(), info.versionNumber);
|
||||
std::set<std::string> allowList;
|
||||
std::string problemEntries;
|
||||
switch (version->asUint()->value()) {
|
||||
// These fields became mandated in version 2.
|
||||
case 2:
|
||||
for (auto attId : getAttestationIdEntrySet()) {
|
||||
if (auto errMsg = assertAttribute(deviceInfo, cppbor::TSTR, attId)) {
|
||||
problemEntries += *errMsg;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ("", problemEntries)
|
||||
<< problemEntries
|
||||
<< "Attestation IDs are missing or malprovisioned. If this test is being "
|
||||
"run against an early proto or EVT build, this error is probably WAI "
|
||||
"and indicates that Device IDs were not provisioned in the factory. If "
|
||||
"this error is returned on a DVT or later build revision, then "
|
||||
"something is likely wrong with the factory provisioning process.";
|
||||
// TODO: Refactor the KeyMint code that validates these fields and include it here.
|
||||
checkType(deviceInfo, cppbor::TSTR, "vb_state");
|
||||
allowList = getAllowedVbStates();
|
||||
EXPECT_NE(allowList.find(deviceInfo.get("vb_state")->asTstr()->value()),
|
||||
allowList.end());
|
||||
checkType(deviceInfo, cppbor::TSTR, "bootloader_state");
|
||||
allowList = getAllowedBootloaderStates();
|
||||
EXPECT_NE(allowList.find(deviceInfo.get("bootloader_state")->asTstr()->value()),
|
||||
allowList.end());
|
||||
checkType(deviceInfo, cppbor::BSTR, "vbmeta_digest");
|
||||
checkType(deviceInfo, cppbor::UINT, "system_patch_level");
|
||||
checkType(deviceInfo, cppbor::UINT, "boot_patch_level");
|
||||
checkType(deviceInfo, cppbor::UINT, "vendor_patch_level");
|
||||
checkType(deviceInfo, cppbor::UINT, "fused");
|
||||
EXPECT_LT(deviceInfo.get("fused")->asUint()->value(), 2); // Must be 0 or 1.
|
||||
checkType(deviceInfo, cppbor::TSTR, "security_level");
|
||||
allowList = getAllowedSecurityLevels();
|
||||
EXPECT_NE(allowList.find(deviceInfo.get("security_level")->asTstr()->value()),
|
||||
allowList.end());
|
||||
if (deviceInfo.get("security_level")->asTstr()->value() == "tee") {
|
||||
checkType(deviceInfo, cppbor::TSTR, "os_version");
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
checkType(deviceInfo, cppbor::TSTR, "security_level");
|
||||
allowList = getAllowedSecurityLevels();
|
||||
EXPECT_NE(allowList.find(deviceInfo.get("security_level")->asTstr()->value()),
|
||||
allowList.end());
|
||||
if (version->asUint()->value() == 1) {
|
||||
checkType(deviceInfo, cppbor::TSTR, "att_id_state");
|
||||
allowList = getAllowedAttIdStates();
|
||||
EXPECT_NE(allowList.find(deviceInfo.get("att_id_state")->asTstr()->value()),
|
||||
allowList.end());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
FAIL() << "Unrecognized version: " << version->asUint()->value();
|
||||
}
|
||||
}
|
||||
|
||||
bytevec eekId_;
|
||||
size_t testEekLength_;
|
||||
EekChain testEekChain_;
|
||||
@@ -584,7 +395,10 @@ TEST_P(CertificateRequestTest, EmptyRequest_testMode) {
|
||||
&protectedData, &keysToSignMac);
|
||||
ASSERT_TRUE(status.isOk()) << status.getMessage();
|
||||
|
||||
checkProtectedData(deviceInfo, cppbor::Array(), keysToSignMac, protectedData);
|
||||
auto result = verifyProductionProtectedData(
|
||||
deviceInfo, cppbor::Array(), keysToSignMac, protectedData, testEekChain_, eekId_,
|
||||
rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
|
||||
ASSERT_TRUE(result) << result.message();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,22 +420,24 @@ TEST_P(CertificateRequestTest, NewKeyPerCallInTestMode) {
|
||||
&protectedData, &keysToSignMac);
|
||||
ASSERT_TRUE(status.isOk()) << status.getMessage();
|
||||
|
||||
std::vector<BccEntryData> firstBcc;
|
||||
checkProtectedData(deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData,
|
||||
&firstBcc);
|
||||
auto firstBcc = verifyProductionProtectedData(
|
||||
deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData, testEekChain_,
|
||||
eekId_, rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
|
||||
ASSERT_TRUE(firstBcc) << firstBcc.message();
|
||||
|
||||
status = provisionable_->generateCertificateRequest(
|
||||
testMode, {} /* keysToSign */, testEekChain_.chain, challenge_, &deviceInfo,
|
||||
&protectedData, &keysToSignMac);
|
||||
ASSERT_TRUE(status.isOk()) << status.getMessage();
|
||||
|
||||
std::vector<BccEntryData> secondBcc;
|
||||
checkProtectedData(deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData,
|
||||
&secondBcc);
|
||||
auto secondBcc = verifyProductionProtectedData(
|
||||
deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData, testEekChain_,
|
||||
eekId_, rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
|
||||
ASSERT_TRUE(secondBcc) << secondBcc.message();
|
||||
|
||||
// Verify that none of the keys in the first BCC are repeated in the second one.
|
||||
for (const auto& i : firstBcc) {
|
||||
for (auto& j : secondBcc) {
|
||||
for (const auto& i : *firstBcc) {
|
||||
for (auto& j : *secondBcc) {
|
||||
ASSERT_THAT(i.pubKey, testing::Not(testing::ElementsAreArray(j.pubKey)))
|
||||
<< "Found a repeated pubkey in two generateCertificateRequest test mode calls";
|
||||
}
|
||||
@@ -664,7 +480,10 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testMode) {
|
||||
&keysToSignMac);
|
||||
ASSERT_TRUE(status.isOk()) << status.getMessage();
|
||||
|
||||
checkProtectedData(deviceInfo, cborKeysToSign_, keysToSignMac, protectedData);
|
||||
auto result = verifyProductionProtectedData(
|
||||
deviceInfo, cborKeysToSign_, keysToSignMac, protectedData, testEekChain_, eekId_,
|
||||
rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
|
||||
ASSERT_TRUE(result) << result.message();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ cc_library {
|
||||
],
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libbinder_ndk",
|
||||
"libcppbor_external",
|
||||
"libcppcose_rkp",
|
||||
"libcrypto",
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h"
|
||||
|
||||
#include <keymaster/cppcose/cppcose.h>
|
||||
|
||||
@@ -139,4 +141,40 @@ struct JsonOutput {
|
||||
JsonOutput jsonEncodeCsrWithBuild(const std::string instance_name,
|
||||
const cppbor::Array& csr);
|
||||
|
||||
/**
|
||||
* Parses a DeviceInfo structure from the given CBOR data. The parsed data is then validated to
|
||||
* ensure it contains the minimum required data at the time of manufacturing. This is only a
|
||||
* partial validation, as some fields may not be provisioned yet at the time this information
|
||||
* is parsed in the manufacturing process.
|
||||
*/
|
||||
ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateFactoryDeviceInfo(
|
||||
const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable);
|
||||
|
||||
/**
|
||||
* Parses a DeviceInfo structure from the given CBOR data. The parsed data is then validated to
|
||||
* ensure it is formatted correctly and that it contains the required values for Remote Key
|
||||
* Provisioning. This is a full validation, and assumes the device is provisioned as if it is
|
||||
* suitable for the end user.
|
||||
*/
|
||||
ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateProductionDeviceInfo(
|
||||
const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable);
|
||||
|
||||
/**
|
||||
* Verify the protected data as if the device is still early in the factory process and may not
|
||||
* have all device identifiers provisioned yet.
|
||||
*/
|
||||
ErrMsgOr<std::vector<BccEntryData>> verifyFactoryProtectedData(
|
||||
const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
|
||||
const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
|
||||
const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
|
||||
IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge);
|
||||
/**
|
||||
* Verify the protected data as if the device is a final production sample.
|
||||
*/
|
||||
ErrMsgOr<std::vector<BccEntryData>> verifyProductionProtectedData(
|
||||
const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
|
||||
const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
|
||||
const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
|
||||
IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge);
|
||||
|
||||
} // namespace aidl::android::hardware::security::keymint::remote_prov
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
*/
|
||||
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include "aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h"
|
||||
|
||||
#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
|
||||
#include <android-base/properties.h>
|
||||
@@ -441,4 +445,293 @@ JsonOutput jsonEncodeCsrWithBuild(const std::string instance_name, const cppbor:
|
||||
return JsonOutput::Ok(Json::writeString(factory, json));
|
||||
}
|
||||
|
||||
} // namespace aidl::android::hardware::security::keymint::remote_prov
|
||||
std::string checkMapEntry(bool isFactory, const cppbor::Map& devInfo, cppbor::MajorType majorType,
|
||||
const std::string& entryName) {
|
||||
const std::unique_ptr<cppbor::Item>& val = devInfo.get(entryName);
|
||||
if (!val) {
|
||||
return entryName + " is missing.\n";
|
||||
}
|
||||
if (val->type() != majorType) {
|
||||
return entryName + " has the wrong type.\n";
|
||||
}
|
||||
if (isFactory) {
|
||||
return "";
|
||||
}
|
||||
switch (majorType) {
|
||||
case cppbor::TSTR:
|
||||
if (val->asTstr()->value().size() <= 0) {
|
||||
return entryName + " is present but the value is empty.\n";
|
||||
}
|
||||
break;
|
||||
case cppbor::BSTR:
|
||||
if (val->asBstr()->value().size() <= 0) {
|
||||
return entryName + " is present but the value is empty.\n";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string checkMapEntry(bool isFactory, const cppbor::Map& devInfo, cppbor::MajorType majorType,
|
||||
const std::string& entryName, const cppbor::Array& allowList) {
|
||||
std::string error = checkMapEntry(isFactory, devInfo, majorType, entryName);
|
||||
if (!error.empty()) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (isFactory) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::unique_ptr<cppbor::Item>& val = devInfo.get(entryName);
|
||||
for (auto i = allowList.begin(); i != allowList.end(); ++i) {
|
||||
if (**i == *val) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return entryName + " has an invalid value.\n";
|
||||
}
|
||||
|
||||
ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateDeviceInfo(
|
||||
const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable,
|
||||
bool isFactory) {
|
||||
const cppbor::Array kValidVbStates = {"green", "yellow", "orange"};
|
||||
const cppbor::Array kValidBootloaderStates = {"locked", "unlocked"};
|
||||
const cppbor::Array kValidSecurityLevels = {"tee", "strongbox"};
|
||||
const cppbor::Array kValidAttIdStates = {"locked", "open"};
|
||||
const cppbor::Array kValidFused = {0, 1};
|
||||
|
||||
struct AttestationIdEntry {
|
||||
const char* id;
|
||||
bool alwaysValidate;
|
||||
};
|
||||
constexpr AttestationIdEntry kAttestationIdEntrySet[] = {{"brand", false},
|
||||
{"manufacturer", true},
|
||||
{"product", false},
|
||||
{"model", false},
|
||||
{"device", false}};
|
||||
|
||||
auto [parsedVerifiedDeviceInfo, ignore1, errMsg] = cppbor::parse(deviceInfoBytes);
|
||||
if (!parsedVerifiedDeviceInfo) {
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
std::unique_ptr<cppbor::Map> parsed(parsedVerifiedDeviceInfo->asMap());
|
||||
if (!parsed) {
|
||||
return "DeviceInfo must be a CBOR map.";
|
||||
}
|
||||
parsedVerifiedDeviceInfo.release();
|
||||
|
||||
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) + ").";
|
||||
}
|
||||
std::string error;
|
||||
switch (version->asUint()->value()) {
|
||||
case 2:
|
||||
for (const auto& entry : kAttestationIdEntrySet) {
|
||||
error += checkMapEntry(isFactory && !entry.alwaysValidate, *parsed, cppbor::TSTR,
|
||||
entry.id);
|
||||
}
|
||||
if (!error.empty()) {
|
||||
return error +
|
||||
"Attestation IDs are missing or malprovisioned. If this test is being\n"
|
||||
"run against an early proto or EVT build, this error is probably WAI\n"
|
||||
"and indicates that Device IDs were not provisioned in the factory. If\n"
|
||||
"this error is returned on a DVT or later build revision, then\n"
|
||||
"something is likely wrong with the factory provisioning process.";
|
||||
}
|
||||
// TODO: Refactor the KeyMint code that validates these fields and include it here.
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "vb_state", kValidVbStates);
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "bootloader_state",
|
||||
kValidBootloaderStates);
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::BSTR, "vbmeta_digest");
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "system_patch_level");
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "boot_patch_level");
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "vendor_patch_level");
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "fused", kValidFused);
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "security_level",
|
||||
kValidSecurityLevels);
|
||||
if (parsed->get("security_level") && parsed->get("security_level")->asTstr() &&
|
||||
parsed->get("security_level")->asTstr()->value() == "tee") {
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "os_version");
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "security_level",
|
||||
kValidSecurityLevels);
|
||||
error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "att_id_state",
|
||||
kValidAttIdStates);
|
||||
break;
|
||||
default:
|
||||
return "Unrecognized version: " + std::to_string(version->asUint()->value());
|
||||
}
|
||||
|
||||
if (!error.empty()) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return std::move(parsed);
|
||||
}
|
||||
|
||||
ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateFactoryDeviceInfo(
|
||||
const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable) {
|
||||
return parseAndValidateDeviceInfo(deviceInfoBytes, provisionable, /*isFactory=*/true);
|
||||
}
|
||||
|
||||
ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateProductionDeviceInfo(
|
||||
const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable) {
|
||||
return parseAndValidateDeviceInfo(deviceInfoBytes, provisionable, /*isFactory=*/false);
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec> getSessionKey(ErrMsgOr<std::pair<bytevec, bytevec>>& senderPubkey,
|
||||
const EekChain& eekChain, int32_t supportedEekCurve) {
|
||||
if (supportedEekCurve == RpcHardwareInfo::CURVE_25519 ||
|
||||
supportedEekCurve == RpcHardwareInfo::CURVE_NONE) {
|
||||
return x25519_HKDF_DeriveKey(eekChain.last_pubkey, eekChain.last_privkey,
|
||||
senderPubkey->first, false /* senderIsA */);
|
||||
} else {
|
||||
return ECDH_HKDF_DeriveKey(eekChain.last_pubkey, eekChain.last_privkey, senderPubkey->first,
|
||||
false /* senderIsA */);
|
||||
}
|
||||
}
|
||||
|
||||
ErrMsgOr<std::vector<BccEntryData>> verifyProtectedData(
|
||||
const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
|
||||
const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
|
||||
const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
|
||||
IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge,
|
||||
bool isFactory) {
|
||||
auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
|
||||
if (!parsedProtectedData) {
|
||||
return protDataErrMsg;
|
||||
}
|
||||
if (!parsedProtectedData->asArray()) {
|
||||
return "Protected data is not a CBOR array.";
|
||||
}
|
||||
if (parsedProtectedData->asArray()->size() != kCoseEncryptEntryCount) {
|
||||
return "The protected data COSE_encrypt structure must have " +
|
||||
std::to_string(kCoseEncryptEntryCount) + " entries, but it only has " +
|
||||
std::to_string(parsedProtectedData->asArray()->size());
|
||||
}
|
||||
|
||||
auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
|
||||
if (!senderPubkey) {
|
||||
return senderPubkey.message();
|
||||
}
|
||||
if (senderPubkey->second != eekId) {
|
||||
return "The COSE_encrypt recipient does not match the expected EEK identifier";
|
||||
}
|
||||
|
||||
auto sessionKey = getSessionKey(senderPubkey, eekChain, supportedEekCurve);
|
||||
if (!sessionKey) {
|
||||
return sessionKey.message();
|
||||
}
|
||||
|
||||
auto protectedDataPayload =
|
||||
decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
|
||||
if (!protectedDataPayload) {
|
||||
return protectedDataPayload.message();
|
||||
}
|
||||
|
||||
auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
|
||||
if (!parsedPayload) {
|
||||
return "Failed to parse payload: " + payloadErrMsg;
|
||||
}
|
||||
if (!parsedPayload->asArray()) {
|
||||
return "The protected data payload must be an Array.";
|
||||
}
|
||||
if (parsedPayload->asArray()->size() != 3U && parsedPayload->asArray()->size() != 2U) {
|
||||
return "The protected data payload must contain SignedMAC and BCC. It may optionally "
|
||||
"contain AdditionalDKSignatures. However, the parsed payload has " +
|
||||
std::to_string(parsedPayload->asArray()->size()) + " entries.";
|
||||
}
|
||||
|
||||
auto& signedMac = parsedPayload->asArray()->get(0);
|
||||
auto& bcc = parsedPayload->asArray()->get(1);
|
||||
if (!signedMac->asArray()) {
|
||||
return "The SignedMAC in the protected data payload is not an Array.";
|
||||
}
|
||||
if (!bcc->asArray()) {
|
||||
return "The BCC in the protected data payload is not an Array.";
|
||||
}
|
||||
|
||||
// BCC is [ pubkey, + BccEntry]
|
||||
auto bccContents = validateBcc(bcc->asArray());
|
||||
if (!bccContents) {
|
||||
return bccContents.message() + "\n" + prettyPrint(bcc.get());
|
||||
}
|
||||
if (bccContents->size() == 0U) {
|
||||
return "The BCC is empty. It must contain at least one entry.";
|
||||
}
|
||||
|
||||
auto deviceInfoResult =
|
||||
parseAndValidateDeviceInfo(deviceInfo.deviceInfo, provisionable, isFactory);
|
||||
if (!deviceInfoResult) {
|
||||
return deviceInfoResult.message();
|
||||
}
|
||||
std::unique_ptr<cppbor::Map> deviceInfoMap = deviceInfoResult.moveValue();
|
||||
auto& signingKey = bccContents->back().pubKey;
|
||||
auto macKey = verifyAndParseCoseSign1(signedMac->asArray(), signingKey,
|
||||
cppbor::Array() // SignedMacAad
|
||||
.add(challenge)
|
||||
.add(std::move(deviceInfoMap))
|
||||
.add(keysToSignMac)
|
||||
.encode());
|
||||
if (!macKey) {
|
||||
return macKey.message();
|
||||
}
|
||||
|
||||
auto coseMac0 = cppbor::Array()
|
||||
.add(cppbor::Map() // protected
|
||||
.add(ALGORITHM, HMAC_256)
|
||||
.canonicalize()
|
||||
.encode())
|
||||
.add(cppbor::Map()) // unprotected
|
||||
.add(keysToSign.encode()) // payload (keysToSign)
|
||||
.add(keysToSignMac); // tag
|
||||
|
||||
auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
|
||||
if (!macPayload) {
|
||||
return macPayload.message();
|
||||
}
|
||||
|
||||
return *bccContents;
|
||||
}
|
||||
|
||||
ErrMsgOr<std::vector<BccEntryData>> verifyFactoryProtectedData(
|
||||
const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
|
||||
const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
|
||||
const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
|
||||
IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge) {
|
||||
return verifyProtectedData(deviceInfo, keysToSign, keysToSignMac, protectedData, eekChain,
|
||||
eekId, supportedEekCurve, provisionable, challenge,
|
||||
/*isFactory=*/true);
|
||||
}
|
||||
|
||||
ErrMsgOr<std::vector<BccEntryData>> verifyProductionProtectedData(
|
||||
const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
|
||||
const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
|
||||
const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
|
||||
IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge) {
|
||||
return verifyProtectedData(deviceInfo, keysToSign, keysToSignMac, protectedData, eekChain,
|
||||
eekId, supportedEekCurve, provisionable, challenge,
|
||||
/*isFactory=*/false);
|
||||
}
|
||||
|
||||
} // namespace aidl::android::hardware::security::keymint::remote_prov
|
||||
Reference in New Issue
Block a user