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,81 @@
// 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_BCC_VALIDATOR_H_
#define WVOEC_UTIL_BCC_VALIDATOR_H_
#include <sstream>
#include <string>
#include <vector>
#include "cbor_validator.h"
#include "cppbor.h"
namespace wvoec {
namespace util {
// Enums and struct to hold EC public key info
enum BccSignatureAlgorithm {
kBccDefaultSignature = 0,
kBccEdDsa = 1,
kBccEcdsaSha256 = 2,
kBccEcdsaSha384 = 3
};
enum BccCurve {
kBccDefaultCurve = 0,
kBccEd25519 = 1,
kBccP256 = 2,
kBccP384 = 3
};
struct BccPublicKeyInfo {
BccSignatureAlgorithm signature_algorithm;
BccCurve curve;
// Raw EC key bytes extracted from BCC
std::vector<uint8_t> key_bytes;
};
// BccValidator processes a Provisioning 4.0 device root of trust. It extracts
// and validates relevant pieces of information of BCC.
// Relevant documents:
// Android definition: go/remote-provisioning-hal#bcc.
// Google Dice Profile: go/dice-profile
class BccValidator : public CborValidator {
public:
explicit BccValidator() {}
virtual ~BccValidator() override = default;
BccValidator(const BccValidator&) = delete;
BccValidator& operator=(const BccValidator&) = delete;
// Verifies the Cbor struct of a client generated root of trust. This message
// is part of an attestation model conforming to the Google Open Dice Profile.
// This message is received from a client device to attest it is a valid
// Widevine device.
virtual CborMessageStatus Validate() override;
// Outputs BCC in YAML.
virtual std::string GetFormattedMessage() const override;
private:
// Processes CoseKey PubKeyEd25519 / PubKeyECDSA256, prints into |fmt_msgs|,
// and extracts the PubKey to *|public_key_info|.
CborMessageStatus ProcessSubjectPublicKeyInfo(
const cppbor::Map& public_key_info_map,
std::vector<std::string>& fmt_msgs, BccPublicKeyInfo* public_key_info);
// Processes DiceChainEntryPayload, which contains subject public key, prints
// into |fmt_msgs|, and extracts the PubKey to *|public_key_info|.
CborMessageStatus ProcessDiceChainEntryPayload(
const std::vector<uint8_t>& payload, std::vector<std::string>& fmt_msgs,
BccPublicKeyInfo* public_key_info);
// Verifies the raw EC signature |signature| with the public key
// |signing_key|. |signature| extracted from BCC is not ASN.1 DER encoded.
bool VerifySignature(const BccPublicKeyInfo& signing_key,
const std::vector<uint8_t>& message,
const std::vector<uint8_t>& signature);
// Used to generate formatted message.
std::stringstream msg_ss_;
};
} // namespace util
} // namespace wvoec
#endif // WVOEC_UTIL_BCC_VALIDATOR_H_

View File

@@ -0,0 +1,86 @@
// 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_CBOR_VALIDATOR_H_
#define WVOEC_UTIL_CBOR_VALIDATOR_H_
#include <string>
#include <vector>
#include "cppbor.h"
#include "cppbor_parse.h"
namespace wvoec {
namespace util {
// CborMessageStatus values are ranked in level of severity.
// kCborUninitialized being the lowest severity, and
// kCborValidateFatal being the highest.
enum CborMessageStatus {
kCborUninitialized = 0,
kCborParseOk = 1,
kCborParseError = 2,
kCborValidateOk = 3,
kCborValidateWarning = 4,
kCborValidateError = 5,
kCborValidateFatal = 6
};
std::string CppborMajorTypeToString(cppbor::MajorType type);
std::string CborMessageStatusToString(CborMessageStatus status);
class CborValidator {
public:
explicit CborValidator() {}
virtual ~CborValidator() = default;
CborValidator(const CborValidator&) = delete;
CborValidator& operator=(const CborValidator&) = delete;
// Decodes |cbor| and sets |message_status_|.
virtual CborMessageStatus Parse(const std::vector<uint8_t>& cbor);
const cppbor::ParseResult* GetParseResult() const;
// Returns pretty-printed CBOR for |parse_result_|. Returns empty string if
// |parse_result_| is not valid.
std::string GetRawMessage() const;
// Verifies the fields in |parse_result_| to have expected types and values.
// Requires that Parse() is called first and |parse_result_| contains a valid
// CBOR message.
virtual CborMessageStatus Validate();
// Returns all validation messages from Validate().
const std::vector<std::pair<CborMessageStatus, std::string>>&
GetValidateMessages() const {
return validate_messages_;
}
// Prints |parse_result_| in readable format. Requires that Parse() is called
// first and |parse_result_| contains a valid CBOR message.
virtual std::string GetFormattedMessage() const;
const cppbor::ParseResult& parse_result() const { return parse_result_; }
const std::vector<std::pair<CborMessageStatus, std::string>>&
validate_messages() {
return validate_messages_;
}
protected:
void Reset();
// 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:
// Internal status of parsing and validating.
cppbor::ParseResult parse_result_ = {};
std::vector<std::pair<CborMessageStatus, std::string>> validate_messages_;
};
} // namespace util
} // namespace wvoec
#endif // WVOEC_UTIL_CBOR_VALIDATOR_H_

