Source release 19.1.0

This commit is contained in:
Matt Feddersen
2024-03-28 19:21:54 -07:00
parent 28ec8548c6
commit b8bdfccebe
182 changed files with 10645 additions and 2040 deletions

View File

@@ -0,0 +1,564 @@
// 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 "bcc_validator.h"
#include <stddef.h>
#include <cstdint>
#include <iomanip>
#include <string>
#include <unordered_set>
#include <vector>
#include <openssl/evp.h>
#include "oemcrypto_ecc_key.h"
#include "string_conversions.h"
namespace wvoec {
namespace util {
namespace {
// The BCC is encoded using RFC 8949- Concise Binary Object Representation
// (CBOR).
// The full definition of the following enums can be found here:
// go/remote-provisioning-hal#bcc.
// The device key is encoded in a cbor map. The key values are a mix of
// positive and negative integer values.
enum {
MAP_KEY_DEVICE_KEY_TYPE = 1,
MAP_KEY_DEVICE_KEY_ALGORITHM = 3,
MAP_KEY_DEVICE_KEY_OPS = 4,
MAP_KEY_DEVICE_KEY_CURVE = -1,
MAP_KEY_DEVICE_KEY_BYTES_0 = -2,
MAP_KEY_DEVICE_KEY_BYTES_1 = -3,
};
// The device key may be encoded in the BCC as either X,Y elliptic curve
// coordinates, or as raw bytes. The value is identified using
// MAP_KEY_DEVICE_KEY_TYPE.
enum {
DEVICE_KEY_ENCODING_UNKNOWN = 0,
DEVICE_KEY_BYTE_STRING = 1,
DEVICE_KEY_OCTET_PAIR = 2,
};
// Android/Widevine Dice Attestation allows two signing models. This is
// identified using MAP_KEY_DEVICE_KEY_ALGORITHM.
enum {
DEVICE_KEY_ALGORITHM_ES256 = -7, // EC key with SHA-256
DEVICE_KEY_ALGORITHM_EDDSA = -8, // Pure ED25519.
DEVICE_KEY_ALGORITHM_ES384 = -35, // EC key with SHA-384
};
// The curve used to generate the device public key is identified using the
// MAP_KEY_DEVICE_KEY_CURVE.
enum {
DEVICE_KEY_CURVE_P256 = 1,
DEVICE_KEY_CURVE_P384 = 2,
DEVICE_KEY_CURVE_ED25519 = 6,
};
// Sized to hold a component of a P256 public key uncompressed point compatible
// with X9.62. The key is formatted in an Z/X/Y format in which Z == 0x04 and X
// and Y are the public key coordinates. X and Y are each 32 bytes.
constexpr int kP256KeyComponentSize = 256 / 8;
// Sized to hold a P384 public key uncompressed point compatible with X9.62.
// The key is formatted in an Z/X/Y format in which Z == 0x04 and X and Y are
// the public key coordinates. X and Y are each 48 bytes.
constexpr int kP384KeyComponentSize = 384 / 8;
constexpr int kMarshaledP256KeySize = kP256KeyComponentSize * 2 + 1;
constexpr int kMarshaledP384KeySize = kP384KeyComponentSize * 2 + 1;
constexpr char kMarshaledECKeyZValue = 0x04;
constexpr int kED25519KeyDataItemSize = 32;
// The Issuer field key in BccEntryPayload.
constexpr int64_t kIssuer = 1;
// The Subject field key in BccEntryPayload.
constexpr int64_t kSubject = 2;
// The SubjectPublicKey field key in BccEntryPayload.
constexpr int64_t kSubjectPublicKey = -4670552;
// This signature context is defined by COSE SIGN1.
constexpr char kSignatureContextString[] = "Signature1";
struct IssuerSubject {
std::string issuer;
std::string subject;
bool IsValid() const { return !issuer.empty() && !subject.empty(); }
void PrintTo(std::vector<std::string>& fmt_msgs) const {
fmt_msgs.push_back("Issuer: ");
fmt_msgs.back().append(issuer.empty() ? "<missing>" : issuer);
fmt_msgs.push_back("Subject: ");
fmt_msgs.back().append(subject.empty() ? "<missing>" : subject);
}
};
IssuerSubject GetIssuerSubjectFromBccEntryPayload(
const cppbor::Map* bcc_entry_payload) {
IssuerSubject ret;
for (size_t i = 0; i < bcc_entry_payload->size(); ++i) {
const auto& entry = (*bcc_entry_payload)[i];
if (entry.first == nullptr || entry.first->asInt() == nullptr ||
entry.second == nullptr || entry.second->asTstr() == nullptr) {
continue;
}
const auto& value = entry.second->asTstr()->value();
if (entry.first->asInt()->value() == kIssuer) {
ret.issuer = value.empty() ? "<empty>" : value;
} else if (entry.first->asInt()->value() == kSubject) {
ret.subject = value.empty() ? "<empty>" : value;
}
}
return ret;
}
const cppbor::Bstr* GetSubjectPublicKeyFromBccEntryPayload(
const cppbor::Map* bcc_entry_payload) {
for (size_t i = 0; i < bcc_entry_payload->size(); ++i) {
const auto& entry = (*bcc_entry_payload)[i];
if (entry.first == nullptr || entry.first->asInt() == nullptr ||
entry.second == nullptr) {
continue;
}
if (entry.first->asInt()->value() == kSubjectPublicKey) {
return entry.second->asBstr();
}
}
return nullptr;
}
void AddMessages(std::stringstream& ss,
const std::vector<std::string>& fmt_msgs, int indent) {
const std::string spaces = std::string(indent * 2, ' ');
for (auto& msg : fmt_msgs) {
ss << spaces << msg << "\n";
}
}
} // namespace
bool BccValidator::VerifySignature(const BccPublicKeyInfo& signing_key,
const std::vector<uint8_t>& message,
const std::vector<uint8_t>& signature) {
if (signing_key.signature_algorithm == kBccEdDsa) {
constexpr size_t kEd25519SignatureLength = 64;
// ED25519 incorporates SHA512 into the signing algorithm.
if (signature.size() != kEd25519SignatureLength) {
AddValidationMessage(
kCborValidateError,
"Signature has unexpected size: " + std::to_string(signature.size()));
return false;
}
EVP_PKEY* pkey = nullptr;
if ((pkey = EVP_PKEY_new_raw_public_key(
EVP_PKEY_ED25519, nullptr,
reinterpret_cast<const uint8_t*>(signing_key.key_bytes.data()),
signing_key.key_bytes.size())) == nullptr) {
AddValidationMessage(
kCborValidateError,
"Can not create EVP_PKEY_ED25519 from the public key info.");
return false;
}
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
const bool res =
EVP_DigestVerifyInit(md_ctx, nullptr, nullptr, nullptr, pkey) &&
EVP_DigestVerify(md_ctx, signature.data(), signature.size(),
message.data(), message.size()) == 1;
EVP_MD_CTX_free(md_ctx);
EVP_PKEY_free(pkey);
return res;
}
if (signing_key.signature_algorithm == kBccEcdsaSha256 ||
signing_key.signature_algorithm == kBccEcdsaSha384) {
const EccCurve curve = (signing_key.signature_algorithm == kBccEcdsaSha256)
? EccCurve::kEccSecp256r1
: EccCurve::kEccSecp384r1;
std::unique_ptr<EccPublicKey> key = EccPublicKey::LoadKeyPoint(
curve, reinterpret_cast<const uint8_t*>(signing_key.key_bytes.data()),
signing_key.key_bytes.size());
if (!key) {
AddValidationMessage(kCborValidateError,
"Can not create ECPublicKey from raw EC KeyPoint.");
return false;
}
const OEMCryptoResult res = key->VerifyRawSignature(
message.data(), message.size(), signature.data(), signature.size());
return (res == OEMCrypto_SUCCESS);
}
AddValidationMessage(kCborValidateError,
"Unknown signature algorithm: " +
std::to_string(signing_key.signature_algorithm));
return false;
}
CborMessageStatus BccValidator::Validate() {
if (message_status_ != kCborParseOk) return message_status_;
const cppbor::Item* parsed_bcc = std::get<0>(parse_result()).get();
if (parsed_bcc == nullptr) {
AddValidationMessage(kCborValidateFatal, "BCC is empty.");
return message_status_;
}
if (parsed_bcc->asArray() == nullptr) {
AddValidationMessage(kCborValidateFatal,
"BCC is not a CBOR array. Actual type: " +
CppborMajorTypeToString(parsed_bcc->type()));
return message_status_;
}
const cppbor::Array* bcc_array = parsed_bcc->asArray();
if (bcc_array->size() < 2) {
AddValidationMessage(kCborValidateFatal,
"BCC should contain at least two elements. Actual: " +
std::to_string(bcc_array->size()));
return message_status_;
}
// Writes YAML-formatted output to |msg_ss_| during validation.
msg_ss_.str(std::string());
msg_ss_ << "---"
<< "\n";
msg_ss_ << "DEVICE PUBLIC KEY:\n";
// The first element in the array contains the root device public key
// definition.
const cppbor::Map* device_public_key_map = (*bcc_array)[0]->asMap();
if (device_public_key_map == nullptr) {
AddValidationMessage(
kCborValidateFatal,
"Device public key info is not a CBOR map. Actual type: " +
CppborMajorTypeToString((*bcc_array)[0]->type()));
return message_status_;
}
BccPublicKeyInfo root_pub_key;
std::vector<std::string> key_value_texts; // for pretty print
CborMessageStatus status = ProcessSubjectPublicKeyInfo(
*device_public_key_map, key_value_texts, &root_pub_key);
AddMessages(msg_ss_, key_value_texts, 1);
if (status == kCborValidateFatal) return status;
BccPublicKeyInfo leaf_pub_key = root_pub_key;
msg_ss_ << "BCC ENTRY:\n";
// Parse and verify each certificate in the chain. The structure of thr
// entries are COSE_Sign1 (untagged). leaf_pub_key is being updated while we
// process the chain.
for (size_t i = 1; i < bcc_array->size(); ++i) {
msg_ss_ << "- CDI PUBLIC KEY INDEX: " << i << "\n";
const cppbor::Array* bcc_entry = (*bcc_array)[i]->asArray();
if (bcc_entry == nullptr) {
AddValidationMessage(kCborValidateFatal,
"BCC entry is empty at index " + std::to_string(i));
return message_status_;
}
if (bcc_entry->size() != 4) {
AddValidationMessage(kCborValidateFatal,
"BCC entry should contain 4 items. Actual: " +
std::to_string(bcc_entry->size()));
return message_status_;
}
// Skip CoseSign1 signature verification here, only extract pub keys
if ((*bcc_entry)[0]->type() != cppbor::BSTR ||
(*bcc_entry)[1]->type() != cppbor::MAP ||
(*bcc_entry)[2]->type() != cppbor::BSTR ||
(*bcc_entry)[3]->type() != cppbor::BSTR) {
AddValidationMessage(kCborValidateFatal, "Invalid BCC entry type.");
return message_status_;
}
// Signature verification Step 1: construct and encode signature input
const std::vector<uint8_t>& protected_bytes =
(*bcc_entry)[0]->asBstr()->value();
// Index 1 is unprotected parameters, which is ignored.
const std::vector<uint8_t>& payload = (*bcc_entry)[2]->asBstr()->value();
const std::vector<uint8_t>& actual_signature =
(*bcc_entry)[3]->asBstr()->value();
const std::vector<uint8_t> signature_input =
cppbor::Array()
.add(kSignatureContextString)
.add(protected_bytes)
.add(/* AAD */ std::vector<uint8_t>())
.add(payload)
.encode();
// Signature verification Step 2: verify
if (!VerifySignature(leaf_pub_key, signature_input, actual_signature)) {
AddValidationMessage(
kCborValidateError,
"Failed to verify the signature for BCC entry index: " +
std::to_string(i));
}
key_value_texts.clear();
BccPublicKeyInfo entry_pub_key;
status =
ProcessDiceChainEntryPayload(payload, key_value_texts, &entry_pub_key);
AddMessages(msg_ss_, key_value_texts, 1);
if (status == kCborValidateFatal) return status;
leaf_pub_key = std::move(entry_pub_key);
}
// If the size of the BCC array (including device pub key) is 2, then it
// must be a de-generated BCC, which means the second element in the array
// is a self-signed entry. The entry's public key should be identical to the
// device's public key.
if (bcc_array->size() == 2) {
// self-signed BCC entry
if (leaf_pub_key.key_bytes != root_pub_key.key_bytes) {
AddValidationMessage(kCborValidateError,
"The public key of a self-signed entry should be "
"identical to its device public key.");
}
}
msg_ss_ << "...\n";
if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk;
return message_status_;
}
CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo(
const cppbor::Map& public_key_info_map, std::vector<std::string>& fmt_msgs,
BccPublicKeyInfo* public_key_info) {
int key_encoding_format = DEVICE_KEY_ENCODING_UNKNOWN;
std::vector<uint8_t> device_key_bytes_0;
std::vector<uint8_t> device_key_bytes_1;
std::unordered_set<int64_t> key_set;
for (size_t index = 0; index < public_key_info_map.size(); ++index) {
std::pair<const std::unique_ptr<cppbor::Item>&,
const std::unique_ptr<cppbor::Item>&>
entry = public_key_info_map[index];
if (entry.first->type() != cppbor::NINT &&
entry.first->type() != cppbor::UINT) {
AddValidationMessage(kCborValidateFatal,
"Invalid key type in public key info map: " +
CppborMajorTypeToString(entry.first->type()));
return kCborValidateFatal;
}
const int64_t map_key = entry.first->asInt()->value();
switch (map_key) {
case MAP_KEY_DEVICE_KEY_TYPE: {
if (entry.second->type() != cppbor::UINT) {
AddValidationMessage(
kCborValidateFatal,
"Invalid value type in public key info map for "
"key MAP_KEY_DEVICE_KEY_TYPE: " +
CppborMajorTypeToString(entry.second->type()));
return kCborValidateFatal;
}
std::string kv = "key encoding format: ";
const int64_t value = entry.second->asUint()->value();
if (value == DEVICE_KEY_OCTET_PAIR) {
key_encoding_format = DEVICE_KEY_OCTET_PAIR;
kv += "DEVICE_KEY_OCTET_PAIR";
} else if (value == DEVICE_KEY_BYTE_STRING) {
key_encoding_format = DEVICE_KEY_BYTE_STRING;
kv += "DEVICE_KEY_BYTE_STRING";
} else {
AddValidationMessage(kCborValidateError,
"Invalid value in public key info map for key "
"MAP_KEY_DEVICE_KEY_TYPE: " +
std::to_string(value));
}
fmt_msgs.push_back(kv);
} break;
case MAP_KEY_DEVICE_KEY_ALGORITHM: {
if (entry.second->type() != cppbor::NINT) {
AddValidationMessage(
kCborValidateFatal,
"Invalid value type in public key info map for "
"key MAP_KEY_DEVICE_KEY_ALGORITHM: " +
CppborMajorTypeToString(entry.second->type()));
return kCborValidateFatal;
}
std::string kv = "key algorithm type: ";
const int64_t value = entry.second->asNint()->value();
if (value == DEVICE_KEY_ALGORITHM_ES256) {
kv += "ECDSA_SHA256";
public_key_info->signature_algorithm = kBccEcdsaSha256;
} else if (value == DEVICE_KEY_ALGORITHM_ES384) {
kv += "ECDSA_SHA384";
public_key_info->signature_algorithm = kBccEcdsaSha384;
} else if (value == DEVICE_KEY_ALGORITHM_EDDSA) {
kv += "EDDSA";
public_key_info->signature_algorithm = kBccEdDsa;
} else {
AddValidationMessage(kCborValidateError,
"Invalid value in public key info map for key "
"MAP_KEY_DEVICE_KEY_ALGORITHM: " +
std::to_string(value));
}
fmt_msgs.push_back(kv);
} break;
case MAP_KEY_DEVICE_KEY_OPS:
// The OPS is an array. Ignored for now.
break;
case MAP_KEY_DEVICE_KEY_CURVE: {
if (entry.second->type() != cppbor::UINT) {
AddValidationMessage(
kCborValidateFatal,
"Invalid value type in public key info map for "
"key MAP_KEY_DEVICE_KEY_CURVE: " +
CppborMajorTypeToString(entry.second->type()));
return kCborValidateFatal;
}
std::string kv = "curve: ";
const int64_t value = entry.second->asUint()->value();
if (value == DEVICE_KEY_CURVE_P256) {
public_key_info->curve = kBccP256;
kv += "P256";
} else if (value == DEVICE_KEY_CURVE_P384) {
public_key_info->curve = kBccP384;
kv += "P384";
} else if (value == DEVICE_KEY_CURVE_ED25519) {
public_key_info->curve = kBccEd25519;
kv += "ED25519";
} else {
AddValidationMessage(kCborValidateError,
"Invalid value in public key info map for key "
"MAP_KEY_DEVICE_KEY_CURVE: " +
std::to_string(value));
}
fmt_msgs.push_back(kv);
} break;
case MAP_KEY_DEVICE_KEY_BYTES_0:
case MAP_KEY_DEVICE_KEY_BYTES_1:
// BCC encodes keys as either two X, Y octet strings or a single
// octet string. The format used depends on the key type.
if (entry.second->type() != cppbor::BSTR) {
AddValidationMessage(
kCborValidateFatal,
"Invalid value type in public key info map for "
"key MAP_KEY_DEVICE_KEY_BYTES_0/1: " +
CppborMajorTypeToString(entry.second->type()));
return kCborValidateFatal;
}
const std::vector<uint8_t>& key_bytes = entry.second->asBstr()->value();
// Key byte length depends upon the key type.
if (key_bytes.size() != kED25519KeyDataItemSize &&
key_bytes.size() != kP256KeyComponentSize &&
key_bytes.size() != kP384KeyComponentSize) {
AddValidationMessage(kCborValidateFatal,
"Malformed public key data size of: " +
std::to_string(key_bytes.size()));
return kCborValidateFatal;
}
if (map_key == MAP_KEY_DEVICE_KEY_BYTES_0) {
device_key_bytes_0 = key_bytes;
} else {
device_key_bytes_1 = key_bytes;
}
}
key_set.insert(map_key);
}
if (key_set.find(MAP_KEY_DEVICE_KEY_TYPE) == key_set.end()) {
AddValidationMessage(kCborValidateError,
"Missing MAP_KEY_DEVICE_KEY_TYPE.");
}
if (key_set.find(MAP_KEY_DEVICE_KEY_ALGORITHM) == key_set.end()) {
AddValidationMessage(kCborValidateError,
"Missing MAP_KEY_DEVICE_KEY_ALGORITHM.");
}
if (key_set.find(MAP_KEY_DEVICE_KEY_CURVE) == key_set.end()) {
AddValidationMessage(kCborValidateError,
"Missing MAP_KEY_DEVICE_KEY_CURVE.");
}
if (device_key_bytes_0.empty() ||
(key_encoding_format == DEVICE_KEY_OCTET_PAIR &&
device_key_bytes_1.empty())) {
AddValidationMessage(
kCborValidateFatal,
"Malformed public key definition. Missing device public key bytes.");
return kCborValidateFatal;
}
std::vector<uint8_t> device_key_bytes;
if (key_encoding_format == DEVICE_KEY_OCTET_PAIR) {
// Key is an ECDSA elliptic key. We need to return the ANSI X9.62
// marshaled public key. Generate the marshaled key if needed. The
// marshaled key is needed to create an ECPublicKey object.
device_key_bytes.push_back(kMarshaledECKeyZValue);
device_key_bytes.insert(device_key_bytes.end(), device_key_bytes_0.begin(),
device_key_bytes_0.end());
device_key_bytes.insert(device_key_bytes.end(), device_key_bytes_1.begin(),
device_key_bytes_1.end());
if (device_key_bytes.size() != kMarshaledP384KeySize &&
device_key_bytes.size() != kMarshaledP256KeySize) {
AddValidationMessage(kCborValidateFatal,
"Invalid ECDSA public key size: " +
std::to_string(device_key_bytes.size()));
return kCborValidateFatal;
}
} else {
device_key_bytes = std::move(device_key_bytes_0);
}
fmt_msgs.push_back("public key bytes: " + wvutil::b2a_hex(device_key_bytes));
public_key_info->key_bytes = std::move(device_key_bytes);
return message_status_;
}
CborMessageStatus BccValidator::ProcessDiceChainEntryPayload(
const std::vector<uint8_t>& payload, std::vector<std::string>& fmt_msgs,
BccPublicKeyInfo* entry_public_key_info) {
if (payload.empty()) {
AddValidationMessage(kCborValidateFatal, "Empty bcc entry payload.");
return kCborValidateFatal;
}
auto parse_result = cppbor::parse(payload);
std::unique_ptr<cppbor::Item> item = std::move(std::get<0>(parse_result));
std::string error_message = std::move(std::get<2>(parse_result));
if (item == nullptr || !error_message.empty()) {
AddValidationMessage(kCborValidateFatal,
"Unable to parse bcc entry payload: " + error_message);
return kCborValidateFatal;
}
if (item->type() != cppbor::MAP) {
AddValidationMessage(kCborValidateFatal,
"Unexpected bcc entry payload type: " +
CppborMajorTypeToString(item->type()));
return kCborValidateFatal;
}
const IssuerSubject issuer_subject =
GetIssuerSubjectFromBccEntryPayload(item->asMap());
if (!issuer_subject.IsValid()) {
AddValidationMessage(kCborValidateError, "Missing Issuer or Subject.");
}
issuer_subject.PrintTo(fmt_msgs);
const cppbor::Bstr* subject_public_key =
GetSubjectPublicKeyFromBccEntryPayload(item->asMap());
if (subject_public_key == nullptr) {
AddValidationMessage(kCborValidateFatal,
"Bcc entry payload has no subject public key.");
return kCborValidateFatal;
}
// Now parse the serialized subject public key.
parse_result = cppbor::parse(subject_public_key->value());
item = std::move(std::get<0>(parse_result));
error_message = std::move(std::get<2>(parse_result));
if (item == nullptr || !error_message.empty()) {
AddValidationMessage(
kCborValidateFatal,
"Unable to parse serialized subject public key: " + error_message);
return kCborValidateFatal;
}
const cppbor::Map* subject_public_key_info = item->asMap();
if (subject_public_key_info == nullptr) {
AddValidationMessage(kCborValidateFatal,
"Invalid subject public key type. Expected Map.");
return kCborValidateFatal;
}
return ProcessSubjectPublicKeyInfo(*subject_public_key_info, fmt_msgs,
entry_public_key_info);
}
std::string BccValidator::GetFormattedMessage() const {
if (message_status_ == kCborUninitialized ||
message_status_ == kCborParseError) {
return std::string();
}
const cppbor::Item* parsed_item = std::get<0>(parse_result()).get();
if (parsed_item == nullptr) {
return "<null>";
}
return msg_ss_.str();
}
} // namespace util
} // namespace wvoec

