Add Device info validator to oemcrypto util and unit tests
Validator that can parse and validate device info Cbor object. This is to support better prov40 unit tests regarding OEMCrypto_GetDeviceInformation() later. Test: opk_ta_p40 Bug: 300304834 Change-Id: Ic260a6626dffcbef5d6b386263839499f83a69db
This commit is contained in:
@@ -68,6 +68,12 @@ class CborValidator {
|
|||||||
// Writes validation output |msg| to |validate_messages_|, and updates
|
// Writes validation output |msg| to |validate_messages_|, and updates
|
||||||
// |message_status_| if the |status| is more severe than the current value.
|
// |message_status_| if the |status| is more severe than the current value.
|
||||||
void AddValidationMessage(CborMessageStatus status, const std::string& msg);
|
void AddValidationMessage(CborMessageStatus status, const std::string& msg);
|
||||||
|
static const cppbor::Item* GetMapEntry(const cppbor::Map& map,
|
||||||
|
const std::string& entry_name);
|
||||||
|
// Checks whether an entry with |entry_name| and |major_type| exists in |map|.
|
||||||
|
static std::string CheckMapEntry(const cppbor::Map& map,
|
||||||
|
cppbor::MajorType major_type,
|
||||||
|
const std::string& entry_name);
|
||||||
CborMessageStatus message_status_ = kCborUninitialized;
|
CborMessageStatus message_status_ = kCborUninitialized;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||||
|
// source code may only be used and distributed under the Widevine License
|
||||||
|
// Agreement.
|
||||||
|
//
|
||||||
|
// Reference implementation utilities of OEMCrypto APIs
|
||||||
|
//
|
||||||
|
#ifndef WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_
|
||||||
|
#define WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cbor_validator.h"
|
||||||
|
#include "cppbor.h"
|
||||||
|
|
||||||
|
namespace wvoec {
|
||||||
|
namespace util {
|
||||||
|
// DeviceInfoValidator parses and validates a Cbor struct of DeviceInfo used by
|
||||||
|
// Provisioning 4.0. DeviceInfo definition:
|
||||||
|
// https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/DeviceInfoV3.cddl
|
||||||
|
class DeviceInfoValidator : public CborValidator {
|
||||||
|
public:
|
||||||
|
explicit DeviceInfoValidator(int version_number)
|
||||||
|
: version_number_(version_number) {}
|
||||||
|
DeviceInfoValidator() = delete;
|
||||||
|
virtual ~DeviceInfoValidator() override = default;
|
||||||
|
DeviceInfoValidator(const DeviceInfoValidator&) = delete;
|
||||||
|
DeviceInfoValidator& operator=(const DeviceInfoValidator&) = delete;
|
||||||
|
|
||||||
|
// Decodes |device_info| and sets |message_status_|.
|
||||||
|
virtual CborMessageStatus Parse(
|
||||||
|
const std::vector<uint8_t>& device_info) override;
|
||||||
|
// Verifies the Cbor struct of a client generated device info.
|
||||||
|
virtual CborMessageStatus Validate() override;
|
||||||
|
// Outputs DeviceInfo in YAML.
|
||||||
|
virtual std::string GetFormattedMessage() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Checks whether a device info entry with |entry_name| and |major_type|
|
||||||
|
// exists in |device_info| map.
|
||||||
|
void CheckDeviceInfoMapEntry(const cppbor::Map& device_info,
|
||||||
|
cppbor::MajorType major_type,
|
||||||
|
const std::string& entry_name);
|
||||||
|
// Used to generate formatted message.
|
||||||
|
std::stringstream msg_ss_;
|
||||||
|
// Device info version. Validations are done based on the version number.
|
||||||
|
int version_number_;
|
||||||
|
// Saved Cbor-encoded device info.
|
||||||
|
std::vector<uint8_t> device_info_bytes_;
|
||||||
|
};
|
||||||
|
} // namespace util
|
||||||
|
} // namespace wvoec
|
||||||
|
#endif // WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_
|
||||||
@@ -109,5 +109,32 @@ void CborValidator::AddValidationMessage(CborMessageStatus status,
|
|||||||
validate_messages_.push_back({status, msg});
|
validate_messages_.push_back({status, msg});
|
||||||
if (status > message_status_) message_status_ = status;
|
if (status > message_status_) message_status_ = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(b/314141962): Replace this with the map lookup function in cppbor
|
||||||
|
// library
|
||||||
|
const cppbor::Item* CborValidator::GetMapEntry(const cppbor::Map& map,
|
||||||
|
const std::string& entry_name) {
|
||||||
|
for (auto const& entry : map) {
|
||||||
|
if (!entry.first->asTstr()) continue;
|
||||||
|
const std::string& name = entry.first->asTstr()->value();
|
||||||
|
if (name == entry_name) return entry.second.get();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CborValidator::CheckMapEntry(const cppbor::Map& map,
|
||||||
|
cppbor::MajorType major_type,
|
||||||
|
const std::string& entry_name) {
|
||||||
|
const cppbor::Item* value = GetMapEntry(map, entry_name);
|
||||||
|
if (!value) {
|
||||||
|
return entry_name + " is missing.";
|
||||||
|
}
|
||||||
|
if (value->type() != major_type) {
|
||||||
|
return entry_name + " has the wrong type. Expect: " +
|
||||||
|
CppborMajorTypeToString(major_type) +
|
||||||
|
", actual: " + CppborMajorTypeToString(value->type());
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
} // namespace util
|
} // namespace util
|
||||||
} // namespace wvoec
|
} // namespace wvoec
|
||||||
|
|||||||
227
libwvdrmengine/oemcrypto/util/src/device_info_validator.cpp
Normal file
227
libwvdrmengine/oemcrypto/util/src/device_info_validator.cpp
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||||
|
// source code may only be used and distributed under the Widevine License
|
||||||
|
// Agreement.
|
||||||
|
//
|
||||||
|
// Reference implementation utilities of OEMCrypto APIs
|
||||||
|
//
|
||||||
|
#include "device_info_validator.h"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "string_conversions.h"
|
||||||
|
|
||||||
|
namespace wvoec {
|
||||||
|
namespace util {
|
||||||
|
namespace {
|
||||||
|
// Number of required device info properties returned from TEE for DeviceInfo
|
||||||
|
// version v3.
|
||||||
|
constexpr uint32_t kNumTeeDeviceInfoEntriesV3 = 14;
|
||||||
|
const std::vector<std::string> kDeviceInfoKeysV3 = {"brand",
|
||||||
|
"manufacturer",
|
||||||
|
"product",
|
||||||
|
"model",
|
||||||
|
"device",
|
||||||
|
"vb_state",
|
||||||
|
"bootloader_state",
|
||||||
|
"vbmeta_digest",
|
||||||
|
"os_version",
|
||||||
|
"system_patch_level",
|
||||||
|
"boot_patch_level",
|
||||||
|
"vendor_patch_level",
|
||||||
|
"security_level",
|
||||||
|
"fused"};
|
||||||
|
|
||||||
|
struct AttestationIdEntry {
|
||||||
|
const char* id;
|
||||||
|
bool alwaysValidate;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attestation Id and whether it is required.
|
||||||
|
constexpr AttestationIdEntry kAttestationIdEntrySet[] = {{"brand", false},
|
||||||
|
{"manufacturer", true},
|
||||||
|
{"product", true},
|
||||||
|
{"model", true},
|
||||||
|
{"device", false}};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CborMessageStatus DeviceInfoValidator::Parse(
|
||||||
|
const std::vector<uint8_t>& device_info) {
|
||||||
|
message_status_ = CborValidator::Parse(device_info);
|
||||||
|
device_info_bytes_ = device_info;
|
||||||
|
return message_status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
CborMessageStatus DeviceInfoValidator::Validate() {
|
||||||
|
if (message_status_ != kCborParseOk) return message_status_;
|
||||||
|
std::unique_ptr<cppbor::Item> parsed_device_info =
|
||||||
|
std::get<0>(parse_result())->clone();
|
||||||
|
if (!parsed_device_info) {
|
||||||
|
AddValidationMessage(kCborValidateFatal, "Device info is empty.");
|
||||||
|
return message_status_;
|
||||||
|
}
|
||||||
|
cppbor::Map* device_info_map = parsed_device_info->asMap();
|
||||||
|
if (!device_info_map) {
|
||||||
|
AddValidationMessage(
|
||||||
|
kCborValidateFatal,
|
||||||
|
"Device info is not a CBOR map. Actual type: " +
|
||||||
|
CppborMajorTypeToString(parsed_device_info->type()));
|
||||||
|
return message_status_;
|
||||||
|
}
|
||||||
|
if (device_info_map->canonicalize().encode() != device_info_bytes_) {
|
||||||
|
AddValidationMessage(kCborValidateError,
|
||||||
|
"Device info ordering is non-canonical.");
|
||||||
|
}
|
||||||
|
const cppbor::Item* security_level =
|
||||||
|
GetMapEntry(*device_info_map, "security_level");
|
||||||
|
const bool is_tee_device_info = security_level && security_level->asTstr() &&
|
||||||
|
security_level->asTstr()->value() == "tee";
|
||||||
|
std::set<std::string> previous_keys;
|
||||||
|
switch (version_number_) {
|
||||||
|
case 3:
|
||||||
|
if (is_tee_device_info &&
|
||||||
|
device_info_map->size() != kNumTeeDeviceInfoEntriesV3) {
|
||||||
|
AddValidationMessage(
|
||||||
|
kCborValidateError,
|
||||||
|
"Incorrect number of TEE device info entries. Expected " +
|
||||||
|
std::to_string(kNumTeeDeviceInfoEntriesV3) + " but got " +
|
||||||
|
std::to_string(device_info_map->size()));
|
||||||
|
}
|
||||||
|
// TEE IRPC instances require all entries to be present in device info.
|
||||||
|
// Non-TEE instances may omit `os_version`.
|
||||||
|
if (!is_tee_device_info &&
|
||||||
|
(device_info_map->size() != kNumTeeDeviceInfoEntriesV3 &&
|
||||||
|
device_info_map->size() != kNumTeeDeviceInfoEntriesV3 - 1)) {
|
||||||
|
AddValidationMessage(
|
||||||
|
kCborValidateError,
|
||||||
|
"Incorrect number of non-TEE device info entries. Expected " +
|
||||||
|
std::to_string(kNumTeeDeviceInfoEntriesV3 - 1) + " but got " +
|
||||||
|
std::to_string(device_info_map->size()));
|
||||||
|
}
|
||||||
|
for (auto const& entry : *device_info_map) {
|
||||||
|
if (!entry.first->asTstr()) {
|
||||||
|
AddValidationMessage(
|
||||||
|
kCborValidateError,
|
||||||
|
"Unexpected entry key type. Expected TSTR, but got " +
|
||||||
|
CppborMajorTypeToString(entry.first->type()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string& key = entry.first->asTstr()->value();
|
||||||
|
if (previous_keys.find(key) != previous_keys.end()) {
|
||||||
|
AddValidationMessage(kCborValidateError,
|
||||||
|
"Duplicate device info entry: " + key);
|
||||||
|
}
|
||||||
|
previous_keys.insert(key);
|
||||||
|
if (std::find(kDeviceInfoKeysV3.begin(), kDeviceInfoKeysV3.end(),
|
||||||
|
key) == kDeviceInfoKeysV3.end()) {
|
||||||
|
AddValidationMessage(kCborValidateError,
|
||||||
|
"Unrecognized device info entry: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Checks for the required fields that only apply to v3.
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||||
|
"system_patch_level");
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||||
|
"boot_patch_level");
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||||
|
"vendor_patch_level");
|
||||||
|
// Fall through
|
||||||
|
CORE_UTIL_FALLTHROUGH;
|
||||||
|
case 2:
|
||||||
|
for (const auto& entry : kAttestationIdEntrySet) {
|
||||||
|
if (entry.alwaysValidate) {
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, entry.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "vb_state");
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR,
|
||||||
|
"bootloader_state");
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::BSTR, "vbmeta_digest");
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||||
|
"system_patch_level");
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||||
|
"boot_patch_level");
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||||
|
"vendor_patch_level");
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, "fused");
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "security_level");
|
||||||
|
if (is_tee_device_info) {
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "os_version");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "security_level");
|
||||||
|
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "att_id_state");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
AddValidationMessage(
|
||||||
|
kCborValidateFatal,
|
||||||
|
"Unrecognized version: " + std::to_string(version_number_));
|
||||||
|
return message_status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk;
|
||||||
|
return message_status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceInfoValidator::CheckDeviceInfoMapEntry(
|
||||||
|
const cppbor::Map& device_info, cppbor::MajorType major_type,
|
||||||
|
const std::string& entry_name) {
|
||||||
|
const std::string error = CheckMapEntry(device_info, major_type, entry_name);
|
||||||
|
if (!error.empty()) {
|
||||||
|
AddValidationMessage(kCborValidateError, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DeviceInfoValidator::GetFormattedMessage() const {
|
||||||
|
if (message_status_ == kCborUninitialized ||
|
||||||
|
message_status_ == kCborParseError ||
|
||||||
|
message_status_ == kCborValidateFatal) {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
const cppbor::Item* parsed_item = std::get<0>(parse_result()).get();
|
||||||
|
if (parsed_item == nullptr) {
|
||||||
|
return "<null>";
|
||||||
|
}
|
||||||
|
// Writes YAML-formatted output to |msg_ss_|.
|
||||||
|
std::stringstream msg_ss;
|
||||||
|
msg_ss << "---\n";
|
||||||
|
msg_ss << "DEVICE INFO MAP:\n";
|
||||||
|
|
||||||
|
for (auto const& entry : *(parsed_item->asMap())) {
|
||||||
|
auto const& entry_value = entry.second;
|
||||||
|
// Device info map only allows TSTR key type.
|
||||||
|
if (!entry.first->asTstr()) continue;
|
||||||
|
const std::string& name = entry.first->asTstr()->value();
|
||||||
|
msg_ss << " " << name << ": ";
|
||||||
|
switch (entry_value->type()) {
|
||||||
|
case cppbor::TSTR: {
|
||||||
|
const std::string val = entry_value->asTstr()->value().empty()
|
||||||
|
? "<null>"
|
||||||
|
: entry_value->asTstr()->value();
|
||||||
|
msg_ss << val << "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case cppbor::UINT:
|
||||||
|
msg_ss << std::to_string(entry_value->asUint()->value()) << "\n";
|
||||||
|
break;
|
||||||
|
case cppbor::NINT:
|
||||||
|
msg_ss << std::to_string(entry_value->asNint()->value()) << "\n";
|
||||||
|
break;
|
||||||
|
case cppbor::BSTR: {
|
||||||
|
const std::vector<uint8_t>& bytes = entry_value->asBstr()->value();
|
||||||
|
const std::string val =
|
||||||
|
bytes.empty() ? "<null>" : wvutil::b2a_hex(bytes);
|
||||||
|
msg_ss << val << "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
msg_ss << "Unsupported type ("
|
||||||
|
<< CppborMajorTypeToString(entry_value->type()) << ")\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg_ss << "...\n";
|
||||||
|
return msg_ss.str();
|
||||||
|
}
|
||||||
|
} // namespace util
|
||||||
|
} // namespace wvoec
|
||||||
Reference in New Issue
Block a user