View File

@@ -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_

View File

@@ -60,6 +60,13 @@ class EccPublicKey {
size_t length);
static std::unique_ptr<EccPublicKey> Load(const std::string& buffer);
static std::unique_ptr<EccPublicKey> Load(const std::vector<uint8_t>& buffer);
// Loads EC public key from the |curve| and |buffer|.
// The provided |buffer| must contain an EC point serialized from raw X9.62
// format. For uncompressed form, it is a 1-byte prefix plus two 32-byte
// integers representing X, Y coordinates.
static std::unique_ptr<EccPublicKey> LoadKeyPoint(EccCurve curve,
const uint8_t* buffer,
size_t length);
// Loads a serialized ECC private key, but only converting the public key.
static std::unique_ptr<EccPublicKey> LoadPrivateKeyInfo(const uint8_t* buffer,
@@ -107,6 +114,15 @@ class EccPublicKey {
const std::string& signature) const;
OEMCryptoResult VerifySignature(const std::vector<uint8_t>& message,
const std::vector<uint8_t>& signature) const;
// Verifies the raw |signature| matches the provided |message| by the
// private equivalent of this public key.
// A raw ECDSA signature consists of a pair of integers (r,s). The |signature|
// is a concatenation of two octet strings resulting from the integer-to-octet
// encoding of the values of r and s, in the order of (r||s).
OEMCryptoResult VerifyRawSignature(const uint8_t* message,
size_t message_length,
const uint8_t* signature,
size_t signature_length) const;
~EccPublicKey();
@@ -125,6 +141,13 @@ class EccPublicKey {
bool InitFromPrivateKeyInfo(const uint8_t* buffer, size_t length);
// Initializes the public key object from a private.
bool InitFromPrivateKey(const EccPrivateKey& private_key);
// Initializes the public key object from the provided curve and key point
// |buffer|.
bool InitFromKeyPoint(EccCurve curve, const uint8_t* buffer, size_t length);
// Digests the |message| and verifies signature against the provided signature
// point.
OEMCryptoResult DigestAndVerify(const uint8_t* message, size_t message_length,
const ECDSA_SIG* sig_point) const;
// OpenSSL/BoringSSL implementation of an ECC key.
// As a public key, this will only have key point initialized.

View File

@@ -0,0 +1,44 @@
// 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_SIGNED_CSR_PAYLOAD_VALIDATOR_H_
#define WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_
#include <sstream>
#include <string>
#include "cbor_validator.h"
#include "cppbor.h"
namespace wvoec {
namespace util {
// SignedCsrPayloadValidator parses and validates a Cbor struct of
// SignedData<CsrPayload>. The definition of SignedData<T> and CsrPayload can be
// found at:
// https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/generateCertificateRequestV2.cddl
class SignedCsrPayloadValidator : public CborValidator {
public:
explicit SignedCsrPayloadValidator() {}
virtual ~SignedCsrPayloadValidator() override = default;
SignedCsrPayloadValidator(const SignedCsrPayloadValidator&) = delete;
SignedCsrPayloadValidator& operator=(const SignedCsrPayloadValidator&) =
delete;
// Verifies the Cbor struct of a client generated SignedData<CsrPayload>.
virtual CborMessageStatus Validate() override;
// Outputs SignedData<CsrPayload> in YAML.
virtual std::string GetFormattedMessage() const override;
private:
CborMessageStatus ValidateProtectedParams(
const cppbor::Bstr* protected_params);
CborMessageStatus ValidateDataToBeSigned(const cppbor::Bstr* data);
// Used to generate formatted message.
std::stringstream msg_ss_;
};
} // namespace util
} // namespace wvoec
#endif // WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_

View File

@@ -5,6 +5,7 @@
'variables': {
'privacy_crypto_impl%': 'boringssl',
'boringssl_libcrypto_path%': '../../third_party/boringssl/boringssl.gyp:crypto',
'libcppbor_path%': '../../third_party/libcppbor.gyp:cppbor',
},
'include_dirs': [
'<(oemcrypto_dir)/include',
@@ -18,15 +19,22 @@
],
},
'sources': [
'<(oemcrypto_dir)/util/src/bcc_validator.cpp',
'<(oemcrypto_dir)/util/src/cbor_validator.cpp',
'<(oemcrypto_dir)/util/src/cmac.cpp',
'<(oemcrypto_dir)/util/src/device_info_validator.cpp',
'<(oemcrypto_dir)/util/src/hmac.cpp',
'<(oemcrypto_dir)/util/src/oemcrypto_drm_key.cpp',
'<(oemcrypto_dir)/util/src/oemcrypto_ecc_key.cpp',
'<(oemcrypto_dir)/util/src/oemcrypto_key_deriver.cpp',
'<(oemcrypto_dir)/util/src/oemcrypto_oem_cert.cpp',
'<(oemcrypto_dir)/util/src/oemcrypto_rsa_key.cpp',
'<(oemcrypto_dir)/util/src/signed_csr_payload_validator.cpp',
'<(oemcrypto_dir)/util/src/wvcrc.cpp',
],
'dependencies': [
'<(libcppbor_path)',
],
'includes': [
'../../util/libcrypto_dependency.gypi',
],

View File

@@ -15,7 +15,9 @@
],
},
'sources': [
'<(oemcrypto_dir)/util/test/bcc_validator_unittest.cpp',
'<(oemcrypto_dir)/util/test/cmac_unittest.cpp',
'<(oemcrypto_dir)/util/test/device_info_validator_unittest.cpp',
'<(oemcrypto_dir)/util/test/hmac_unittest.cpp',
'<(oemcrypto_dir)/util/test/oem_cert_test.cpp',
'<(oemcrypto_dir)/util/test/oemcrypto_ecc_key_unittest.cpp',
@@ -23,5 +25,9 @@
'<(oemcrypto_dir)/util/test/oemcrypto_ref_test_utils.cpp',
'<(oemcrypto_dir)/util/test/oemcrypto_rsa_key_unittest.cpp',
'<(oemcrypto_dir)/util/test/oemcrypto_wvcrc32_unittest.cpp',
'<(oemcrypto_dir)/util/test/signed_csr_payload_validator_unittest.cpp',
],
'dependencies': [
'<(third_party_path)/libcppbor.gyp:cppbor',
],
}

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

