From d8ce542ff9192c767dc1cda7d3105caf9bce0ddd Mon Sep 17 00:00:00 2001 From: Cong Lin Date: Wed, 29 Nov 2023 13:00:51 -0800 Subject: [PATCH] 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 --- .../oemcrypto/util/include/cbor_validator.h | 6 + .../util/include/device_info_validator.h | 54 +++++ .../oemcrypto/util/src/cbor_validator.cpp | 27 +++ .../util/src/device_info_validator.cpp | 227 ++++++++++++++++++ 4 files changed, 314 insertions(+) create mode 100644 libwvdrmengine/oemcrypto/util/include/device_info_validator.h create mode 100644 libwvdrmengine/oemcrypto/util/src/device_info_validator.cpp diff --git a/libwvdrmengine/oemcrypto/util/include/cbor_validator.h b/libwvdrmengine/oemcrypto/util/include/cbor_validator.h index 0f09c2b0..7d4c621b 100644 --- a/libwvdrmengine/oemcrypto/util/include/cbor_validator.h +++ b/libwvdrmengine/oemcrypto/util/include/cbor_validator.h @@ -68,6 +68,12 @@ class CborValidator { // Writes validation output |msg| to |validate_messages_|, and updates // |message_status_| if the |status| is more severe than the current value. 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; private: diff --git a/libwvdrmengine/oemcrypto/util/include/device_info_validator.h b/libwvdrmengine/oemcrypto/util/include/device_info_validator.h new file mode 100644 index 00000000..d0d298de --- /dev/null +++ b/libwvdrmengine/oemcrypto/util/include/device_info_validator.h @@ -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 +#include +#include + +#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& 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 device_info_bytes_; +}; +} // namespace util +} // namespace wvoec +#endif // WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_ diff --git a/libwvdrmengine/oemcrypto/util/src/cbor_validator.cpp b/libwvdrmengine/oemcrypto/util/src/cbor_validator.cpp index 59b95704..dce72a82 100644 --- a/libwvdrmengine/oemcrypto/util/src/cbor_validator.cpp +++ b/libwvdrmengine/oemcrypto/util/src/cbor_validator.cpp @@ -109,5 +109,32 @@ void CborValidator::AddValidationMessage(CborMessageStatus status, validate_messages_.push_back({status, msg}); 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 wvoec diff --git a/libwvdrmengine/oemcrypto/util/src/device_info_validator.cpp b/libwvdrmengine/oemcrypto/util/src/device_info_validator.cpp new file mode 100644 index 00000000..9877f43b --- /dev/null +++ b/libwvdrmengine/oemcrypto/util/src/device_info_validator.cpp @@ -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 + +#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 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& 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 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 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 ""; + } + // 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() + ? "" + : 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& bytes = entry_value->asBstr()->value(); + const std::string val = + bytes.empty() ? "" : 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