View File

@@ -0,0 +1,140 @@
// 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 "cbor_validator.h"
#include <stddef.h>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
namespace wvoec {
namespace util {
std::string CppborMajorTypeToString(cppbor::MajorType type) {
switch (type) {
case cppbor::UINT:
return "UINT";
case cppbor::NINT:
return "NINT";
case cppbor::BSTR:
return "BSTR";
case cppbor::TSTR:
return "TSTR";
case cppbor::ARRAY:
return "ARRAY";
case cppbor::MAP:
return "MAP";
case cppbor::SEMANTIC:
return "SEMANTIC";
case cppbor::SIMPLE:
return "SIMPLE";
}
return "undefined type";
}
std::string CborMessageStatusToString(CborMessageStatus status) {
switch (status) {
case kCborUninitialized:
return "Uninitialized";
case kCborParseOk:
return "ParseOk";
case kCborParseError:
return "ParseError";
case kCborValidateOk:
return "ValidateOk";
case kCborValidateWarning:
return "ValidateWarning";
case kCborValidateError:
return "ValidateError";
case kCborValidateFatal:
return "ValidateFatal";
}
return "undefined status";
}
void CborValidator::Reset() {
message_status_ = kCborUninitialized;
parse_result_ = {nullptr, nullptr, ""};
validate_messages_.clear();
}
CborMessageStatus CborValidator::Parse(const std::vector<uint8_t>& cbor) {
Reset();
parse_result_ = cppbor::parse(cbor);
message_status_ =
(std::get<0>(parse_result_) && std::get<2>(parse_result_).empty())
? kCborParseOk
: kCborParseError;
return message_status_;
}
const cppbor::ParseResult* CborValidator::GetParseResult() const {
if (message_status_ == kCborUninitialized) {
return nullptr;
}
return &parse_result_;
}
std::string CborValidator::GetRawMessage() const {
if (message_status_ == kCborUninitialized ||
message_status_ == kCborParseError) {
return std::string();
}
const cppbor::Item* parsed_item = std::get<0>(parse_result_).get();
if (parsed_item == nullptr) {
return "<null>";
}
return cppbor::prettyPrint(parsed_item);
}
CborMessageStatus CborValidator::Validate() {
if (message_status_ != kCborParseOk) return message_status_;
// No other validations to be done than Parse() being successful.
AddValidationMessage(kCborValidateOk, "No validations are done.");
return message_status_;
}
std::string CborValidator::GetFormattedMessage() const {
return GetRawMessage();
}
void CborValidator::AddValidationMessage(CborMessageStatus status,
const std::string& msg) {
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

View 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

View File

@@ -44,6 +44,7 @@ using ScopedEvpPkey = ScopedObject<EVP_PKEY, EVP_PKEY_free>;
using ScopedPrivateKeyInfo =
ScopedObject<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
using ScopedSigPoint = ScopedObject<ECDSA_SIG, ECDSA_SIG_free>;
using ScopedEcPoint = ScopedObject<EC_POINT, EC_POINT_free>;
const EC_GROUP* GetEcGroup(EccCurve curve) {
// Creating a named EC_GROUP is an expensive operation, and they
@@ -140,6 +141,45 @@ EccCurve GetCurveFromKeyGroup(const EC_KEY* key) {
return kEccCurveUnknown;
}
// Creates EC public key from |curve| and |key_point|, and sets the result in
// *|public_key|.
bool GetPublicKeyFromKeyPoint(EccCurve curve, const uint8_t* key_point,
size_t key_point_length, EC_KEY** public_key) {
if (key_point == nullptr || key_point_length == 0) {
return false;
}
const EC_GROUP* group = GetEcGroup(curve);
if (!group) {
LOGE("Failed to get ECC group for curve %d", curve);
return false;
}
ScopedEcPoint point(EC_POINT_new(group));
if (!point) {
LOGE("Failed to new EC_POINT");
return false;
}
if (!EC_POINT_oct2point(group, point.get(), key_point, key_point_length,
nullptr)) {
LOGE("Failed to convert the serialized point to EC_POINT");
return false;
}
ScopedEcKey key(EC_KEY_new());
if (!key) {
LOGE("Failed to allocate key");
return false;
}
if (!EC_KEY_set_group(key.get(), group)) {
LOGE("Failed to set group");
return false;
}
if (EC_KEY_set_public_key(key.get(), point.get()) == 0) {
LOGE("Failed to convert the EC_POINT to EC_KEY");
return false;
}
*public_key = key.release();
return true;
}
// Compares the public EC points of both keys to see if they are the
// equal.
// Both |public_key| and |private_key| must be of the same group.
@@ -457,6 +497,26 @@ std::unique_ptr<EccPublicKey> EccPublicKey::LoadPrivateKeyInfo(
return LoadPrivateKeyInfo(buffer.data(), buffer.size());
}
// static
std::unique_ptr<EccPublicKey> EccPublicKey::LoadKeyPoint(EccCurve curve,
const uint8_t* buffer,
size_t length) {
if (buffer == nullptr) {
LOGE("Provided key point buffer is null");
return nullptr;
}
if (length == 0) {
LOGE("Provided key point buffer is zero length");
return nullptr;
}
std::unique_ptr<EccPublicKey> key(new EccPublicKey());
if (!key->InitFromKeyPoint(curve, buffer, length)) {
LOGE("Failed to initialize public key from KeyPoint");
key.reset();
}
return key;
}
bool EccPublicKey::IsMatchingPrivateKey(
const EccPrivateKey& private_key) const {
if (private_key.curve() != curve_) {
@@ -486,7 +546,7 @@ OEMCryptoResult EccPublicKey::VerifySignature(const uint8_t* message,
LOGE("Bad message data");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
// Step 1: Parse signature.
// Parse signature.
const uint8_t* tp = signature;
ScopedSigPoint sig_point(d2i_ECDSA_SIG(nullptr, &tp, signature_length));
if (!sig_point) {
@@ -494,24 +554,7 @@ OEMCryptoResult EccPublicKey::VerifySignature(const uint8_t* message,
// Most likely an invalid signature than an OpenSSL error.
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
}
// Step 2: Hash message
std::vector<uint8_t> digest;
if (!DigestMessage(curve_, message, message_length, &digest)) {
LOGE("Failed to digest message");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
// Step 3: Verify signature
const int res = ECDSA_do_verify(
digest.data(), static_cast<int>(digest.size()), sig_point.get(), key_);
if (res == -1) {
LOGE("Error occurred checking signature");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (res == 0) {
LOGD("Signature did not match");
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
}
return OEMCrypto_SUCCESS;
return DigestAndVerify(message, message_length, sig_point.get());
}
OEMCryptoResult EccPublicKey::VerifySignature(
@@ -536,6 +579,37 @@ OEMCryptoResult EccPublicKey::VerifySignature(
signature.size());
}
OEMCryptoResult EccPublicKey::VerifyRawSignature(
const uint8_t* message, size_t message_length, const uint8_t* signature,
size_t signature_length) const {
if (signature == nullptr || signature_length == 0) {
LOGE("Signature is missing");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (message == nullptr && message_length > 0) {
LOGE("Bad message data");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (signature_length % 2 == 1) {
LOGE("Bad signature size: %zu", signature_length);
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
// Parse signature.
ScopedSigPoint sig_point(ECDSA_SIG_new());
if (!sig_point) {
LOGE("Error occurred in ECDSA_SIG_new()");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
const int r_s_size = static_cast<int>(signature_length) / 2;
if (!ECDSA_SIG_set0(sig_point.get(), BN_bin2bn(signature, r_s_size, nullptr),
BN_bin2bn(signature + r_s_size, r_s_size, nullptr))) {
LOGE("Failed to parse signature");
// Most likely an invalid signature than an OpenSSL error.
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
}
return DigestAndVerify(message, message_length, sig_point.get());
}
EccPublicKey::~EccPublicKey() {
if (key_ != nullptr) {
EC_KEY_free(key_);
@@ -608,6 +682,57 @@ bool EccPublicKey::InitFromPrivateKey(const EccPrivateKey& private_key) {
return true;
}
bool EccPublicKey::InitFromKeyPoint(EccCurve curve, const uint8_t* buffer,
size_t length) {
if (buffer == nullptr || length == 0) {
LOGE("Provided key point buffer is empty");
return false;
}
EC_KEY* ec_key;
if (!GetPublicKeyFromKeyPoint(curve, buffer, length, &ec_key)) {
return false;
}
ScopedEcKey key(ec_key);
if (EC_KEY_check_key(key.get()) != 1) {
LOGE("Invalid public EC key");
return false;
}
// Required flags for IETF compliance.
EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE);
EC_KEY_set_conv_form(key.get(), POINT_CONVERSION_UNCOMPRESSED);
key_ = key.release();
curve_ = curve;
return true;
}
OEMCryptoResult EccPublicKey::DigestAndVerify(
const uint8_t* message, size_t message_length,
const ECDSA_SIG* sig_point) const {
if (message == nullptr && message_length > 0) {
LOGE("Bad message data");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
// Hash message.
std::vector<uint8_t> digest;
if (!DigestMessage(curve_, message, message_length, &digest)) {
LOGE("Failed to digest message");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
// Verify signature.
const int res = ECDSA_do_verify(
digest.data(), static_cast<int>(digest.size()), sig_point, key_);
if (res == -1) {
LOGE("Error occurred checking signature");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (res == 0) {
LOGD("Signature did not match");
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
}
return OEMCrypto_SUCCESS;
}
// static
std::unique_ptr<EccPrivateKey> EccPrivateKey::New(EccCurve curve) {
std::unique_ptr<EccPrivateKey> key(new EccPrivateKey());

View File

@@ -0,0 +1,393 @@
// 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 "signed_csr_payload_validator.h"
#include <vector>
#include "string_conversions.h"
namespace wvoec {
namespace util {
namespace {
enum CoseKeyAlgorithm : int {
AES_GCM_256 = 3,
HMAC_256 = 5,
ES256 = -7, // ECDSA with SHA-256
EDDSA = -8,
ECDH_ES_HKDF_256 = -25,
ES384 = -35, // ECDSA with SHA-384
};
void AddMessages(std::stringstream& ss,
const std::vector<std::string>& fmt_msgs, int indent) {
const std::string spaces = std::string(indent * 2, ' ');
for (auto& msg : fmt_msgs) {
ss << spaces << msg << "\n";
}
}
} // namespace
// clang-format off
// Signed CSR payload CBOR data to be verified:
// SignedData<CsrPayload> = [
// protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 /
// AlgorithmES384 },
// unprotected: {},
// payload: bstr .cbor DataToBeSigned / nil,
// signature: bstr ; PureEd25519(priv_key, Sig_structure<DataToBeSigned>) /
// ; ECDSA(priv_key, Sig_structure<DataToBeSigned>)
// ]
//
// DataToBeSigned = [
// challenge: bstr .size (0..64),
// bstr .cbor CsrPayload,
// ]
//
// CsrPayload = [ ; CBOR Array defining the payload for CSR.
// version: 3, ; The CsrPayload CDDL Schema version.
// CertificateType: "widevine" ; The type of certificate being requested.
// DeviceInfo, ; Defined in Android DeviceInfo.aidl
// KeysToSign: [] ; Empty list
// ]
//
// Sig_structure<DataToBeSigned> = [
// context: "Signature1",
// protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / AlgorithmES384 },
// external_aad: bstr .size 0,
// payload: bstr .cbor DataToBeSigned / nil,
// ]
// clang-format on
// Caller ensures the pointer is not NULL.
CborMessageStatus SignedCsrPayloadValidator::ValidateProtectedParams(
const cppbor::Bstr* protected_params) {
auto parse_result = cppbor::parse(protected_params);
std::unique_ptr<cppbor::Item> parsed_protected_params =
std::move(std::get<0>(parse_result));
const std::string error_message = std::move(std::get<2>(parse_result));
if (!parsed_protected_params || !error_message.empty()) {
AddValidationMessage(kCborValidateFatal,
"Unable to parse protectedParams: " + error_message);
return message_status_;
}
// |parsed_protected_params| is a CBOR map of 1 entry
const cppbor::Map* protected_params_map = parsed_protected_params->asMap();
if (!protected_params_map) {
AddValidationMessage(
kCborValidateFatal,
"ProtectedParams must be a CBOR map. Actual type: " +
CppborMajorTypeToString(parsed_protected_params->type()));
return message_status_;
}
if (protected_params_map->size() == 0) {
AddValidationMessage(kCborValidateFatal, "ProtectedParams is empty.");
return message_status_;
}
if (protected_params_map->size() > 1) {
AddValidationMessage(kCborValidateWarning,
"ProtectedParams expects 1 entry, but got " +
std::to_string(protected_params_map->size()));
}
// TODO(b/314141962): Replace this with the map lookup function in cppbor
// library
bool algo_found = false;
for (auto const& entry : *protected_params_map) {
if (!entry.first->asInt()) {
AddValidationMessage(kCborValidateWarning,
"Unsupported key type: " +
CppborMajorTypeToString(entry.first->type()));
} else if (entry.first->asInt()->value() != 1) {
AddValidationMessage(kCborValidateWarning,
"Unsupported key value: " +
std::to_string(entry.first->asInt()->value()));
} else {
auto& algorithm = entry.second;
if (!algorithm) {
AddValidationMessage(kCborValidateFatal, "Algorithm value is missing.");
return message_status_;
}
if (!algorithm->asInt()) {
AddValidationMessage(kCborValidateFatal,
"Unsupported signature algorithm value type: " +
CppborMajorTypeToString(algorithm->type()));
return message_status_;
}
std::string kv = "1: ";
const int64_t algo = algorithm->asInt()->value();
if (algo == EDDSA)
kv += "EDDSA";
else if (algo == ES256)
kv += "ES256";
else if (algo == ES384)
kv += "ES384";
else {
kv += std::to_string(algo);
AddValidationMessage(
kCborValidateFatal,
"Unsupported signature algorithm value: " + std::to_string(algo));
return message_status_;
}
AddMessages(msg_ss_, {std::move(kv)}, 1);
algo_found = true;
}
}
if (!algo_found) {
AddValidationMessage(kCborValidateFatal,
"ProtectedParams has no signature algorithm.");
}
return message_status_;
}
// Caller ensures the pointer is not NULL.
CborMessageStatus SignedCsrPayloadValidator::ValidateDataToBeSigned(
const cppbor::Bstr* data) {
if (data->value().empty()) {
AddValidationMessage(kCborValidateFatal, "Payload to be signed is empty.");
return message_status_;
}
auto parse_result = cppbor::parse(data);
std::unique_ptr<cppbor::Item> parsed_payload_to_be_signed =
std::move(std::get<0>(parse_result));
std::string error_message = std::move(std::get<2>(parse_result));
if (!parsed_payload_to_be_signed || !error_message.empty()) {
AddValidationMessage(
kCborValidateFatal,
"Unable to parse the payload to be signed: " + error_message);
return message_status_;
}
// Verify that |parsed_payload_to_be_signed| is a valid array.
const cppbor::Array* payload_to_be_signed_array =
parsed_payload_to_be_signed->asArray();
if (!payload_to_be_signed_array) {
AddValidationMessage(
kCborValidateFatal,
"Payload to be signed must be a CBOR array. Actual type: " +
CppborMajorTypeToString(parsed_payload_to_be_signed->type()));
return message_status_;
}
if (payload_to_be_signed_array->size() != 2U) {
AddValidationMessage(
kCborValidateFatal,
"Payload to be signed must contain the challenge and "
"payload. Actual size: " +
std::to_string(payload_to_be_signed_array->size()));
return message_status_;
}
// Element 0: challenge.
std::string msg = "- Challenge: ";
const cppbor::Bstr* challenge = payload_to_be_signed_array->get(0)->asBstr();
if (!challenge) {
AddValidationMessage(kCborValidateFatal,
"Challenge must be Bstr. Actual type: " +
CppborMajorTypeToString(
payload_to_be_signed_array->get(0)->type()));
return message_status_;
}
if (challenge->value().size() > 64) {
AddValidationMessage(
kCborValidateError,
"Challenge size must be between 0 and 64 bytes inclusive. "
"However, challenge is " +
std::to_string(challenge->value().size()) + " bytes long.");
}
msg += wvutil::b2a_hex(challenge->value());
AddMessages(msg_ss_, {std::move(msg)}, 1);
// Element 1: CsrPayload.
AddMessages(msg_ss_, {"- CsrPayload:"}, 1);
const cppbor::Bstr* csr_payload =
payload_to_be_signed_array->get(1)->asBstr();
if (!csr_payload) {
AddValidationMessage(kCborValidateFatal, "CSR payload is missing.");
return message_status_;
}
parse_result = cppbor::parse(csr_payload);
std::unique_ptr<cppbor::Item> parsed_csr_payload =
std::move(std::get<0>(parse_result));
error_message = std::move(std::get<2>(parse_result));
if (!parsed_csr_payload) {
AddValidationMessage(kCborValidateFatal,
"Unable to parse CSR payload: " + error_message);
return message_status_;
}
if (!parsed_csr_payload->asArray()) {
AddValidationMessage(
kCborValidateFatal,
"CSR payload must be an array. Actual: " +
CppborMajorTypeToString(parsed_csr_payload->type()));
return message_status_;
}
if (parsed_csr_payload->asArray()->size() != 4U) {
AddValidationMessage(
kCborValidateFatal,
"CSR payload must contain 4 elements. Actual size: " +
std::to_string(parsed_csr_payload->asArray()->size()));
return message_status_;
}
// |parsed_csr_payload| is an array of 4 elements.
const cppbor::Uint* version = parsed_csr_payload->asArray()->get(0)->asUint();
if (!version) {
AddValidationMessage(kCborValidateFatal,
"CSR payload version must be an unsigned integer.");
return message_status_;
}
AddMessages(msg_ss_, {"- version: " + std::to_string(version->value())}, 2);
if (version->value() != 3U) {
AddValidationMessage(kCborValidateError,
"CSR payload version must be must be equal to 3.");
}
const cppbor::Tstr* certificate_type =
parsed_csr_payload->asArray()->get(1)->asTstr();
if (!certificate_type) {
// Certificate type is allowed to be extended by vendor, i.e. we can't
// enforce its value.
AddValidationMessage(
kCborValidateFatal,
"Certificate type must be Tstr. Actual type: " +
CppborMajorTypeToString(
parsed_csr_payload->asArray()->get(1)->type()));
return message_status_;
}
AddMessages(msg_ss_, {"- certificate_type: " + certificate_type->value()}, 2);
const cppbor::Map* device_info =
parsed_csr_payload->asArray()->get(2)->asMap();
if (!device_info) {
AddValidationMessage(
kCborValidateFatal,
"Device info must be a CBOR map. Actual type: " +
CppborMajorTypeToString(
parsed_csr_payload->asArray()->get(2)->type()));
return message_status_;
}
AddMessages(msg_ss_, {"- device_info: " + cppbor::prettyPrint(device_info)},
2);
const cppbor::Array* keys = parsed_csr_payload->asArray()->get(3)->asArray();
if (!keys) {
AddValidationMessage(
kCborValidateFatal,
"Keys must be a CBOR array. Actual type: " +
CppborMajorTypeToString(
parsed_csr_payload->asArray()->get(3)->type()));
return message_status_;
}
AddMessages(msg_ss_, {"- keys_to_sign: " + cppbor::prettyPrint(keys)}, 2);
return message_status_;
}
CborMessageStatus SignedCsrPayloadValidator::Validate() {
if (message_status_ != kCborParseOk) return message_status_;
const cppbor::Item* parsed_signed_csr_payload =
std::get<0>(parse_result()).get();
if (!parsed_signed_csr_payload) {
AddValidationMessage(kCborValidateFatal, "Signed CSR payload is empty.");
return message_status_;
}
// Verify that |parsed_signed_csr_payload| is a valid array.
if (!parsed_signed_csr_payload->asArray()) {
AddValidationMessage(
kCborValidateFatal,
"Signed CSR payload must be a CBOR array. Actual type: " +
CppborMajorTypeToString(parsed_signed_csr_payload->type()));
return message_status_;
}
const cppbor::Array* signed_csr_payload_array =
parsed_signed_csr_payload->asArray();
if (signed_csr_payload_array->size() != 4U) {
AddValidationMessage(kCborValidateFatal,
"Invalid CoseSign1 size. Actual: " +
std::to_string(signed_csr_payload_array->size()));
return message_status_;
}
// Writes YAML-formatted output to |msg_ss_| during validation.
msg_ss_.str(std::string());
msg_ss_ << "---"
<< "\n";
// Element 0: protected params.
msg_ss_ << "- Protected params:\n";
const cppbor::Bstr* protected_params =
signed_csr_payload_array->get(0)->asBstr();
CborMessageStatus status = ValidateProtectedParams(protected_params);
if (status == kCborValidateFatal) return kCborValidateFatal;
// Element 1: unprotected params map.
std::string msg = "- Unprotected params: ";
const cppbor::Map* unprotected_params =
signed_csr_payload_array->get(1)->asMap();
if (!unprotected_params) {
AddValidationMessage(
kCborValidateFatal,
"UnprotectedParams must be a CBOR map. Actual type: " +
CppborMajorTypeToString(signed_csr_payload_array->get(1)->type()));
return message_status_;
}
if (unprotected_params->size() == 0) {
msg += "<null>";
} else {
msg += "non-null map";
AddValidationMessage(kCborValidateWarning,
"UnprotectedParams is expected to be empty.");
}
msg_ss_ << msg << "\n";
// Element 2: payload (DataToBeSigned).
msg_ss_ << "- Payload:\n";
const cppbor::Bstr* payload_to_be_signed =
signed_csr_payload_array->get(2)->asBstr();
if (!payload_to_be_signed) {
AddValidationMessage(
kCborValidateFatal,
"Payload to be signed must be Bstr. Actual type: " +
CppborMajorTypeToString(signed_csr_payload_array->get(2)->type()));
return message_status_;
}
status = ValidateDataToBeSigned(payload_to_be_signed);
if (status == kCborValidateFatal) return kCborValidateFatal;
// Element 3: signature.
std::string sig_msg = "- Signature: ";
const cppbor::Bstr* signature = signed_csr_payload_array->get(3)->asBstr();
if (!signature) {
AddValidationMessage(
kCborValidateFatal,
"CoseSign1 signature must be Bstr. Actual type: " +
CppborMajorTypeToString(signed_csr_payload_array->get(3)->type()));
return message_status_;
}
// Skip CoseSign1 signature verification since the validator doesn't have
// verifying keys.
if (signature->value().empty()) {
sig_msg += "<null>";
AddValidationMessage(kCborValidateError, "CoseSign1 signature is missing.");
} else {
sig_msg += wvutil::b2a_hex(signature->value());
}
msg_ss_ << sig_msg << "\n";
msg_ss_ << "...\n";
if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk;
return message_status_;
}
std::string SignedCsrPayloadValidator::GetFormattedMessage() const {
if (message_status_ == kCborUninitialized ||
message_status_ == kCborParseError) {
return std::string();
}
const cppbor::Item* parsed_item = std::get<0>(parse_result()).get();
if (parsed_item == nullptr) {
return "<null>";
}
return msg_ss_.str();
}
} // namespace util
} // namespace wvoec