View File

@@ -0,0 +1,147 @@
// 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "bcc_validator.h"
using ::testing::AllOf;
using ::testing::Ge;
using ::testing::HasSubstr;
using ::testing::Le;
namespace wvoec {
namespace util {
namespace {
// Self-signed phase 1 BCC generated by OPK reference implementation.
const std::vector<uint8_t> kBcc = {
0x82, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21,
0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff,
0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee,
0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x84, 0x43,
0xa1, 0x01, 0x27, 0xa0, 0x58, 0x40, 0xa4, 0x01, 0x60, 0x02, 0x60, 0x3a,
0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04,
0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02,
0x41, 0xf1, 0x0b, 0x40, 0xff, 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc,
0x5a, 0x97, 0xa6, 0x80, 0xee, 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77,
0xe5, 0xfb, 0x10, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40,
0x73, 0x02, 0x36, 0xaa, 0x6d, 0x52, 0x50, 0x67, 0x43, 0xc4, 0x0b, 0xf8,
0x3f, 0x35, 0x2a, 0xd8, 0x44, 0x09, 0xf4, 0x1d, 0xca, 0x91, 0x12, 0x27,
0x01, 0xdf, 0x73, 0xb7, 0x9b, 0x31, 0x28, 0x8e, 0xae, 0x9b, 0xc6, 0x7a,
0xdc, 0x07, 0xab, 0x69, 0xd2, 0x85, 0x9a, 0x15, 0x8b, 0xe3, 0x5b, 0xf2,
0x94, 0x95, 0xee, 0x49, 0x74, 0xc5, 0x85, 0x62, 0x3d, 0x46, 0x4c, 0xeb,
0x11, 0x89, 0x68, 0x02};
const std::vector<uint8_t> kBccWrongEntryKey = {
0x82, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21,
0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff,
0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee,
0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x84, 0x43,
0xa1, 0x01, 0x27, 0xa0, 0x58, 0x40, 0xa4, 0x01, 0x60, 0x02, 0x60, 0x3a,
0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04,
0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40,
0x89, 0x1d, 0xff, 0xb3, 0x3b, 0xe2, 0xdc, 0xc6, 0xbc, 0xbd, 0xc7, 0xcd,
0x3f, 0x9c, 0x43, 0xf6, 0xdd, 0xea, 0x58, 0x53, 0x45, 0x8f, 0x87, 0x17,
0x0a, 0xe4, 0x06, 0xf2, 0xbe, 0x14, 0x69, 0x13, 0x3d, 0x1d, 0xd0, 0x52,
0x8f, 0x56, 0x4b, 0x0f, 0xad, 0x2e, 0xf0, 0xbf, 0xbb, 0xd1, 0x35, 0x9c,
0x5a, 0xe8, 0x67, 0xbe, 0xec, 0xff, 0x9d, 0xfe, 0xac, 0x8d, 0x47, 0x4e,
0x6d, 0xd1, 0xd3, 0x02};
const std::vector<uint8_t> kBccMissingIssuer = {
0x82, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21,
0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff,
0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee,
0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x84, 0x43,
0xa1, 0x01, 0x27, 0xa0, 0x58, 0x3e, 0xa3, 0x02, 0x60, 0x3a, 0x00, 0x47,
0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02,
0x20, 0x06, 0x21, 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1,
0x0b, 0x40, 0xff, 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97,
0xa6, 0x80, 0xee, 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb,
0x10, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0xf9, 0x46,
0x36, 0xbd, 0x95, 0x75, 0xc2, 0x3d, 0xf9, 0xa2, 0xbe, 0x60, 0x8e, 0xbf,
0x64, 0x89, 0xdf, 0xb9, 0x9c, 0x3c, 0x17, 0x36, 0x23, 0x9a, 0x68, 0x1a,
0x34, 0x36, 0x51, 0x89, 0x59, 0xf2, 0x54, 0x62, 0xd3, 0x8f, 0xeb, 0x9b,
0x75, 0x3e, 0xe9, 0xfc, 0xe3, 0xc2, 0x8f, 0x84, 0xb1, 0x71, 0xcd, 0x29,
0x12, 0x65, 0xeb, 0xab, 0x28, 0x4b, 0xe2, 0x3e, 0x1b, 0xd8, 0x17, 0xdb,
0x97, 0x0f};
} // namespace
TEST(OEMCryptoBccValidatorTest, BccParseError) {
const std::vector<uint8_t> bcc_bad(kBcc.begin(), kBcc.end() - 1);
BccValidator validator;
CborMessageStatus result = validator.Parse(bcc_bad);
EXPECT_EQ(kCborParseError, result);
result = validator.Validate();
EXPECT_EQ(kCborParseError, result);
EXPECT_EQ("", validator.GetRawMessage());
EXPECT_EQ("", validator.GetFormattedMessage());
}
TEST(OEMCryptoBccValidatorTest, Bcc) {
BccValidator validator;
CborMessageStatus result = validator.Parse(kBcc);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning)));
const std::string out = validator.GetFormattedMessage();
EXPECT_THAT(out, HasSubstr("key encoding format: DEVICE_KEY_BYTE_STRING"));
EXPECT_THAT(out, HasSubstr("key algorithm type: EDDSA"));
}
TEST(OEMCryptoBccValidatorTest, BccWrongEntryKey) {
BccValidator validator;
CborMessageStatus result = validator.Parse(kBccWrongEntryKey);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_EQ(result, kCborValidateError);
// Non-fatal validation error should be able to return formatted output.
const std::string out = validator.GetFormattedMessage();
EXPECT_THAT(out, HasSubstr("key encoding format: DEVICE_KEY_BYTE_STRING"));
EXPECT_THAT(out, HasSubstr("key algorithm type: EDDSA"));
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
EXPECT_EQ(1u, msgs.size());
EXPECT_EQ(kCborValidateError, msgs[0].first);
}
TEST(OEMCryptoBccValidatorTest, BccParseThreeTimes) {
BccValidator validator;
const std::vector<uint8_t> bcc_bad(kBcc.begin(), kBcc.end() - 2);
CborMessageStatus result = validator.Parse(bcc_bad);
EXPECT_EQ(kCborParseError, result);
result = validator.Validate();
EXPECT_EQ(kCborParseError, result);
result = validator.Parse(kBccWrongEntryKey);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_EQ(result, kCborValidateError);
result = validator.Parse(kBcc);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_EQ(result, kCborValidateOk);
}
TEST(OEMCryptoBccValidatorTest, BccMissingIssuer) {
BccValidator validator;
CborMessageStatus result = validator.Parse(kBccMissingIssuer);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_EQ(result, kCborValidateError);
const std::string out = validator.GetFormattedMessage();
EXPECT_THAT(out, HasSubstr("key encoding format: DEVICE_KEY_BYTE_STRING"));
EXPECT_THAT(out, HasSubstr("key algorithm type: EDDSA"));
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
EXPECT_EQ(1u, msgs.size());
EXPECT_EQ(kCborValidateError, msgs[0].first);
EXPECT_THAT(msgs[0].second, HasSubstr("Missing Issuer"));
}
} // namespace util
} // namespace wvoec

