Source release 19.1.0
This commit is contained in:
81
oemcrypto/util/include/bcc_validator.h
Normal file
81
oemcrypto/util/include/bcc_validator.h
Normal 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_
|
||||
86
oemcrypto/util/include/cbor_validator.h
Normal file
86
oemcrypto/util/include/cbor_validator.h
Normal 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_
|
||||
54
oemcrypto/util/include/device_info_validator.h
Normal file
54
oemcrypto/util/include/device_info_validator.h
Normal 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_
|
||||
@@ -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.
|
||||
|
||||
44
oemcrypto/util/include/signed_csr_payload_validator.h
Normal file
44
oemcrypto/util/include/signed_csr_payload_validator.h
Normal 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_
|
||||
@@ -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',
|
||||
],
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
}
|
||||
|
||||
564
oemcrypto/util/src/bcc_validator.cpp
Normal file
564
oemcrypto/util/src/bcc_validator.cpp
Normal 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
|
||||
140
oemcrypto/util/src/cbor_validator.cpp
Normal file
140
oemcrypto/util/src/cbor_validator.cpp
Normal 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
|
||||
227
oemcrypto/util/src/device_info_validator.cpp
Normal file
227
oemcrypto/util/src/device_info_validator.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// Reference implementation utilities of OEMCrypto APIs
|
||||
//
|
||||
#include "device_info_validator.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvoec {
|
||||
namespace util {
|
||||
namespace {
|
||||
// Number of required device info properties returned from TEE for DeviceInfo
|
||||
// version v3.
|
||||
constexpr uint32_t kNumTeeDeviceInfoEntriesV3 = 14;
|
||||
const std::vector<std::string> kDeviceInfoKeysV3 = {"brand",
|
||||
"manufacturer",
|
||||
"product",
|
||||
"model",
|
||||
"device",
|
||||
"vb_state",
|
||||
"bootloader_state",
|
||||
"vbmeta_digest",
|
||||
"os_version",
|
||||
"system_patch_level",
|
||||
"boot_patch_level",
|
||||
"vendor_patch_level",
|
||||
"security_level",
|
||||
"fused"};
|
||||
|
||||
struct AttestationIdEntry {
|
||||
const char* id;
|
||||
bool alwaysValidate;
|
||||
};
|
||||
|
||||
// Attestation Id and whether it is required.
|
||||
constexpr AttestationIdEntry kAttestationIdEntrySet[] = {{"brand", false},
|
||||
{"manufacturer", true},
|
||||
{"product", true},
|
||||
{"model", true},
|
||||
{"device", false}};
|
||||
} // namespace
|
||||
|
||||
CborMessageStatus DeviceInfoValidator::Parse(
|
||||
const std::vector<uint8_t>& device_info) {
|
||||
message_status_ = CborValidator::Parse(device_info);
|
||||
device_info_bytes_ = device_info;
|
||||
return message_status_;
|
||||
}
|
||||
|
||||
CborMessageStatus DeviceInfoValidator::Validate() {
|
||||
if (message_status_ != kCborParseOk) return message_status_;
|
||||
std::unique_ptr<cppbor::Item> parsed_device_info =
|
||||
std::get<0>(parse_result())->clone();
|
||||
if (!parsed_device_info) {
|
||||
AddValidationMessage(kCborValidateFatal, "Device info is empty.");
|
||||
return message_status_;
|
||||
}
|
||||
cppbor::Map* device_info_map = parsed_device_info->asMap();
|
||||
if (!device_info_map) {
|
||||
AddValidationMessage(
|
||||
kCborValidateFatal,
|
||||
"Device info is not a CBOR map. Actual type: " +
|
||||
CppborMajorTypeToString(parsed_device_info->type()));
|
||||
return message_status_;
|
||||
}
|
||||
if (device_info_map->canonicalize().encode() != device_info_bytes_) {
|
||||
AddValidationMessage(kCborValidateError,
|
||||
"Device info ordering is non-canonical.");
|
||||
}
|
||||
const cppbor::Item* security_level =
|
||||
GetMapEntry(*device_info_map, "security_level");
|
||||
const bool is_tee_device_info = security_level && security_level->asTstr() &&
|
||||
security_level->asTstr()->value() == "tee";
|
||||
std::set<std::string> previous_keys;
|
||||
switch (version_number_) {
|
||||
case 3:
|
||||
if (is_tee_device_info &&
|
||||
device_info_map->size() != kNumTeeDeviceInfoEntriesV3) {
|
||||
AddValidationMessage(
|
||||
kCborValidateError,
|
||||
"Incorrect number of TEE device info entries. Expected " +
|
||||
std::to_string(kNumTeeDeviceInfoEntriesV3) + " but got " +
|
||||
std::to_string(device_info_map->size()));
|
||||
}
|
||||
// TEE IRPC instances require all entries to be present in device info.
|
||||
// Non-TEE instances may omit `os_version`.
|
||||
if (!is_tee_device_info &&
|
||||
(device_info_map->size() != kNumTeeDeviceInfoEntriesV3 &&
|
||||
device_info_map->size() != kNumTeeDeviceInfoEntriesV3 - 1)) {
|
||||
AddValidationMessage(
|
||||
kCborValidateError,
|
||||
"Incorrect number of non-TEE device info entries. Expected " +
|
||||
std::to_string(kNumTeeDeviceInfoEntriesV3 - 1) + " but got " +
|
||||
std::to_string(device_info_map->size()));
|
||||
}
|
||||
for (auto const& entry : *device_info_map) {
|
||||
if (!entry.first->asTstr()) {
|
||||
AddValidationMessage(
|
||||
kCborValidateError,
|
||||
"Unexpected entry key type. Expected TSTR, but got " +
|
||||
CppborMajorTypeToString(entry.first->type()));
|
||||
continue;
|
||||
}
|
||||
const std::string& key = entry.first->asTstr()->value();
|
||||
if (previous_keys.find(key) != previous_keys.end()) {
|
||||
AddValidationMessage(kCborValidateError,
|
||||
"Duplicate device info entry: " + key);
|
||||
}
|
||||
previous_keys.insert(key);
|
||||
if (std::find(kDeviceInfoKeysV3.begin(), kDeviceInfoKeysV3.end(),
|
||||
key) == kDeviceInfoKeysV3.end()) {
|
||||
AddValidationMessage(kCborValidateError,
|
||||
"Unrecognized device info entry: " + key);
|
||||
}
|
||||
}
|
||||
// Checks for the required fields that only apply to v3.
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||
"system_patch_level");
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||
"boot_patch_level");
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||
"vendor_patch_level");
|
||||
// Fall through
|
||||
CORE_UTIL_FALLTHROUGH;
|
||||
case 2:
|
||||
for (const auto& entry : kAttestationIdEntrySet) {
|
||||
if (entry.alwaysValidate) {
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, entry.id);
|
||||
}
|
||||
}
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "vb_state");
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR,
|
||||
"bootloader_state");
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::BSTR, "vbmeta_digest");
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||
"system_patch_level");
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||
"boot_patch_level");
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT,
|
||||
"vendor_patch_level");
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, "fused");
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "security_level");
|
||||
if (is_tee_device_info) {
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "os_version");
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "security_level");
|
||||
CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "att_id_state");
|
||||
break;
|
||||
default:
|
||||
AddValidationMessage(
|
||||
kCborValidateFatal,
|
||||
"Unrecognized version: " + std::to_string(version_number_));
|
||||
return message_status_;
|
||||
}
|
||||
|
||||
if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk;
|
||||
return message_status_;
|
||||
}
|
||||
|
||||
void DeviceInfoValidator::CheckDeviceInfoMapEntry(
|
||||
const cppbor::Map& device_info, cppbor::MajorType major_type,
|
||||
const std::string& entry_name) {
|
||||
const std::string error = CheckMapEntry(device_info, major_type, entry_name);
|
||||
if (!error.empty()) {
|
||||
AddValidationMessage(kCborValidateError, error);
|
||||
}
|
||||
}
|
||||
|
||||
std::string DeviceInfoValidator::GetFormattedMessage() const {
|
||||
if (message_status_ == kCborUninitialized ||
|
||||
message_status_ == kCborParseError ||
|
||||
message_status_ == kCborValidateFatal) {
|
||||
return std::string();
|
||||
}
|
||||
const cppbor::Item* parsed_item = std::get<0>(parse_result()).get();
|
||||
if (parsed_item == nullptr) {
|
||||
return "<null>";
|
||||
}
|
||||
// Writes YAML-formatted output to |msg_ss_|.
|
||||
std::stringstream msg_ss;
|
||||
msg_ss << "---\n";
|
||||
msg_ss << "DEVICE INFO MAP:\n";
|
||||
|
||||
for (auto const& entry : *(parsed_item->asMap())) {
|
||||
auto const& entry_value = entry.second;
|
||||
// Device info map only allows TSTR key type.
|
||||
if (!entry.first->asTstr()) continue;
|
||||
const std::string& name = entry.first->asTstr()->value();
|
||||
msg_ss << " " << name << ": ";
|
||||
switch (entry_value->type()) {
|
||||
case cppbor::TSTR: {
|
||||
const std::string val = entry_value->asTstr()->value().empty()
|
||||
? "<null>"
|
||||
: entry_value->asTstr()->value();
|
||||
msg_ss << val << "\n";
|
||||
break;
|
||||
}
|
||||
case cppbor::UINT:
|
||||
msg_ss << std::to_string(entry_value->asUint()->value()) << "\n";
|
||||
break;
|
||||
case cppbor::NINT:
|
||||
msg_ss << std::to_string(entry_value->asNint()->value()) << "\n";
|
||||
break;
|
||||
case cppbor::BSTR: {
|
||||
const std::vector<uint8_t>& bytes = entry_value->asBstr()->value();
|
||||
const std::string val =
|
||||
bytes.empty() ? "<null>" : wvutil::b2a_hex(bytes);
|
||||
msg_ss << val << "\n";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
msg_ss << "Unsupported type ("
|
||||
<< CppborMajorTypeToString(entry_value->type()) << ")\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
msg_ss << "...\n";
|
||||
return msg_ss.str();
|
||||
}
|
||||
} // namespace util
|
||||
} // namespace wvoec
|
||||
@@ -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());
|
||||
|
||||
393
oemcrypto/util/src/signed_csr_payload_validator.cpp
Normal file
393
oemcrypto/util/src/signed_csr_payload_validator.cpp
Normal 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
|
||||
147
oemcrypto/util/test/bcc_validator_unittest.cpp
Normal file
147
oemcrypto/util/test/bcc_validator_unittest.cpp
Normal 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
|
||||
204
oemcrypto/util/test/device_info_validator_unittest.cpp
Normal file
204
oemcrypto/util/test/device_info_validator_unittest.cpp
Normal 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
|
||||
294
oemcrypto/util/test/signed_csr_payload_validator_unittest.cpp
Normal file
294
oemcrypto/util/test/signed_csr_payload_validator_unittest.cpp
Normal 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
|
||||
Reference in New Issue
Block a user