View File

@@ -0,0 +1,204 @@
// 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "device_info_validator.h"
using ::testing::AllOf;
using ::testing::Ge;
using ::testing::HasSubstr;
using ::testing::Le;
namespace wvoec {
namespace util {
namespace {
constexpr int kDeviceVersion1 = 1;
constexpr int kDeviceVersion2 = 2;
constexpr int kDeviceVersion3 = 3;
cppbor::Map BuildDeviceInfoMap(int version) {
cppbor::Map device_info =
cppbor::Map()
.add("brand", "brand")
.add("manufacturer", "manufacturer")
.add("product", "product")
.add("model", "model")
.add("vb_state", "green")
.add("bootloader_state", "unlocked")
.add("vbmeta_digest", cppbor::Bstr(std::vector<uint8_t>()))
.add("os_version", "os_version")
.add("system_patch_level", 202312)
.add("boot_patch_level", 20231201)
.add("vendor_patch_level", 20231201)
.add("security_level", "tee");
switch (version) {
case kDeviceVersion1:
device_info.add("board", "board");
device_info.add("version", 1);
device_info.add("att_id_state", "open");
break;
case kDeviceVersion2:
device_info.add("device", "device");
device_info.add("version", 2);
device_info.add("fused", 0);
break;
case kDeviceVersion3:
device_info.add("device", "device");
device_info.add("fused", 0);
break;
}
return device_info;
}
std::vector<uint8_t> BuildDeviceInfo(int version) {
auto map = BuildDeviceInfoMap(version);
return map.canonicalize().encode();
}
} // namespace
TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoParseError) {
const std::vector<uint8_t> device_info = BuildDeviceInfo(kDeviceVersion3);
const std::vector<uint8_t> device_info_bad(device_info.begin(),
device_info.end() - 1);
DeviceInfoValidator validator(kDeviceVersion3);
CborMessageStatus result = validator.Parse(device_info_bad);
EXPECT_EQ(kCborParseError, result);
result = validator.Validate();
EXPECT_EQ(kCborParseError, result);
EXPECT_EQ("", validator.GetRawMessage());
EXPECT_EQ("", validator.GetFormattedMessage());
}
TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoNotMap) {
cppbor::Array array = cppbor::Array().add("make").add(123).add("model");
const std::vector<uint8_t> device_info_bad = array.encode();
DeviceInfoValidator validator(kDeviceVersion3);
CborMessageStatus result = validator.Parse(device_info_bad);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, kCborValidateFatal);
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
EXPECT_EQ(1u, msgs.size());
EXPECT_EQ(kCborValidateFatal, msgs[0].first);
EXPECT_THAT(msgs[0].second, HasSubstr("Device info is not a CBOR map"));
}
TEST(OEMCryptoDeviceInfoValidatorTest,
DeviceInfoV3WrongKeyValueTypeAndMissingField) {
const std::vector<uint8_t> device_info_bad =
cppbor::Map()
.add("brand", "brand")
.add("manufacturer", "manufacturer")
.add(123, 456) // Non-Tstr key type
.add("system_patch_level", "not a uint") // Non-uint value type
.canonicalize()
.encode();
DeviceInfoValidator validator(kDeviceVersion3);
CborMessageStatus result = validator.Parse(device_info_bad);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_EQ(kCborValidateError, result);
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
const bool unexpected_key_type_found = std::any_of(
msgs.begin(), msgs.end(),
[](const std::pair<CborMessageStatus, std::string>& p) {
return p.second.find("Unexpected entry key type") != std::string::npos;
});
EXPECT_EQ(true, unexpected_key_type_found);
const bool unexpected_value_type_found = std::any_of(
msgs.begin(), msgs.end(),
[](const std::pair<CborMessageStatus, std::string>& p) {
return p.second.find("system_patch_level has the wrong type") !=
std::string::npos;
});
EXPECT_EQ(true, unexpected_value_type_found);
const bool missing_model_found = std::any_of(
msgs.begin(), msgs.end(),
[](const std::pair<CborMessageStatus, std::string>& p) {
return p.second.find("model is missing") != std::string::npos;
});
EXPECT_EQ(true, missing_model_found);
}
TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV3NonCanonical) {
const cppbor::Map map = BuildDeviceInfoMap(kDeviceVersion3);
const std::vector<uint8_t> device_info = map.encode();
DeviceInfoValidator validator(kDeviceVersion3);
CborMessageStatus result = validator.Parse(device_info);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, kCborValidateError);
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
EXPECT_EQ(1u, msgs.size());
EXPECT_EQ(kCborValidateError, msgs[0].first);
EXPECT_THAT(msgs[0].second,
HasSubstr("Device info ordering is non-canonical"));
}
TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV3) {
const std::vector<uint8_t> device_info = BuildDeviceInfo(kDeviceVersion3);
DeviceInfoValidator validator(kDeviceVersion3);
CborMessageStatus result = validator.Parse(device_info);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning)));
const std::string out = validator.GetFormattedMessage();
EXPECT_THAT(out, HasSubstr("manufacturer: manufacturer"));
EXPECT_THAT(out, HasSubstr("model: model"));
EXPECT_THAT(out, HasSubstr("fused: 0"));
}
TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV2) {
const std::vector<uint8_t> device_info = BuildDeviceInfo(kDeviceVersion2);
DeviceInfoValidator validator(kDeviceVersion2);
CborMessageStatus result = validator.Parse(device_info);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning)));
const std::string out = validator.GetFormattedMessage();
EXPECT_THAT(out, HasSubstr("manufacturer: manufacturer"));
EXPECT_THAT(out, HasSubstr("model: model"));
EXPECT_THAT(out, HasSubstr("fused: 0"));
EXPECT_THAT(out, HasSubstr("version: 2"));
}
TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV1MissingField) {
const std::vector<uint8_t> device_info = cppbor::Map()
.add("brand", "brand")
.add("security_level", "tee")
.add("version", 1)
.canonicalize()
.encode();
DeviceInfoValidator validator(kDeviceVersion1);
CborMessageStatus result = validator.Parse(device_info);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, kCborValidateError);
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
EXPECT_EQ(1u, msgs.size());
EXPECT_EQ(kCborValidateError, msgs[0].first);
EXPECT_THAT(msgs[0].second, HasSubstr("att_id_state is missing"));
}
TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV1) {
DeviceInfoValidator validator(kDeviceVersion1);
const std::vector<uint8_t> device_info = BuildDeviceInfo(kDeviceVersion1);
CborMessageStatus result = validator.Parse(device_info);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning)));
const std::string out = validator.GetFormattedMessage();
EXPECT_THAT(out, HasSubstr("board: board"));
EXPECT_THAT(out, HasSubstr("version: 1"));
}
} // namespace util
} // namespace wvoec

View File

@@ -0,0 +1,294 @@
// 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "signed_csr_payload_validator.h"
using ::testing::AllOf;
using ::testing::Ge;
using ::testing::HasSubstr;
using ::testing::Le;
namespace wvoec {
namespace util {
namespace {
std::vector<uint8_t> GetDefaultProtectedData() {
return cppbor::Map().add(1, /*ES256=*/-7).encode();
}
cppbor::Map GetDefaultDeviceInfoMap() {
return cppbor::Map()
.add("brand", "brand")
.add("manufacturer", "manufacturer")
.add("product", "product")
.add("model", "model")
.add("vb_state", "green")
.add("bootloader_state", "unlocked")
.add("vbmeta_digest", cppbor::Bstr(std::vector<uint8_t>()))
.add("os_version", "os_version")
.add("system_patch_level", 202312)
.add("boot_patch_level", 20231201)
.add("vendor_patch_level", 20231201)
.add("security_level", "tee")
.add("device", "device")
.add("fused", 0);
}
std::vector<uint8_t> GetDefaultCsrPayload() {
// 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
// ]
return cppbor::Array()
.add(3)
.add("widevine")
.add(GetDefaultDeviceInfoMap())
.add(/*KeysToSign*/ cppbor::Array())
.encode();
}
std::vector<uint8_t> GetDefaultPayloadData() {
// payload: bstr .cbor DataToBeSigned / nil
// DataToBeSigned = [
// challenge: bstr .size (0..64),
// bstr .cbor CsrPayload,
// ]
const std::vector<uint8_t> challenge(64, 0xC0);
return cppbor::Array()
.add(cppbor::Bstr(challenge))
.add(cppbor::Bstr(GetDefaultCsrPayload()))
.encode();
}
std::vector<uint8_t> GetDefaultSignature() {
return std::vector<uint8_t>(64, 0xA0);
}
std::vector<uint8_t> GetDefaultSignedCsrPayload() {
// SignedData<CsrPayload> = [
// protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 /
// AlgorithmES384 },
// unprotected: {},
// payload: bstr .cbor DataToBeSigned / nil,
// signature: bstr
// ]
return cppbor::Array()
.add(cppbor::Bstr(GetDefaultProtectedData()))
.add(/*unprotected*/ cppbor::Map())
.add(cppbor::Bstr(GetDefaultPayloadData()))
.add(cppbor::Bstr(GetDefaultSignature()))
.encode();
}
} // namespace
TEST(OEMCryptoSignedCsrPayloadValidatorTest, SignedCsrPayloadParseError) {
const std::vector<uint8_t> signed_csr_payload = GetDefaultSignedCsrPayload();
const std::vector<uint8_t> signed_csr_payload_bad(
signed_csr_payload.begin(), signed_csr_payload.end() - 1);
SignedCsrPayloadValidator validator;
CborMessageStatus result = validator.Parse(signed_csr_payload_bad);
EXPECT_EQ(kCborParseError, result);
result = validator.Validate();
EXPECT_EQ(kCborParseError, result);
EXPECT_EQ("", validator.GetRawMessage());
EXPECT_EQ("", validator.GetFormattedMessage());
}
TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataNotMap) {
cppbor::Array protected_data_array = cppbor::Array().add(1).add(-7);
std::vector<uint8_t> signed_csr_payload =
cppbor::Array()
.add(cppbor::Bstr(protected_data_array.encode()))
.add(/*unprotected*/ cppbor::Map())
.add(cppbor::Bstr(GetDefaultPayloadData()))
.add(cppbor::Bstr(GetDefaultSignature()))
.encode();
SignedCsrPayloadValidator validator;
CborMessageStatus result = validator.Parse(signed_csr_payload);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, kCborValidateFatal);
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
EXPECT_EQ(1u, msgs.size());
EXPECT_EQ(kCborValidateFatal, msgs[0].first);
EXPECT_THAT(msgs[0].second, HasSubstr("ProtectedParams must be a CBOR map"));
}
TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataMapMissingKey) {
cppbor::Map protected_data = cppbor::Map();
std::vector<uint8_t> signed_csr_payload =
cppbor::Array()
.add(cppbor::Bstr(protected_data.encode()))
.add(/*unprotected*/ cppbor::Map())
.add(cppbor::Bstr(GetDefaultPayloadData()))
.add(cppbor::Bstr(GetDefaultSignature()))
.encode();
SignedCsrPayloadValidator validator;
CborMessageStatus result = validator.Parse(signed_csr_payload);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, kCborValidateFatal);
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
EXPECT_EQ(1u, msgs.size());
EXPECT_EQ(kCborValidateFatal, msgs[0].first);
EXPECT_THAT(msgs[0].second, HasSubstr("ProtectedParams is empty"));
}
TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataMapKeyWarnings) {
cppbor::Map protected_data =
cppbor::Map().add(1, -7).add("1", -7).add(2, "abc");
std::vector<uint8_t> signed_csr_payload =
cppbor::Array()
.add(cppbor::Bstr(protected_data.encode()))
.add(/*unprotected*/ cppbor::Map())
.add(cppbor::Bstr(GetDefaultPayloadData()))
.add(cppbor::Bstr(GetDefaultSignature()))
.encode();
SignedCsrPayloadValidator validator;
CborMessageStatus result = validator.Parse(signed_csr_payload);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, kCborValidateWarning);
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
const bool unexpected_key_type_found = std::any_of(
msgs.begin(), msgs.end(),
[](const std::pair<CborMessageStatus, std::string>& p) {
return p.second.find("Unsupported key type") != std::string::npos;
});
EXPECT_EQ(true, unexpected_key_type_found);
const bool unexpected_key_value_found = std::any_of(
msgs.begin(), msgs.end(),
[](const std::pair<CborMessageStatus, std::string>& p) {
return p.second.find("Unsupported key value") != std::string::npos;
});
EXPECT_EQ(true, unexpected_key_value_found);
const bool unexpected_entry_found =
std::any_of(msgs.begin(), msgs.end(),
[](const std::pair<CborMessageStatus, std::string>& p) {
return p.second.find("ProtectedParams expects 1 entry") !=
std::string::npos;
});
EXPECT_EQ(true, unexpected_entry_found);
}
TEST(OEMCryptoSignedCsrPayloadValidatorTest, InvalidChallengeAndPayload) {
const std::vector<uint8_t> challenge(65, 0xC0); // bad length
const std::vector<uint8_t> csr_payload =
cppbor::Array()
.add(2) // wrong version
.add("widevine")
.add(GetDefaultDeviceInfoMap())
.add(/*KeysToSign*/ cppbor::Array())
.encode();
const std::vector<uint8_t> payload = cppbor::Array()
.add(cppbor::Bstr(challenge))
.add(cppbor::Bstr(csr_payload))
.encode();
std::vector<uint8_t> signed_csr_payload =
cppbor::Array()
.add(cppbor::Bstr(GetDefaultProtectedData()))
.add(/*unprotected*/ cppbor::Map())
.add(cppbor::Bstr(payload))
.add(cppbor::Bstr(GetDefaultSignature()))
.encode();
SignedCsrPayloadValidator validator;
CborMessageStatus result = validator.Parse(signed_csr_payload);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, kCborValidateError);
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
EXPECT_EQ(2u, msgs.size());
const bool challenge_error_found = std::any_of(
msgs.begin(), msgs.end(),
[](const std::pair<CborMessageStatus, std::string>& p) {
return p.second.find("Challenge size must be between 0 and 64 bytes") !=
std::string::npos;
});
EXPECT_EQ(true, challenge_error_found);
const bool csr_payload_error_found = std::any_of(
msgs.begin(), msgs.end(),
[](const std::pair<CborMessageStatus, std::string>& p) {
return p.second.find(
"CSR payload version must be must be equal to 3") !=
std::string::npos;
});
EXPECT_EQ(true, csr_payload_error_found);
}
TEST(OEMCryptoSignedCsrPayloadValidatorTest, KeysToSignEmptyList) {
const std::vector<uint8_t> challenge(64, 0xC0);
const std::vector<uint8_t> csr_payload =
cppbor::Array()
.add(3)
.add("widevine")
.add(GetDefaultDeviceInfoMap())
.add(/*KeysToSign*/ cppbor::Bstr()) // wrong type, expect list
.encode();
const std::vector<uint8_t> payload = cppbor::Array()
.add(cppbor::Bstr(challenge))
.add(cppbor::Bstr(csr_payload))
.encode();
std::vector<uint8_t> signed_csr_payload =
cppbor::Array()
.add(cppbor::Bstr(GetDefaultProtectedData()))
.add(/*unprotected*/ cppbor::Map())
.add(cppbor::Bstr(payload))
.add(cppbor::Bstr(GetDefaultSignature()))
.encode();
SignedCsrPayloadValidator validator;
CborMessageStatus result = validator.Parse(signed_csr_payload);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, kCborValidateFatal);
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
EXPECT_EQ(1u, msgs.size());
EXPECT_EQ(kCborValidateFatal, msgs[0].first);
EXPECT_THAT(msgs[0].second, HasSubstr("Keys must be a CBOR array"));
}
TEST(OEMCryptoSignedCsrPayloadValidatorTest, SignatureMissing) {
std::vector<uint8_t> signed_csr_payload =
cppbor::Array()
.add(cppbor::Bstr(GetDefaultProtectedData()))
.add(/*unprotected*/ cppbor::Map())
.add(cppbor::Bstr(GetDefaultPayloadData()))
.add(cppbor::Bstr(std::vector<uint8_t>())) // empty signature
.encode();
SignedCsrPayloadValidator validator;
CborMessageStatus result = validator.Parse(signed_csr_payload);
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, kCborValidateError);
const std::vector<std::pair<CborMessageStatus, std::string>> msgs =
validator.GetValidateMessages();
EXPECT_EQ(1u, msgs.size());
EXPECT_EQ(kCborValidateError, msgs[0].first);
EXPECT_THAT(msgs[0].second, HasSubstr("CoseSign1 signature is missing"));
}
TEST(OEMCryptoSignedCsrPayloadValidatorTest, ValidateOk) {
SignedCsrPayloadValidator validator;
CborMessageStatus result = validator.Parse(GetDefaultSignedCsrPayload());
EXPECT_EQ(kCborParseOk, result);
result = validator.Validate();
EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning)));
const std::string out = validator.GetFormattedMessage();
EXPECT_THAT(out, HasSubstr("1: ES256"));
EXPECT_THAT(out, HasSubstr("version: 3"));
EXPECT_THAT(out, HasSubstr("certificate_type: widevine"));
EXPECT_THAT(out, HasSubstr("keys_to_sign: []"));
}
} // namespace util
} // namespace wvoec