Add function body of OEMCrypto_LoadCasECMKeys() with a few TODOs. At the end, Cas content keys and IVs shall be installed to key slot by WTPIs, which will be declared in the next CL. Test: opk_ta Bug: 241146324 Merged from https://widevine-internal-review.googlesource.com/169378 Merged from https://widevine-internal-review.googlesource.com/178139 Change-Id: Idf3930ec916e95012067741e46254018fc6e4381
2269 lines
96 KiB
C++
2269 lines
96 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine
|
|
// License Agreement.
|
|
//
|
|
// OEMCrypto unit tests
|
|
//
|
|
|
|
#include "oec_session_util.h"
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
#include <openssl/aes.h>
|
|
#include <openssl/bio.h>
|
|
#include <openssl/cmac.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/x509.h>
|
|
#include <openssl/x509_vfy.h>
|
|
#include <stdint.h>
|
|
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "OEMCryptoCENC.h"
|
|
#include "clock.h"
|
|
#include "core_message_deserialize.h"
|
|
#include "core_message_serialize.h"
|
|
#include "disallow_copy_and_assign.h"
|
|
#include "log.h"
|
|
#include "odk_attributes.h"
|
|
#include "odk_structs.h"
|
|
#include "oec_device_features.h"
|
|
#include "oec_test_data.h"
|
|
#include "oemcrypto_corpus_generator_helper.h"
|
|
#include "oemcrypto_types.h"
|
|
#include "platform.h"
|
|
#include "string_conversions.h"
|
|
#include "test_sleep.h"
|
|
|
|
using namespace std;
|
|
|
|
using testing::AnyOf;
|
|
|
|
// GTest requires PrintTo to be in the same namespace as the thing it prints,
|
|
// which is std::vector in this case.
|
|
namespace std {
|
|
void PrintTo(const vector<uint8_t>& value, ostream* os) {
|
|
*os << wvutil::b2a_hex(value);
|
|
}
|
|
} // namespace std
|
|
|
|
namespace wvoec {
|
|
namespace {
|
|
using oemcrypto_core_message::features::CoreMessageFeatures;
|
|
|
|
constexpr size_t kTestSubsampleSectionSize = 256;
|
|
|
|
// Fill objects by consuming a source buffer of fuzzed data.
|
|
class FuzzedData {
|
|
public:
|
|
FuzzedData(const uint8_t* source, size_t source_size)
|
|
: source_(source), source_size_(source_size) {}
|
|
|
|
// Fill the destination buffer with fuzzed data.
|
|
void Fill(void* destination, size_t destination_size) {
|
|
if (source_ && destination) {
|
|
const size_t fill_size = std::min(source_size_, destination_size);
|
|
memcpy(destination, source_, fill_size);
|
|
source_ += fill_size;
|
|
source_size_ -= fill_size;
|
|
}
|
|
}
|
|
|
|
private:
|
|
const uint8_t* source_;
|
|
size_t source_size_;
|
|
};
|
|
|
|
// Encrypt a block of data using CTR mode.
|
|
void EncryptCTR(const vector<uint8_t>& in_buffer, const uint8_t* key,
|
|
const uint8_t* starting_iv, vector<uint8_t>* out_buffer) {
|
|
ASSERT_NE(nullptr, key);
|
|
ASSERT_NE(nullptr, starting_iv);
|
|
ASSERT_NE(nullptr, out_buffer);
|
|
AES_KEY aes_key;
|
|
AES_set_encrypt_key(key, AES_BLOCK_SIZE * 8, &aes_key);
|
|
out_buffer->resize(in_buffer.size());
|
|
|
|
uint8_t iv[AES_BLOCK_SIZE]; // Current iv.
|
|
|
|
memcpy(iv, &starting_iv[0], AES_BLOCK_SIZE);
|
|
size_t l = 0; // byte index into encrypted subsample.
|
|
while (l < in_buffer.size()) {
|
|
uint8_t aes_output[AES_BLOCK_SIZE];
|
|
AES_encrypt(iv, aes_output, &aes_key);
|
|
for (size_t n = 0; n < AES_BLOCK_SIZE && l < in_buffer.size(); n++, l++) {
|
|
(*out_buffer)[l] = aes_output[n] ^ in_buffer[l];
|
|
}
|
|
ctr128_inc64(1, iv);
|
|
}
|
|
}
|
|
|
|
// Uses OEMCrypto to decrypt some random data in 'cenc' mode. This function
|
|
// assumes that the correct key is already selected in the session. It requires
|
|
// the plaintext of that key so that it can encrypt the test data. It resizes
|
|
// the provided vectors and fills them with the expected and actual decrypt
|
|
// results. Returns the result of OEMCrypto_DecryptCENC().
|
|
OEMCryptoResult DecryptCTR(const vector<uint8_t>& key_handle,
|
|
const uint8_t* key, vector<uint8_t>* expected_data,
|
|
vector<uint8_t>* actual_data) {
|
|
vector<uint8_t> encrypted_data(kTestSubsampleSectionSize);
|
|
expected_data->resize(encrypted_data.size());
|
|
actual_data->resize(encrypted_data.size());
|
|
|
|
// Create test sample description
|
|
OEMCrypto_SampleDescription sample_description;
|
|
OEMCrypto_SubSampleDescription subsample_description;
|
|
GenerateSimpleSampleDescription(encrypted_data, *actual_data,
|
|
&sample_description, &subsample_description);
|
|
|
|
// Generate test data
|
|
EXPECT_EQ(GetRandBytes(expected_data->data(), expected_data->size()), 1);
|
|
EncryptCTR(*expected_data, key, &sample_description.iv[0], &encrypted_data);
|
|
|
|
// Create the pattern description (always 0,0 for 'cenc')
|
|
OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0};
|
|
|
|
// Decrypt the data
|
|
return OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
|
&sample_description, 1, &pattern);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int GetRandBytes(unsigned char* buf, size_t num) {
|
|
// returns 1 on success, -1 if not supported, or 0 if other failure.
|
|
return RAND_bytes(buf, static_cast<int>(num));
|
|
}
|
|
|
|
// Does the boilerplate to fill out sample and subsample descriptions for
|
|
// decrypting a single contiguous block of encrypted data to clear memory, which
|
|
// is a common operation for tests. Generates a random IV which can be used to
|
|
// encrypt the input buffer.
|
|
void GenerateSimpleSampleDescription(
|
|
const std::vector<uint8_t>& in, std::vector<uint8_t>& out,
|
|
OEMCrypto_SampleDescription* sample,
|
|
OEMCrypto_SubSampleDescription* subsample) {
|
|
// Generate test data
|
|
EXPECT_EQ(GetRandBytes(&sample->iv[0], KEY_IV_SIZE), 1);
|
|
|
|
// Describe the test data
|
|
sample->buffers.input_data = in.data();
|
|
sample->buffers.input_data_length = in.size();
|
|
subsample->num_bytes_clear = 0;
|
|
subsample->num_bytes_encrypted = sample->buffers.input_data_length;
|
|
subsample->subsample_flags =
|
|
OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample;
|
|
subsample->block_offset = 0;
|
|
sample->subsamples = subsample;
|
|
sample->subsamples_length = 1;
|
|
|
|
// Describe the output
|
|
OEMCrypto_DestBufferDesc& out_buffer_descriptor =
|
|
sample->buffers.output_descriptor;
|
|
out_buffer_descriptor.type = OEMCrypto_BufferType_Clear;
|
|
out_buffer_descriptor.buffer.clear.clear_buffer = out.data();
|
|
out_buffer_descriptor.buffer.clear.clear_buffer_length = out.size();
|
|
}
|
|
|
|
// Increment counter for AES-CTR. The CENC spec specifies we increment only
|
|
// the low 64 bits of the IV counter, and leave the high 64 bits alone. This
|
|
// is different from the BoringSSL implementation, so we implement the CTR loop
|
|
// ourselves.
|
|
void ctr128_inc64(int64_t increaseBy, uint8_t* iv) {
|
|
ASSERT_NE(nullptr, iv);
|
|
uint64_t* counterBuffer = reinterpret_cast<uint64_t*>(&iv[8]);
|
|
(*counterBuffer) =
|
|
wvutil::htonll64(wvutil::ntohll64(*counterBuffer) + increaseBy);
|
|
}
|
|
|
|
// Some compilers don't like the macro htonl within an ASSERT_EQ.
|
|
uint32_t htonl_fnc(uint32_t x) { return htonl(x); }
|
|
|
|
void dump_boringssl_error() {
|
|
// BoringSSL and OpenSSL disagree about what the type of an error code is, so
|
|
// we must use "auto" here.
|
|
while (auto err = ERR_get_error()) {
|
|
char buffer[120];
|
|
ERR_error_string_n(err, buffer, sizeof(buffer));
|
|
cerr << "BoringSSL Error -- " << buffer << "\n";
|
|
}
|
|
}
|
|
|
|
// A smart pointer for BoringSSL objects. It uses the specified free function
|
|
// to release resources and free memory when the pointer is deleted.
|
|
template <typename T, void (*func)(T*)>
|
|
class boringssl_ptr {
|
|
public:
|
|
explicit boringssl_ptr(T* p = nullptr) : ptr_(p) {}
|
|
~boringssl_ptr() {
|
|
if (ptr_) func(ptr_);
|
|
}
|
|
T& operator*() const { return *ptr_; }
|
|
T* operator->() const { return ptr_; }
|
|
T* get() const { return ptr_; }
|
|
bool NotNull() const { return ptr_; }
|
|
|
|
private:
|
|
T* ptr_;
|
|
CORE_DISALLOW_COPY_AND_ASSIGN(boringssl_ptr);
|
|
};
|
|
|
|
Test_PST_Report::Test_PST_Report(const std::string& pst_in,
|
|
OEMCrypto_Usage_Entry_Status status_in)
|
|
: status(status_in), pst(pst_in) {
|
|
time_created = wvutil::Clock().GetCurrentTime();
|
|
}
|
|
|
|
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
|
|
class CoreResponse, class ResponseData>
|
|
OEMCryptoResult
|
|
RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse, ResponseData>::
|
|
SignAndCreateRequestWithCustomBufferLengths(bool verify_request) {
|
|
// In the real world, a message should be signed by the client and
|
|
// verified by the server. This simulates that.
|
|
size_t gen_signature_length = 0;
|
|
size_t core_message_length = 0;
|
|
constexpr size_t small_size = 42; // arbitrary.
|
|
if (RequestHasNonce()) {
|
|
session()->GenerateNonce();
|
|
}
|
|
uint32_t session_id = session()->session_id();
|
|
GetDefaultRequestSignatureAndCoreMessageLengths<PrepAndSignRequest>(
|
|
session_id, small_size, &gen_signature_length, &core_message_length);
|
|
// Used to test request APIs with varying lengths of core message.
|
|
core_message_length =
|
|
std::max(core_message_length, required_core_message_size_);
|
|
// Used to test request APIs with varying lengths of signature.
|
|
gen_signature_length =
|
|
std::max(gen_signature_length, required_request_signature_size_);
|
|
// Make the message buffer a little bigger than the core message, or the
|
|
// required size, whichever is larger.
|
|
size_t message_size =
|
|
std::max(required_message_size_, core_message_length + small_size);
|
|
vector<uint8_t> data(message_size);
|
|
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
|
|
if (ShouldGenerateCorpus()) {
|
|
WriteRequestApiCorpus<CoreRequest>(gen_signature_length,
|
|
core_message_length, data);
|
|
}
|
|
|
|
vector<uint8_t> gen_signature(gen_signature_length);
|
|
OEMCryptoResult result = PrepAndSignRequest(
|
|
session()->session_id(), data.data(), data.size(), &core_message_length,
|
|
gen_signature.data(), &gen_signature_length);
|
|
// We need to fill in core request and verify signature only for calls other
|
|
// than OEMCryptoMemory buffer overflow test. Any test other than buffer
|
|
// overflow will pass true.
|
|
if (result == OEMCrypto_SUCCESS) {
|
|
gen_signature.resize(gen_signature_length);
|
|
}
|
|
if (!verify_request || result != OEMCrypto_SUCCESS) return result;
|
|
std::string core_message(reinterpret_cast<char*>(data.data()),
|
|
core_message_length);
|
|
FillAndVerifyCoreRequest(core_message);
|
|
VerifyRequestSignature(data, gen_signature, core_message_length);
|
|
return result;
|
|
}
|
|
|
|
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
|
|
class CoreResponse, class ResponseData>
|
|
void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
|
|
ResponseData>::SetEncryptAndSignResponseLengths() {
|
|
encrypted_response_length_ = encrypted_response_.size();
|
|
response_signature_length_ = response_signature_.size();
|
|
}
|
|
|
|
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
|
|
class CoreResponse, class ResponseData>
|
|
void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
|
|
ResponseData>::VerifyEncryptAndSignResponseLengths() const {
|
|
EXPECT_NE(encrypted_response_length_, 0u);
|
|
EXPECT_EQ(encrypted_response_length_, encrypted_response_.size());
|
|
EXPECT_NE(response_signature_length_, 0u);
|
|
EXPECT_EQ(response_signature_length_, response_signature_.size());
|
|
}
|
|
|
|
template <PrepAndSignRequest_t PrepAndSignRequest>
|
|
void GetDefaultRequestSignatureAndCoreMessageLengths(
|
|
uint32_t& session_id, const size_t& small_size,
|
|
size_t* gen_signature_length, size_t* core_message_length) {
|
|
vector<uint8_t> data(small_size);
|
|
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
|
|
ASSERT_EQ(
|
|
PrepAndSignRequest(session_id, data.data(), data.size(),
|
|
core_message_length, nullptr, gen_signature_length),
|
|
OEMCrypto_ERROR_SHORT_BUFFER);
|
|
}
|
|
|
|
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
|
|
class CoreResponse, class ResponseData>
|
|
void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
|
|
ResponseData>::InjectFuzzedRequestData(uint8_t* data,
|
|
size_t size) {
|
|
OEMCrypto_Request_Fuzz fuzz_structure;
|
|
// Copy data into fuzz structure, cap signature length at 1mb as it will be
|
|
// used to initialize signature vector.
|
|
memcpy(&fuzz_structure, data, sizeof(fuzz_structure));
|
|
fuzz_structure.signature_length =
|
|
std::min(fuzz_structure.signature_length, MB);
|
|
vector<uint8_t> signature(fuzz_structure.signature_length);
|
|
|
|
// Interpret rest of data as actual message buffer to request APIs.
|
|
uint8_t* message_ptr = data + sizeof(fuzz_structure);
|
|
size_t message_size = size - sizeof(fuzz_structure);
|
|
PrepAndSignRequest(session()->session_id(), message_ptr, message_size,
|
|
&fuzz_structure.core_message_length, signature.data(),
|
|
&fuzz_structure.signature_length);
|
|
}
|
|
|
|
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
|
|
class CoreResponse, class ResponseData>
|
|
OEMCrypto_Substring RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
|
|
ResponseData>::FindSubstring(const void* pointer,
|
|
size_t length) {
|
|
OEMCrypto_Substring substring;
|
|
if (length == 0 || pointer == nullptr) {
|
|
substring.offset = 0;
|
|
substring.length = 0;
|
|
} else {
|
|
substring.offset = reinterpret_cast<const uint8_t*>(pointer) -
|
|
reinterpret_cast<const uint8_t*>(&response_data_);
|
|
substring.length = length;
|
|
}
|
|
return substring;
|
|
}
|
|
|
|
void ProvisioningRoundTrip::PrepareSession(
|
|
const wvoec::WidevineKeybox& keybox) {
|
|
ASSERT_NO_FATAL_FAILURE(session_->open());
|
|
if (global_features.provisioning_method == OEMCrypto_Keybox) {
|
|
session_->GenerateDerivedKeysFromKeybox(keybox);
|
|
encryptor_ = session_->key_deriver();
|
|
} else if (global_features.provisioning_method ==
|
|
OEMCrypto_BootCertificateChain) {
|
|
// TODO(chelu): change this to CSR provisioning.
|
|
session_->LoadOEMCert(true);
|
|
session_->GenerateRsaSessionKey(&message_key_, &encrypted_message_key_);
|
|
encryptor_.set_enc_key(message_key_);
|
|
} else {
|
|
EXPECT_EQ(global_features.provisioning_method, OEMCrypto_OEMCertificate);
|
|
session_->LoadOEMCert(true);
|
|
session_->GenerateRsaSessionKey(&message_key_, &encrypted_message_key_);
|
|
encryptor_.set_enc_key(message_key_);
|
|
}
|
|
}
|
|
|
|
void ProvisioningRoundTrip::VerifyRequestSignature(
|
|
const vector<uint8_t>& data, const vector<uint8_t>& generated_signature,
|
|
size_t /* core_message_length */) {
|
|
if (global_features.provisioning_method == OEMCrypto_OEMCertificate) {
|
|
session()->VerifyRsaSignature(data, generated_signature.data(),
|
|
generated_signature.size(), kSign_RSASSA_PSS);
|
|
} else {
|
|
EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox);
|
|
ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, generated_signature.size());
|
|
std::vector<uint8_t> expected_signature;
|
|
session()->key_deriver().ClientSignBuffer(data, &expected_signature);
|
|
ASSERT_EQ(expected_signature, generated_signature);
|
|
}
|
|
}
|
|
|
|
void ProvisioningRoundTrip::FillAndVerifyCoreRequest(
|
|
const std::string& core_message_string) {
|
|
EXPECT_TRUE(
|
|
oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage(
|
|
core_message_string, &core_request_));
|
|
EXPECT_EQ(global_features.api_version, core_request_.api_major_version);
|
|
EXPECT_EQ(session()->nonce(), core_request_.nonce);
|
|
EXPECT_EQ(session()->session_id(), core_request_.session_id);
|
|
}
|
|
|
|
void ProvisioningRoundTrip::CreateDefaultResponse() {
|
|
if (allowed_schemes_ != kSign_RSASSA_PSS) {
|
|
uint32_t algorithm_n = htonl(allowed_schemes_);
|
|
memcpy(response_data_.rsa_key, "SIGN", 4);
|
|
memcpy(response_data_.rsa_key + 4, &algorithm_n, 4);
|
|
memcpy(response_data_.rsa_key + 8, encoded_rsa_key_.data(),
|
|
encoded_rsa_key_.size());
|
|
response_data_.rsa_key_length = 8 + encoded_rsa_key_.size();
|
|
} else {
|
|
memcpy(response_data_.rsa_key, encoded_rsa_key_.data(),
|
|
encoded_rsa_key_.size());
|
|
response_data_.rsa_key_length = encoded_rsa_key_.size();
|
|
}
|
|
response_data_.nonce = session_->nonce();
|
|
if (encrypted_message_key_.size() > 0) {
|
|
ASSERT_LE(encrypted_message_key_.size(), kMaxTestRSAKeyLength);
|
|
memcpy(response_data_.enc_message_key, encrypted_message_key_.data(),
|
|
encrypted_message_key_.size());
|
|
response_data_.enc_message_key_length = encrypted_message_key_.size();
|
|
} else {
|
|
response_data_.enc_message_key_length = 0;
|
|
}
|
|
core_response_.key_type = OEMCrypto_RSA_Private_Key;
|
|
core_response_.enc_private_key =
|
|
FindSubstring(response_data_.rsa_key, response_data_.rsa_key_length);
|
|
core_response_.enc_private_key_iv = FindSubstring(
|
|
response_data_.rsa_key_iv, sizeof(response_data_.rsa_key_iv));
|
|
core_response_.encrypted_message_key = FindSubstring(
|
|
response_data_.enc_message_key, response_data_.enc_message_key_length);
|
|
}
|
|
|
|
void ProvisioningRoundTrip::EncryptAndSignResponse() {
|
|
encryptor_.PadAndEncryptProvisioningMessage(&response_data_,
|
|
&encrypted_response_data_);
|
|
core_response_.enc_private_key.length =
|
|
encrypted_response_data_.rsa_key_length;
|
|
SignResponse();
|
|
}
|
|
|
|
// We need this for provisioning response out of range tests where
|
|
// core response substring lengths are modified.
|
|
void ProvisioningRoundTrip::
|
|
EncryptAndSignResponseWithoutUpdatingEncPrivateKeyLength() {
|
|
encryptor_.PadAndEncryptProvisioningMessage(&response_data_,
|
|
&encrypted_response_data_);
|
|
SignResponse();
|
|
}
|
|
|
|
void ProvisioningRoundTrip::SignResponse() {
|
|
CoreMessageFeatures features =
|
|
CoreMessageFeatures::DefaultFeatures(ODK_MAJOR_VERSION);
|
|
ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreProvisioningResponse(
|
|
features, core_response_, core_request_, &serialized_core_message_));
|
|
// Resizing for huge core message length unit tests.
|
|
serialized_core_message_.resize(
|
|
std::max(required_core_message_size_, serialized_core_message_.size()));
|
|
// Make the message buffer a just big enough, or the
|
|
// required size, whichever is larger.
|
|
const size_t message_size =
|
|
std::max(required_message_size_, serialized_core_message_.size() +
|
|
sizeof(encrypted_response_data_));
|
|
// Stripe the encrypted message.
|
|
encrypted_response_.resize(message_size);
|
|
for (size_t i = 0; i < encrypted_response_.size(); i++) {
|
|
encrypted_response_[i] = i & 0xFF;
|
|
}
|
|
ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size());
|
|
memcpy(encrypted_response_.data(), serialized_core_message_.data(),
|
|
serialized_core_message_.size());
|
|
ASSERT_GE(encrypted_response_.size(),
|
|
serialized_core_message_.size() + sizeof(encrypted_response_data_));
|
|
memcpy(encrypted_response_.data() + serialized_core_message_.size(),
|
|
reinterpret_cast<const uint8_t*>(&encrypted_response_data_),
|
|
sizeof(encrypted_response_data_));
|
|
if (global_features.provisioning_method == OEMCrypto_OEMCertificate) {
|
|
session()->GenerateDerivedKeysFromSessionKey();
|
|
}
|
|
session()->key_deriver().ServerSignBuffer(encrypted_response_.data(),
|
|
encrypted_response_.size(),
|
|
&response_signature_);
|
|
SetEncryptAndSignResponseLengths();
|
|
}
|
|
|
|
void ProvisioningRoundTrip::InjectFuzzedResponseData(const uint8_t* data,
|
|
size_t size) {
|
|
// Interpreting fuzz data as unencrypted core_response + message_data
|
|
FuzzedData fuzzed_data(data, size);
|
|
|
|
// Copy core_response from data and serialize.
|
|
fuzzed_data.Fill(&core_response_, sizeof(core_response_));
|
|
|
|
// Copy provisioning message data into response_data.
|
|
fuzzed_data.Fill(&response_data_, sizeof(response_data_));
|
|
|
|
// Set nonce to one from session to pass nonce checks.
|
|
response_data_.nonce = session()->nonce();
|
|
}
|
|
|
|
OEMCryptoResult ProvisioningRoundTrip::LoadResponse(Session* session) {
|
|
EXPECT_NE(session, nullptr);
|
|
// Write corpus for oemcrypto_load_provisioning_fuzz. Fuzz script expects
|
|
// unencrypted response from provisioning server as input corpus data.
|
|
// Data will be encrypted and signed again explicitly by fuzzer script after
|
|
// mutations.
|
|
if (ShouldGenerateCorpus()) {
|
|
const std::string file_name =
|
|
GetFileName("oemcrypto_load_provisioning_fuzz_seed_corpus");
|
|
// Corpus for license response fuzzer should be in the format:
|
|
// unencrypted (core_response + response_data).
|
|
AppendToFile(file_name, reinterpret_cast<const char*>(&core_response_),
|
|
sizeof(ODK_ParsedProvisioning));
|
|
AppendToFile(file_name, reinterpret_cast<const char*>(&response_data_),
|
|
sizeof(response_data_));
|
|
}
|
|
size_t wrapped_key_length = 0;
|
|
OEMCryptoResult sts = LoadResponseNoRetry(session, &wrapped_key_length);
|
|
if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts;
|
|
wrapped_rsa_key_.assign(wrapped_key_length, 0);
|
|
sts = LoadResponseNoRetry(session, &wrapped_key_length);
|
|
if (sts == OEMCrypto_SUCCESS) {
|
|
wrapped_rsa_key_.resize(wrapped_key_length);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
template <typename T>
|
|
const T* ProvisioningRoundTrip::RemapPointer(const T* response_pointer) const {
|
|
const uint8_t* original_pointer =
|
|
reinterpret_cast<const uint8_t*>(response_pointer);
|
|
size_t delta =
|
|
original_pointer - reinterpret_cast<const uint8_t*>(&response_data_);
|
|
// Base offset should be 0 if this is a v15 message, which is the only time
|
|
// this function is called.
|
|
size_t base_offset = serialized_core_message_.size();
|
|
const uint8_t* new_pointer = encrypted_response_.data() + delta + base_offset;
|
|
return reinterpret_cast<const T*>(new_pointer);
|
|
}
|
|
|
|
OEMCryptoResult ProvisioningRoundTrip::LoadResponseNoRetry(
|
|
Session* session, size_t* wrapped_key_length) {
|
|
EXPECT_NE(session, nullptr);
|
|
VerifyEncryptAndSignResponseLengths();
|
|
return OEMCrypto_LoadProvisioning(
|
|
session->session_id(), encrypted_response_.data(),
|
|
encrypted_response_.size(), serialized_core_message_.size(),
|
|
response_signature_.data(), response_signature_.size(),
|
|
wrapped_rsa_key_.data(), wrapped_key_length);
|
|
}
|
|
|
|
void ProvisioningRoundTrip::VerifyLoadFailed() {
|
|
if (wrapped_rsa_key_.size() == 0) return;
|
|
std::vector<uint8_t> zero(wrapped_rsa_key_.size(), 0);
|
|
ASSERT_EQ(zero, wrapped_rsa_key_);
|
|
}
|
|
|
|
void Provisioning40RoundTrip::PrepareSession(bool is_oem_key) {
|
|
const size_t buffer_size = 5000; // Make sure it is large enough.
|
|
std::vector<uint8_t> public_key(buffer_size);
|
|
size_t public_key_size = buffer_size;
|
|
std::vector<uint8_t> public_key_signature(buffer_size);
|
|
size_t public_key_signature_size = buffer_size;
|
|
std::vector<uint8_t> wrapped_private_key(buffer_size);
|
|
size_t wrapped_private_key_size = buffer_size;
|
|
OEMCrypto_PrivateKeyType key_type;
|
|
ASSERT_EQ(
|
|
OEMCrypto_SUCCESS,
|
|
OEMCrypto_GenerateCertificateKeyPair(
|
|
session()->session_id(), public_key.data(), &public_key_size,
|
|
public_key_signature.data(), &public_key_signature_size,
|
|
wrapped_private_key.data(), &wrapped_private_key_size, &key_type));
|
|
wrapped_private_key.resize(wrapped_private_key_size);
|
|
public_key.resize(public_key_size);
|
|
|
|
if (is_oem_key) {
|
|
wrapped_oem_key_ = wrapped_private_key;
|
|
oem_public_key_ = public_key;
|
|
oem_key_type_ = key_type;
|
|
} else {
|
|
wrapped_drm_key_ = wrapped_private_key;
|
|
drm_public_key_ = public_key;
|
|
drm_key_type_ = key_type;
|
|
}
|
|
}
|
|
|
|
void Provisioning40RoundTrip::FillAndVerifyCoreRequest(
|
|
const std::string& core_message_string) {
|
|
EXPECT_TRUE(
|
|
oemcrypto_core_message::deserialize::CoreProvisioning40RequestFromMessage(
|
|
core_message_string, &core_request_));
|
|
EXPECT_EQ(global_features.api_version, core_request_.api_major_version);
|
|
EXPECT_EQ(session()->nonce(), core_request_.nonce);
|
|
EXPECT_EQ(session()->session_id(), core_request_.session_id);
|
|
}
|
|
|
|
void Provisioning40RoundTrip::VerifyRequestSignature(
|
|
const vector<uint8_t>& data, const vector<uint8_t>& generated_signature,
|
|
size_t /* core_message_length */) {
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
session()->VerifySignature(data, generated_signature.data(),
|
|
generated_signature.size(), kSign_RSASSA_PSS));
|
|
}
|
|
|
|
OEMCryptoResult Provisioning40RoundTrip::LoadOEMCertResponse() {
|
|
EXPECT_GE(wrapped_oem_key_.size(), 0UL);
|
|
return OEMCrypto_InstallOemPrivateKey(
|
|
session()->session_id(), oem_key_type_,
|
|
reinterpret_cast<const uint8_t*>(wrapped_oem_key_.data()),
|
|
wrapped_oem_key_.size());
|
|
}
|
|
|
|
OEMCryptoResult Provisioning40RoundTrip::LoadDRMCertResponse() {
|
|
EXPECT_GE(wrapped_drm_key_.size(), 0UL);
|
|
return OEMCrypto_LoadDRMPrivateKey(session()->session_id(), drm_key_type_,
|
|
wrapped_drm_key_.data(),
|
|
wrapped_drm_key_.size());
|
|
}
|
|
|
|
void Provisioning40CastRoundTrip::PrepareSession() {
|
|
const size_t buffer_size = 5000; // Make sure it is large enough.
|
|
std::vector<uint8_t> public_key(buffer_size);
|
|
size_t public_key_size = buffer_size;
|
|
std::vector<uint8_t> public_key_signature(buffer_size);
|
|
size_t public_key_signature_size = buffer_size;
|
|
std::vector<uint8_t> wrapped_private_key(buffer_size);
|
|
size_t wrapped_private_key_size = buffer_size;
|
|
OEMCrypto_PrivateKeyType key_type;
|
|
ASSERT_EQ(
|
|
OEMCrypto_SUCCESS,
|
|
OEMCrypto_GenerateCertificateKeyPair(
|
|
session()->session_id(), public_key.data(), &public_key_size,
|
|
public_key_signature.data(), &public_key_signature_size,
|
|
wrapped_private_key.data(), &wrapped_private_key_size, &key_type));
|
|
wrapped_private_key.resize(wrapped_private_key_size);
|
|
public_key.resize(public_key_size);
|
|
|
|
wrapped_drm_key_ = wrapped_private_key;
|
|
drm_public_key_ = public_key;
|
|
drm_key_type_ = key_type;
|
|
}
|
|
|
|
void Provisioning40CastRoundTrip::LoadDRMPrivateKey() {
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadDRMPrivateKey(session()->session_id(), drm_key_type_,
|
|
wrapped_drm_key_.data(),
|
|
wrapped_drm_key_.size()));
|
|
}
|
|
|
|
void Provisioning40CastRoundTrip::FillAndVerifyCoreRequest(
|
|
const std::string& core_message_string) {
|
|
EXPECT_TRUE(
|
|
oemcrypto_core_message::deserialize::CoreProvisioning40RequestFromMessage(
|
|
core_message_string, &core_request_));
|
|
EXPECT_EQ(global_features.api_version, core_request_.api_major_version);
|
|
EXPECT_EQ(session()->nonce(), core_request_.nonce);
|
|
EXPECT_EQ(session()->session_id(), core_request_.session_id);
|
|
}
|
|
|
|
void Provisioning40CastRoundTrip::VerifyRequestSignature(
|
|
const vector<uint8_t>& data, const vector<uint8_t>& generated_signature,
|
|
size_t /* core_message_length */) {
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
session()->VerifySignature(data, generated_signature.data(),
|
|
generated_signature.size(), kSign_RSASSA_PSS));
|
|
}
|
|
|
|
// Creates a prov2 response
|
|
void Provisioning40CastRoundTrip::CreateDefaultResponse() {
|
|
uint32_t algorithm_n = htonl(allowed_schemes_);
|
|
memcpy(response_data_.rsa_key, "SIGN", 4);
|
|
memcpy(response_data_.rsa_key + 4, &algorithm_n, 4);
|
|
memcpy(response_data_.rsa_key + 8, encoded_rsa_key_.data(),
|
|
encoded_rsa_key_.size());
|
|
response_data_.rsa_key_length = 8 + encoded_rsa_key_.size();
|
|
response_data_.nonce = session_->nonce();
|
|
response_data_.enc_message_key_length = 0;
|
|
core_response_.key_type = OEMCrypto_RSA_Private_Key;
|
|
core_response_.enc_private_key =
|
|
FindSubstring(response_data_.rsa_key, response_data_.rsa_key_length);
|
|
core_response_.enc_private_key_iv = FindSubstring(
|
|
response_data_.rsa_key_iv, sizeof(response_data_.rsa_key_iv));
|
|
core_response_.encrypted_message_key = FindSubstring(
|
|
response_data_.enc_message_key, response_data_.enc_message_key_length);
|
|
}
|
|
|
|
void Provisioning40CastRoundTrip::EncryptAndSignResponse() {
|
|
session()->key_deriver().PadAndEncryptProvisioningMessage(&response_data_,
|
|
&encrypted_response_data_);
|
|
core_response_.enc_private_key.length =
|
|
encrypted_response_data_.rsa_key_length;
|
|
SignResponse();
|
|
}
|
|
|
|
void Provisioning40CastRoundTrip::SignResponse() {
|
|
CoreMessageFeatures features =
|
|
CoreMessageFeatures::DefaultFeatures(ODK_MAJOR_VERSION);
|
|
|
|
// Create prov 2 request struct from prov 4 request
|
|
oemcrypto_core_message::ODK_ProvisioningRequest core_request_prov2;
|
|
core_request_prov2.api_minor_version = core_request_.api_minor_version;
|
|
core_request_prov2.api_major_version = core_request_.api_major_version;
|
|
core_request_prov2.nonce = core_request_.nonce;
|
|
core_request_prov2.session_id = core_request_.session_id;
|
|
memcpy(&core_request_prov2.counter_info, &core_request_.counter_info,
|
|
sizeof(core_request_.counter_info));
|
|
|
|
ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreProvisioningResponse(
|
|
features, core_response_, core_request_prov2, &serialized_core_message_));
|
|
// Resizing for huge core message length unit tests.
|
|
serialized_core_message_.resize(
|
|
std::max(required_core_message_size_, serialized_core_message_.size()));
|
|
// Make the message buffer a just big enough, or the
|
|
// required size, whichever is larger.
|
|
const size_t message_size =
|
|
std::max(required_message_size_, serialized_core_message_.size() +
|
|
sizeof(encrypted_response_data_));
|
|
// Stripe the encrypted message.
|
|
encrypted_response_.resize(message_size);
|
|
for (size_t i = 0; i < encrypted_response_.size(); i++) {
|
|
encrypted_response_[i] = i & 0xFF;
|
|
}
|
|
ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size());
|
|
memcpy(encrypted_response_.data(), serialized_core_message_.data(),
|
|
serialized_core_message_.size());
|
|
ASSERT_GE(encrypted_response_.size(),
|
|
serialized_core_message_.size() + sizeof(encrypted_response_data_));
|
|
memcpy(encrypted_response_.data() + serialized_core_message_.size(),
|
|
reinterpret_cast<const uint8_t*>(&encrypted_response_data_),
|
|
sizeof(encrypted_response_data_));
|
|
session()->key_deriver().ServerSignBuffer(encrypted_response_.data(),
|
|
encrypted_response_.size(),
|
|
&response_signature_);
|
|
SetEncryptAndSignResponseLengths();
|
|
}
|
|
|
|
OEMCryptoResult Provisioning40CastRoundTrip::LoadResponse(Session* session) {
|
|
EXPECT_NE(session, nullptr);
|
|
// Write corpus for oemcrypto_load_provisioning_fuzz. Fuzz script expects
|
|
// unencrypted response from provisioning server as input corpus data.
|
|
// Data will be encrypted and signed again explicitly by fuzzer script after
|
|
// mutations.
|
|
if (ShouldGenerateCorpus()) {
|
|
const std::string file_name =
|
|
GetFileName("oemcrypto_load_provisioning_fuzz_seed_corpus");
|
|
// Corpus for license response fuzzer should be in the format:
|
|
// unencrypted (core_response + response_data).
|
|
AppendToFile(file_name, reinterpret_cast<const char*>(&core_response_),
|
|
sizeof(ODK_ParsedProvisioning));
|
|
AppendToFile(file_name, reinterpret_cast<const char*>(&response_data_),
|
|
sizeof(response_data_));
|
|
}
|
|
size_t wrapped_key_length = 0;
|
|
OEMCryptoResult sts = LoadResponseNoRetry(session, &wrapped_key_length);
|
|
if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts;
|
|
wrapped_rsa_key_.assign(wrapped_key_length, 0);
|
|
sts = LoadResponseNoRetry(session, &wrapped_key_length);
|
|
if (sts == OEMCrypto_SUCCESS) {
|
|
wrapped_rsa_key_.resize(wrapped_key_length);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
OEMCryptoResult Provisioning40CastRoundTrip::LoadResponseNoRetry(
|
|
Session* session, size_t* wrapped_key_length) {
|
|
EXPECT_NE(session, nullptr);
|
|
VerifyEncryptAndSignResponseLengths();
|
|
return OEMCrypto_LoadProvisioning(
|
|
session->session_id(), encrypted_response_.data(),
|
|
encrypted_response_.size(), serialized_core_message_.size(),
|
|
response_signature_.data(), response_signature_.size(),
|
|
wrapped_rsa_key_.data(), wrapped_key_length);
|
|
}
|
|
|
|
void LicenseRoundTrip::VerifyRequestSignature(
|
|
const vector<uint8_t>& data, const vector<uint8_t>& generated_signature,
|
|
size_t core_message_length) {
|
|
// If the api version was not set by the test, then we record the api version
|
|
// from the request. Also, if the api was set to be higher than oemcrypto
|
|
// supports, then we lower it. This version will be used in the response.
|
|
if (api_version_ == 0) api_version_ = core_request_.api_major_version;
|
|
if (api_version_ > global_features.api_version)
|
|
api_version_ = global_features.api_version;
|
|
|
|
if (global_features.api_version < 17) {
|
|
const std::vector<uint8_t> subdata(data.begin() + core_message_length,
|
|
data.end());
|
|
session()->VerifyRsaSignature(subdata, generated_signature.data(),
|
|
generated_signature.size(), kSign_RSASSA_PSS);
|
|
SHA256(data.data(), core_message_length, request_hash_);
|
|
} else {
|
|
session()->VerifySignature(data, generated_signature.data(),
|
|
generated_signature.size(), kSign_RSASSA_PSS);
|
|
SHA256(data.data(), core_message_length, request_hash_);
|
|
}
|
|
}
|
|
|
|
void LicenseRoundTrip::FillAndVerifyCoreRequest(
|
|
const std::string& core_message_string) {
|
|
EXPECT_TRUE(
|
|
oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage(
|
|
core_message_string, &core_request_));
|
|
EXPECT_EQ(global_features.api_version, core_request_.api_major_version);
|
|
if (global_features.api_version == 16) {
|
|
// We support either 16.3-16.4 for OEMCrypto 16 public release, and v16.5
|
|
// for L3 release only.
|
|
EXPECT_LE(3, core_request_.api_minor_version);
|
|
EXPECT_GE(5, core_request_.api_minor_version);
|
|
} else if (global_features.api_version == ODK_MAJOR_VERSION) {
|
|
// We do not expect older tests to work with a newer OEMCrypto.
|
|
EXPECT_GE(ODK_MINOR_VERSION, core_request_.api_minor_version);
|
|
}
|
|
if (expect_request_has_correct_nonce_) {
|
|
EXPECT_EQ(session()->nonce(), core_request_.nonce);
|
|
}
|
|
EXPECT_EQ(session()->session_id(), core_request_.session_id);
|
|
}
|
|
|
|
void LicenseRoundTrip::CreateDefaultResponse() {
|
|
EXPECT_EQ(1, GetRandBytes(response_data_.mac_key_iv,
|
|
sizeof(response_data_.mac_key_iv)));
|
|
memset(response_data_.padding, 0, sizeof(response_data_.padding));
|
|
EXPECT_EQ(1, GetRandBytes(response_data_.mac_keys,
|
|
sizeof(response_data_.mac_keys)));
|
|
// For backwards compatibility, we use the largest limit in timer_limits for
|
|
// each key's duration.
|
|
uint32_t key_duration = static_cast<uint32_t>(
|
|
std::max({core_response_.timer_limits.rental_duration_seconds,
|
|
core_response_.timer_limits.total_playback_duration_seconds,
|
|
core_response_.timer_limits.initial_renewal_duration_seconds}));
|
|
// The key data for an entitlement license is an AES-256 key, otherwise the
|
|
// default is an AES_128 key.
|
|
uint32_t default_key_size =
|
|
(license_type_ == OEMCrypto_EntitlementLicense) ? KEY_SIZE * 2 : KEY_SIZE;
|
|
for (unsigned int i = 0; i < num_keys_; i++) {
|
|
memset(response_data_.keys[i].key_id, 0, kTestKeyIdMaxLength);
|
|
response_data_.keys[i].key_id_length = kDefaultKeyIdLength;
|
|
memset(response_data_.keys[i].key_id, i,
|
|
response_data_.keys[i].key_id_length);
|
|
EXPECT_EQ(1, GetRandBytes(response_data_.keys[i].key_data,
|
|
sizeof(response_data_.keys[i].key_data)));
|
|
response_data_.keys[i].key_data_length = default_key_size;
|
|
EXPECT_EQ(1, GetRandBytes(response_data_.keys[i].key_iv,
|
|
sizeof(response_data_.keys[i].key_iv)));
|
|
EXPECT_EQ(1, GetRandBytes(response_data_.keys[i].control_iv,
|
|
sizeof(response_data_.keys[i].control_iv)));
|
|
std::string kcVersion = "kc" + std::to_string(api_version_);
|
|
memcpy(response_data_.keys[i].control.verification, kcVersion.c_str(), 4);
|
|
response_data_.keys[i].control.duration = htonl(key_duration);
|
|
response_data_.keys[i].control.nonce = htonl(session_->nonce());
|
|
response_data_.keys[i].control.control_bits = htonl(control_);
|
|
response_data_.keys[i].cipher_mode = OEMCrypto_CipherMode_CENC;
|
|
}
|
|
// Fill in the default core_response_ fields, except the substrings, which are
|
|
// filled in the next function.
|
|
core_response_.nonce_required =
|
|
(wvoec::kControlNonceEnabled | wvoec::kControlNonceOrEntry |
|
|
wvoec::kControlNonceRequired) &
|
|
control_;
|
|
core_response_.license_type = license_type_;
|
|
FillCoreResponseSubstrings();
|
|
}
|
|
|
|
void LicenseRoundTrip::ConvertDataToValidBools(ODK_Packing_ParsedLicense* t) {
|
|
t->nonce_required = ConvertByteToValidBoolean(&t->nonce_required);
|
|
t->timer_limits.soft_enforce_playback_duration = ConvertByteToValidBoolean(
|
|
&t->timer_limits.soft_enforce_playback_duration);
|
|
t->timer_limits.soft_enforce_rental_duration =
|
|
ConvertByteToValidBoolean(&t->timer_limits.soft_enforce_rental_duration);
|
|
}
|
|
|
|
void LicenseRoundTrip::InjectFuzzedTimerLimits(
|
|
OEMCrypto_Renewal_Response_Fuzz& fuzzed_data) {
|
|
// Interpreting fuzz data as timer limits.
|
|
// Copy timer limits from data.
|
|
memcpy(&core_response_.timer_limits, &fuzzed_data.timer_limits,
|
|
sizeof(fuzzed_data.timer_limits));
|
|
ConvertDataToValidBools(&core_response_);
|
|
}
|
|
|
|
void LicenseRoundTrip::InjectFuzzedResponseData(const uint8_t* data,
|
|
size_t size) {
|
|
// Interpreting fuzz data as unencrypted core_response + response_data +
|
|
// key_array
|
|
FuzzedData fuzzed_data(data, size);
|
|
|
|
// Copy core_response from data.
|
|
fuzzed_data.Fill(&core_response_, sizeof(core_response_));
|
|
|
|
// Copy response_data from data.
|
|
fuzzed_data.Fill(&response_data_, sizeof(response_data_));
|
|
|
|
// If key_array_length is more than kMaxNumKeys, we set it to kMaxNumKeys to
|
|
// prevent it from going out of bounds. For corpus, this value is already hard
|
|
// coded to 4.
|
|
if (core_response_.key_array_length > kMaxNumKeys) {
|
|
core_response_.key_array_length = kMaxNumKeys;
|
|
}
|
|
|
|
// For corpus data, this value gets set to 4, but we need to test other
|
|
// scenarios too, hence reading key_array_length value.
|
|
set_num_keys(core_response_.key_array_length);
|
|
|
|
// Copy key_array from data.
|
|
key_array_.resize(num_keys_);
|
|
core_response_.key_array = key_array_.data();
|
|
fuzzed_data.Fill(core_response_.key_array,
|
|
num_keys_ * sizeof(*core_response_.key_array));
|
|
|
|
ConvertDataToValidBools(&core_response_);
|
|
|
|
// TODO(b/157520981): Once assertion bug is fixed, for loop can be removed.
|
|
// Workaround for the above bug: key_data.length and key_id.length are being
|
|
// used in AES decryption process and are expected to be a multiple of 16. An
|
|
// assertion in AES decryption fails if this condition is not met which will
|
|
// crash fuzzer.
|
|
for (uint32_t i = 0; i < num_keys_; ++i) {
|
|
size_t key_data_length = core_response_.key_array[i].key_data.length;
|
|
size_t key_id_length = core_response_.key_array[i].key_id.length;
|
|
if (key_data_length % 16 != 0) {
|
|
core_response_.key_array[i].key_data.length =
|
|
key_data_length - (key_data_length % 16);
|
|
}
|
|
if (key_id_length % 16 != 0) {
|
|
core_response_.key_array[i].key_id.length =
|
|
key_id_length - (key_id_length % 16);
|
|
}
|
|
}
|
|
|
|
// Set nonce to match one in request to pass nonce validations.
|
|
for (uint32_t i = 0; i < num_keys_; ++i) {
|
|
response_data_.keys[i].control.nonce = htonl(session()->nonce());
|
|
}
|
|
}
|
|
|
|
void LicenseRoundTrip::CreateResponseWithGenericCryptoKeys() {
|
|
CreateDefaultResponse();
|
|
response_data_.keys[0].control.control_bits |=
|
|
htonl(wvoec::kControlAllowEncrypt);
|
|
response_data_.keys[1].control.control_bits |=
|
|
htonl(wvoec::kControlAllowDecrypt);
|
|
response_data_.keys[2].control.control_bits |=
|
|
htonl(wvoec::kControlAllowSign);
|
|
response_data_.keys[3].control.control_bits |=
|
|
htonl(wvoec::kControlAllowVerify);
|
|
response_data_.keys[2].key_data_length = wvoec::MAC_KEY_SIZE;
|
|
response_data_.keys[3].key_data_length = wvoec::MAC_KEY_SIZE;
|
|
FillCoreResponseSubstrings();
|
|
}
|
|
|
|
void LicenseRoundTrip::FillCoreResponseSubstrings() {
|
|
if (update_mac_keys_) {
|
|
core_response_.enc_mac_keys_iv = FindSubstring(
|
|
response_data_.mac_key_iv, sizeof(response_data_.mac_key_iv));
|
|
core_response_.enc_mac_keys =
|
|
FindSubstring(response_data_.mac_keys, sizeof(response_data_.mac_keys));
|
|
}
|
|
if (pst_.size() > 0) {
|
|
ASSERT_LE(pst_.size(), sizeof(response_data_.pst));
|
|
memcpy(response_data_.pst, pst_.c_str(),
|
|
min(sizeof(response_data_.pst), pst_.length()));
|
|
core_response_.pst = FindSubstring(response_data_.pst, pst_.size());
|
|
}
|
|
if (minimum_srm_version_ > 0) {
|
|
const std::string verification = "HDCPDATA";
|
|
ASSERT_EQ(verification.size(),
|
|
sizeof(response_data_.srm_restriction_data.verification));
|
|
memcpy(response_data_.srm_restriction_data.verification,
|
|
verification.c_str(), verification.size());
|
|
response_data_.srm_restriction_data.minimum_srm_version =
|
|
htonl(minimum_srm_version_);
|
|
core_response_.srm_restriction_data =
|
|
FindSubstring(&response_data_.srm_restriction_data,
|
|
sizeof(response_data_.srm_restriction_data));
|
|
}
|
|
core_response_.key_array_length = num_keys_;
|
|
key_array_.clear();
|
|
for (unsigned int i = 0; i < num_keys_; i++) {
|
|
OEMCrypto_KeyObject obj;
|
|
obj.key_id = FindSubstring(response_data_.keys[i].key_id,
|
|
response_data_.keys[i].key_id_length);
|
|
obj.key_data_iv = FindSubstring(response_data_.keys[i].key_iv,
|
|
sizeof(response_data_.keys[i].key_iv));
|
|
obj.key_data = FindSubstring(response_data_.keys[i].key_data,
|
|
response_data_.keys[i].key_data_length);
|
|
if (core_request().api_major_version < kClearControlBlockAPIMajor ||
|
|
(core_request().api_major_version == kClearControlBlockAPIMajor &&
|
|
core_request().api_minor_version < kClearControlBlockAPIMinor)) {
|
|
obj.key_control_iv =
|
|
FindSubstring(response_data_.keys[i].control_iv,
|
|
sizeof(response_data_.keys[i].control_iv));
|
|
} else {
|
|
obj.key_control_iv = FindSubstring(nullptr, 0);
|
|
}
|
|
obj.key_control = FindSubstring(&response_data_.keys[i].control,
|
|
sizeof(response_data_.keys[i].control));
|
|
key_array_.push_back(obj);
|
|
}
|
|
core_response_.key_array = key_array_.data();
|
|
core_response_.key_array_length = static_cast<uint32_t>(key_array_.size());
|
|
}
|
|
|
|
void LicenseRoundTrip::EncryptResponse(bool force_clear_kcb) {
|
|
ASSERT_NO_FATAL_FAILURE(session_->GenerateDerivedKeysFromSessionKey());
|
|
encrypted_response_data_ = response_data_;
|
|
uint8_t iv_buffer[KEY_IV_SIZE];
|
|
memcpy(iv_buffer, &response_data_.mac_key_iv[0], KEY_IV_SIZE);
|
|
session_->key_deriver().CBCEncrypt(
|
|
&response_data_.mac_keys[0], &encrypted_response_data_.mac_keys[0],
|
|
2 * MAC_KEY_SIZE, response_data_.mac_key_iv);
|
|
|
|
for (unsigned int i = 0; i < num_keys_; i++) {
|
|
// OEMCrypto Fuzzing skip encryption: key_data_length can be any number when
|
|
// called from fuzzer. We want to skip encryption if that happens and let
|
|
// LoadLicense be called with unencrypted data for that key. OEMCrypto
|
|
// Fuzzing skip encryption: key_data_length being a random value will
|
|
// encrypt data which is not expected to, there by leading to inefficient
|
|
// fuzzing.
|
|
if (!force_clear_kcb &&
|
|
response_data_.keys[i].key_data_length <=
|
|
sizeof(response_data_.keys[i].key_data) &&
|
|
response_data_.keys[i].key_data_length % 16 == 0) {
|
|
memcpy(iv_buffer, &response_data_.keys[i].control_iv[0], KEY_IV_SIZE);
|
|
if (core_request_.api_major_version < kClearControlBlockAPIMajor ||
|
|
(core_request_.api_major_version == kClearControlBlockAPIMajor &&
|
|
core_request_.api_minor_version < kClearControlBlockAPIMinor)) {
|
|
AES_KEY aes_key;
|
|
AES_set_encrypt_key(&response_data_.keys[i].key_data[0], 128, &aes_key);
|
|
AES_cbc_encrypt(
|
|
reinterpret_cast<const uint8_t*>(&response_data_.keys[i].control),
|
|
reinterpret_cast<uint8_t*>(
|
|
&encrypted_response_data_.keys[i].control),
|
|
KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT);
|
|
} else {
|
|
encrypted_response_data_.keys[i].control =
|
|
response_data_.keys[i].control;
|
|
}
|
|
session_->key_deriver().CBCEncrypt(
|
|
&response_data_.keys[i].key_data[0],
|
|
&encrypted_response_data_.keys[i].key_data[0],
|
|
response_data_.keys[i].key_data_length,
|
|
response_data_.keys[i].key_iv);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LicenseRoundTrip::CreateCoreLicenseResponseWithFeatures(
|
|
const CoreMessageFeatures& features) {
|
|
if (core_request_.api_major_version == 0) {
|
|
// If we don't have a valid request, then we should at least set the
|
|
// version number of the request so that CreateCoreLicenseResponse can
|
|
// compute the version number of the response.
|
|
core_request_.api_major_version = ODK_MAJOR_VERSION;
|
|
core_request_.api_minor_version = ODK_MINOR_VERSION;
|
|
}
|
|
std::string request_hash_string(reinterpret_cast<const char*>(request_hash_),
|
|
sizeof(request_hash_));
|
|
ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreLicenseResponse(
|
|
features, core_response_, core_request_, request_hash_string,
|
|
&serialized_core_message_));
|
|
// Resize serialize core message to be just big enough or required core
|
|
// message size, whichever is larger.
|
|
serialized_core_message_.resize(
|
|
std::max(required_core_message_size_, serialized_core_message_.size()));
|
|
}
|
|
|
|
void LicenseRoundTrip::SignEncryptedResponse() {
|
|
// Make the message buffer a just big enough, or the
|
|
// required size, whichever is larger.
|
|
const size_t message_size =
|
|
std::max(required_message_size_, serialized_core_message_.size() +
|
|
sizeof(encrypted_response_data_));
|
|
// Stripe the encrypted message.
|
|
encrypted_response_.resize(message_size);
|
|
for (size_t i = 0; i < encrypted_response_.size(); i++) {
|
|
encrypted_response_[i] = i % 0x100;
|
|
}
|
|
ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size());
|
|
memcpy(encrypted_response_.data(), serialized_core_message_.data(),
|
|
serialized_core_message_.size());
|
|
ASSERT_GE(encrypted_response_.size(),
|
|
serialized_core_message_.size() + sizeof(encrypted_response_data_));
|
|
memcpy(encrypted_response_.data() + serialized_core_message_.size(),
|
|
reinterpret_cast<const uint8_t*>(&encrypted_response_data_),
|
|
sizeof(encrypted_response_data_));
|
|
session()->key_deriver().ServerSignBuffer(encrypted_response_.data(),
|
|
encrypted_response_.size(),
|
|
&response_signature_);
|
|
SetEncryptAndSignResponseLengths();
|
|
}
|
|
|
|
void LicenseRoundTrip::EncryptAndSignResponse() {
|
|
EncryptResponse();
|
|
// We might try to test a future api_version_, but we can only make a core
|
|
// message with at most the current ODK version. This is only done to verify
|
|
// that OEMCrypto does not attempt to load a future version.
|
|
CoreMessageFeatures features = CoreMessageFeatures::DefaultFeatures(
|
|
std::min(api_version_, static_cast<uint32_t>(ODK_MAJOR_VERSION)));
|
|
CreateCoreLicenseResponseWithFeatures(features);
|
|
SignEncryptedResponse();
|
|
}
|
|
|
|
void LicenseRoundTrip::EncryptAndSignResponseWithCoreMessageFeatures(
|
|
const CoreMessageFeatures& features, bool force_clear_kcb) {
|
|
EncryptResponse(force_clear_kcb);
|
|
CreateCoreLicenseResponseWithFeatures(features);
|
|
SignEncryptedResponse();
|
|
}
|
|
|
|
OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) {
|
|
return LoadResponse(session, true);
|
|
}
|
|
|
|
OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session,
|
|
bool verify_keys) {
|
|
EXPECT_NE(session, nullptr);
|
|
// Write corpus for oemcrypto_load_license_fuzz. Fuzz script expects
|
|
// unecnrypted response from license server as input corpus data.
|
|
// Data will be encrypted and signed again explicitly by fuzzer script
|
|
// after mutations.
|
|
if (ShouldGenerateCorpus()) {
|
|
const std::string file_name =
|
|
GetFileName("oemcrypto_load_license_fuzz_seed_corpus");
|
|
// Corpus for license response fuzzer should be in the format:
|
|
// core_response + response_data + key_array.
|
|
AppendToFile(file_name, reinterpret_cast<const char*>(&core_response_),
|
|
sizeof(core_response_));
|
|
AppendToFile(file_name, reinterpret_cast<const char*>(&response_data_),
|
|
sizeof(response_data_));
|
|
AppendToFile(
|
|
file_name, reinterpret_cast<const char*>(core_response_.key_array),
|
|
core_response_.key_array_length * sizeof(*core_response_.key_array));
|
|
}
|
|
|
|
// Some tests adjust the offset to be beyond the length of the message. Here,
|
|
// we create a duplicate of the main message buffer so that these offsets do
|
|
// not point to garbage data. The goal is to make sure OEMCrypto is verifying
|
|
// that the offset points outside of the message -- we don't want OEMCrypto to
|
|
// look at what offset points to and return an error if the data is
|
|
// garbage. Since the memory after the message buffer is an exact copy of the
|
|
// message, we can increment the offset by the message size and get valid
|
|
// data.
|
|
VerifyEncryptAndSignResponseLengths();
|
|
std::vector<uint8_t> double_message = encrypted_response_;
|
|
double_message.insert(
|
|
double_message.end(),
|
|
reinterpret_cast<const uint8_t*>(&encrypted_response_data_),
|
|
reinterpret_cast<const uint8_t*>(&encrypted_response_data_) +
|
|
sizeof(encrypted_response_data_));
|
|
OEMCryptoResult result = OEMCrypto_LoadLicense(
|
|
session->session_id(), double_message.data(), encrypted_response_.size(),
|
|
serialized_core_message_.size(), response_signature_.data(),
|
|
response_signature_.size());
|
|
if (verify_keys && result == OEMCrypto_SUCCESS) {
|
|
// Give the session object a copy of the license truth data so that it can
|
|
// call GetKeyHandle, use key control information, and so that it has key
|
|
// data to verify decrypt operations.
|
|
session->set_license(response_data_);
|
|
// Also, if the license has new mac keys, then install them now.
|
|
if (core_response_.enc_mac_keys.length > 0) {
|
|
session->set_mac_keys(response_data_.mac_keys);
|
|
}
|
|
|
|
// Note: we verify content licenses here. For entitlement license, we verify
|
|
// the key control blocks after loading entitled content keys.
|
|
if (license_type_ == OEMCrypto_ContentLicense) VerifyTestKeys(session);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult LicenseRoundTrip::ReloadResponse(Session* session) {
|
|
session->GenerateDerivedKeysFromSessionKey();
|
|
return LoadResponse(session);
|
|
}
|
|
|
|
// This function verifies that the key control block reported by OEMCrypto agree
|
|
// with the truth key control block. Failures in this function probably
|
|
// indicate the OEMCrypto_LoadLicense did not correctly process the key
|
|
// control block.
|
|
void LicenseRoundTrip::VerifyTestKeys(Session* session) {
|
|
for (unsigned int i = 0; i < num_keys_; i++) {
|
|
KeyControlBlock block;
|
|
size_t size = sizeof(block);
|
|
OEMCryptoResult sts = OEMCrypto_QueryKeyControl(
|
|
session->session_id(), response_data_.keys[i].key_id,
|
|
response_data_.keys[i].key_id_length,
|
|
reinterpret_cast<uint8_t*>(&block), &size);
|
|
if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
ASSERT_EQ(sizeof(block), size);
|
|
// Note: we do not assume that duration is stored with each key after v16.
|
|
// control bits stored in network byte order. For printing
|
|
// we change to host byte order.
|
|
ASSERT_EQ(htonl_fnc(response_data_.keys[i].control.control_bits),
|
|
htonl_fnc(block.control_bits))
|
|
<< "For key " << i;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LicenseRoundTrip::SetKeyId(size_t index, const string& key_id) {
|
|
ASSERT_LT(index, num_keys_);
|
|
MessageKeyData& key = response_data_.keys[index];
|
|
key.key_id_length = key_id.length();
|
|
ASSERT_LE(key.key_id_length, kTestKeyIdMaxLength);
|
|
memcpy(key.key_id, key_id.data(), key.key_id_length);
|
|
}
|
|
|
|
void EntitledMessage::FillKeyArray() {
|
|
for (size_t i = 0; i < license_messages_->num_keys(); ++i) {
|
|
MakeOneKey(i);
|
|
}
|
|
}
|
|
|
|
void EntitledMessage::MakeOneKey(size_t entitlement_key_index) {
|
|
ASSERT_LT(entitlement_key_index, kMaxNumKeys);
|
|
ASSERT_LT(num_keys_, kMaxNumKeys);
|
|
EntitledContentKeyData* key_data = &entitled_key_data_[num_keys_];
|
|
MessageKeyData* entitlement_key =
|
|
&license_messages_->response_data().keys[entitlement_key_index];
|
|
OEMCrypto_EntitledContentKeyObject* offsets = &entitled_key_array_[num_keys_];
|
|
num_keys_++;
|
|
|
|
key_data->key_index = entitlement_key_index;
|
|
ASSERT_LE(entitlement_key->key_id_length, kTestKeyIdMaxLength);
|
|
memcpy(key_data->entitlement_key_id, entitlement_key->key_id,
|
|
entitlement_key->key_id_length);
|
|
key_data->entitlement_key_id_length = entitlement_key->key_id_length;
|
|
offsets->entitlement_key_id = FindSubstring(key_data->entitlement_key_id,
|
|
entitlement_key->key_id_length);
|
|
|
|
key_data->content_key_id_length = kDefaultKeyIdLength;
|
|
// Fill the key ID as CnCnCnCn... so it's easy to see in debug logs.
|
|
memset(key_data->content_key_id, 0xC0 + num_keys_,
|
|
key_data->content_key_id_length);
|
|
offsets->content_key_id =
|
|
FindSubstring(key_data->content_key_id, key_data->content_key_id_length);
|
|
|
|
EXPECT_EQ(1, GetRandBytes(key_data->content_key_data,
|
|
sizeof(key_data->content_key_data)));
|
|
// Note: we give the encrypted content key to OEMCrypto, not the clear
|
|
// content key.
|
|
offsets->content_key_data =
|
|
FindSubstring(key_data->encrypted_content_key_data,
|
|
sizeof(key_data->encrypted_content_key_data));
|
|
|
|
EXPECT_EQ(1, GetRandBytes(key_data->content_key_data_iv,
|
|
sizeof(key_data->content_key_data_iv)));
|
|
offsets->content_key_data_iv = FindSubstring(
|
|
key_data->content_key_data_iv, sizeof(key_data->content_key_data_iv));
|
|
}
|
|
|
|
OEMCrypto_EntitledContentKeyObject* EntitledMessage::entitled_key_array() {
|
|
return entitled_key_array_;
|
|
}
|
|
|
|
EntitledContentKeyData* EntitledMessage::entitled_key_data() {
|
|
return entitled_key_data_;
|
|
}
|
|
|
|
size_t EntitledMessage::entitled_key_data_size() {
|
|
return sizeof(entitled_key_data_);
|
|
}
|
|
|
|
void EntitledMessage::SetEntitlementKeyId(unsigned int index,
|
|
const std::string& key_id) {
|
|
ASSERT_LT(index, num_keys_);
|
|
ASSERT_LE(key_id.size(), kTestKeyIdMaxLength);
|
|
entitled_key_data_[index].entitlement_key_id_length = key_id.size();
|
|
memcpy(entitled_key_data_[index].entitlement_key_id,
|
|
reinterpret_cast<const uint8_t*>(key_id.c_str()), key_id.length());
|
|
entitled_key_array_[index].entitlement_key_id = FindSubstring(
|
|
entitled_key_data_[index].entitlement_key_id, key_id.length());
|
|
}
|
|
|
|
void EntitledMessage::SetContentKeyId(unsigned int index,
|
|
const std::string& key_id) {
|
|
ASSERT_LT(index, num_keys_);
|
|
ASSERT_LE(key_id.size(), kTestKeyIdMaxLength);
|
|
entitled_key_data_[index].content_key_id_length = key_id.size();
|
|
memcpy(entitled_key_data_[index].content_key_id,
|
|
reinterpret_cast<const uint8_t*>(key_id.c_str()), key_id.length());
|
|
entitled_key_array_[index].content_key_id =
|
|
FindSubstring(entitled_key_data_[index].content_key_id, key_id.length());
|
|
}
|
|
|
|
OEMCrypto_Substring EntitledMessage::FindSubstring(const void* ptr,
|
|
size_t size) {
|
|
OEMCrypto_Substring substring{0, 0};
|
|
if (ptr != nullptr) {
|
|
substring.offset = reinterpret_cast<const uint8_t*>(ptr) -
|
|
reinterpret_cast<const uint8_t*>(entitled_key_data_);
|
|
substring.length = size;
|
|
}
|
|
return substring;
|
|
}
|
|
|
|
void EntitledMessage::LoadKeys(bool expected_success) {
|
|
EncryptContentKey();
|
|
if (expected_success) {
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadEntitledContentKeys(
|
|
entitled_key_session_,
|
|
reinterpret_cast<const uint8_t*>(entitled_key_data_),
|
|
sizeof(entitled_key_data_), num_keys_, entitled_key_array_));
|
|
} else {
|
|
ASSERT_NE(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadEntitledContentKeys(
|
|
entitled_key_session_,
|
|
reinterpret_cast<const uint8_t*>(entitled_key_data_),
|
|
sizeof(entitled_key_data_), num_keys_, entitled_key_array_));
|
|
return;
|
|
}
|
|
VerifyKCBs();
|
|
VerifyDecrypt();
|
|
}
|
|
|
|
OEMCryptoResult EntitledMessage::LoadKeys(const vector<uint8_t>& message) {
|
|
return OEMCrypto_LoadEntitledContentKeys(entitled_key_session_,
|
|
message.data(), message.size(),
|
|
num_keys_, entitled_key_array_);
|
|
}
|
|
|
|
OEMCryptoResult EntitledMessage::LoadKeys() {
|
|
return OEMCrypto_LoadEntitledContentKeys(
|
|
entitled_key_session_,
|
|
reinterpret_cast<const uint8_t*>(entitled_key_data_),
|
|
sizeof(entitled_key_data_), num_keys_, entitled_key_array_);
|
|
}
|
|
|
|
void EntitledMessage::EncryptContentKey() {
|
|
for (size_t i = 0; i < num_keys_; ++i) {
|
|
EntitledContentKeyData* key_data = &entitled_key_data_[i];
|
|
const size_t entitlement_key_index = key_data->key_index;
|
|
MessageKeyData* entitlement_key =
|
|
&license_messages_->response_data().keys[entitlement_key_index];
|
|
|
|
// Load the entitlement key from |key_array_|.
|
|
AES_KEY aes_key;
|
|
AES_set_encrypt_key(entitlement_key->key_data, 256, &aes_key);
|
|
|
|
// Encrypt the content key with the entitlement key.
|
|
uint8_t iv[16];
|
|
memcpy(&iv[0], key_data->content_key_data_iv, KEY_IV_SIZE);
|
|
AES_cbc_encrypt(key_data->content_key_data,
|
|
key_data->encrypted_content_key_data, KEY_SIZE, &aes_key,
|
|
iv, AES_ENCRYPT);
|
|
}
|
|
if (ShouldGenerateCorpus()) {
|
|
const std::string file_name =
|
|
GetFileName("oemcrypto_load_entitled_content_keys_fuzz_seed_corpus");
|
|
// Corpus for load entitled keys fuzzer should be in the format:
|
|
// message buffer to be verified | entitled content key object array.
|
|
AppendToFile(file_name, reinterpret_cast<const char*>(entitled_key_data_),
|
|
num_keys_ * sizeof(EntitledContentKeyData));
|
|
AppendSeparator(file_name);
|
|
AppendToFile(file_name, reinterpret_cast<const char*>(entitled_key_array_),
|
|
num_keys_ * sizeof(OEMCrypto_EntitledContentKeyObject));
|
|
}
|
|
}
|
|
|
|
void EntitledMessage::LoadCasKeys(bool load_even, bool load_odd,
|
|
OEMCryptoResult expected_sts) {
|
|
for (size_t i = 0; i < num_keys_; ++i) {
|
|
EntitledContentKeyData* key_data = &entitled_key_data_[i];
|
|
const size_t entitlement_key_index = key_data->key_index;
|
|
MessageKeyData* entitlement_key =
|
|
&license_messages_->response_data().keys[entitlement_key_index];
|
|
|
|
// Load the entitlement key from |key_array_|.
|
|
AES_KEY aes_key;
|
|
AES_set_encrypt_key(entitlement_key->key_data, 256, &aes_key);
|
|
|
|
// Encrypt the content key with the entitlement key.
|
|
uint8_t iv[16];
|
|
memcpy(&iv[0], key_data->content_key_data_iv, KEY_IV_SIZE);
|
|
AES_cbc_encrypt(key_data->content_key_data,
|
|
key_data->encrypted_content_key_data, KEY_SIZE, &aes_key,
|
|
iv, AES_ENCRYPT);
|
|
}
|
|
|
|
// Convert the OEMCrypto_EntitledContentKeyObject to
|
|
// OEMCrypto_EntitledCasKeyObject. Only the first two key object is used.
|
|
OEMCrypto_EntitledContentKeyObject even_key = {};
|
|
OEMCrypto_EntitledContentKeyObject odd_key = {};
|
|
bool has_even = load_even && num_keys_ >= 1;
|
|
bool has_odd = load_odd && num_keys_ >= 2;
|
|
if (has_even) {
|
|
even_key.entitlement_key_id = entitled_key_array_[0].entitlement_key_id;
|
|
even_key.content_key_id = entitled_key_array_[0].content_key_id;
|
|
even_key.content_key_data_iv = entitled_key_array_[0].content_key_data_iv;
|
|
even_key.content_key_data = entitled_key_array_[0].content_key_data;
|
|
even_key.content_iv.length = 0;
|
|
}
|
|
if (has_odd) {
|
|
odd_key.entitlement_key_id = entitled_key_array_[1].entitlement_key_id;
|
|
odd_key.content_key_id = entitled_key_array_[1].content_key_id;
|
|
odd_key.content_key_data_iv = entitled_key_array_[1].content_key_data_iv;
|
|
odd_key.content_key_data = entitled_key_array_[1].content_key_data;
|
|
odd_key.content_iv.length = 0;
|
|
}
|
|
|
|
OEMCryptoResult sts = OEMCrypto_LoadCasECMKeys(
|
|
entitled_key_session_,
|
|
reinterpret_cast<const uint8_t*>(entitled_key_data_),
|
|
sizeof(entitled_key_data_), has_even ? &even_key : nullptr,
|
|
has_odd ? &odd_key : nullptr);
|
|
ASSERT_EQ(expected_sts, sts);
|
|
if (expected_sts != OEMCrypto_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
if (has_even) {
|
|
VerifyEntitlementTestKey(0);
|
|
}
|
|
if (has_odd) {
|
|
VerifyEntitlementTestKey(1);
|
|
}
|
|
}
|
|
|
|
// This function verifies that the key control blocks reported by OEMCrypto
|
|
// agree with the truth key control block. Failures in this function probably
|
|
// indicate the OEMCrypto_LoadEntitledKeys did not correctly process the key
|
|
// control block.
|
|
void EntitledMessage::VerifyKCBs() {
|
|
for (unsigned int i = 0; i < num_keys_; i++) {
|
|
VerifyEntitlementTestKey(i);
|
|
}
|
|
}
|
|
|
|
void EntitledMessage::VerifyEntitlementTestKey(size_t index) {
|
|
ASSERT_GE(num_keys_, index);
|
|
|
|
EntitledContentKeyData* key_data = &entitled_key_data_[index];
|
|
const size_t entitlement_key_index = key_data->key_index;
|
|
MessageKeyData* entitlement_key =
|
|
&license_messages_->response_data().keys[entitlement_key_index];
|
|
KeyControlBlock block;
|
|
size_t size = sizeof(block);
|
|
OEMCryptoResult sts =
|
|
OEMCrypto_QueryKeyControl(entitled_key_session_, key_data->content_key_id,
|
|
key_data->content_key_id_length,
|
|
reinterpret_cast<uint8_t*>(&block), &size);
|
|
if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
ASSERT_EQ(sizeof(block), size);
|
|
// control duration and bits stored in network byte order. For printing
|
|
// we change to host byte order.
|
|
ASSERT_EQ((htonl_fnc(entitlement_key->control.duration)),
|
|
(htonl_fnc(block.duration)))
|
|
<< "For key " << index;
|
|
ASSERT_EQ(htonl_fnc(entitlement_key->control.control_bits),
|
|
htonl_fnc(block.control_bits))
|
|
<< "For key " << index;
|
|
}
|
|
}
|
|
|
|
void EntitledMessage::VerifyDecrypt() {
|
|
// Loop through all the keys and try decrypt with each one.
|
|
for (unsigned int i = 0; i < num_keys_; i++) {
|
|
const EntitledContentKeyData* const key_data = &entitled_key_data_[i];
|
|
|
|
vector<uint8_t> key_handle;
|
|
OEMCryptoResult result = GetKeyHandleIntoVector(
|
|
entitled_key_session_, key_data->content_key_id,
|
|
key_data->content_key_id_length, OEMCrypto_CipherMode_CENC, key_handle);
|
|
ASSERT_EQ(result, OEMCrypto_SUCCESS) << "For key " << i;
|
|
|
|
vector<uint8_t> expected_data;
|
|
vector<uint8_t> actual_data;
|
|
result = DecryptCTR(key_handle, key_data->content_key_data, &expected_data,
|
|
&actual_data);
|
|
EXPECT_EQ(result, OEMCrypto_SUCCESS) << "For key " << i;
|
|
EXPECT_EQ(actual_data, expected_data) << "For key " << i;
|
|
}
|
|
}
|
|
|
|
void RenewalRoundTrip::VerifyRequestSignature(
|
|
const vector<uint8_t>& data, const vector<uint8_t>& generated_signature,
|
|
size_t core_message_length) {
|
|
ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, generated_signature.size());
|
|
std::vector<uint8_t> expected_signature;
|
|
session()->key_deriver().ClientSignBuffer(data, &expected_signature);
|
|
ASSERT_EQ(expected_signature, generated_signature);
|
|
}
|
|
|
|
void RenewalRoundTrip::FillAndVerifyCoreRequest(
|
|
const std::string& core_message_string) {
|
|
if (is_release_) {
|
|
// For a release we expect that no core request was created.
|
|
EXPECT_FALSE(
|
|
oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage(
|
|
core_message_string, &core_request_));
|
|
} else {
|
|
EXPECT_TRUE(
|
|
oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage(
|
|
core_message_string, &core_request_));
|
|
EXPECT_EQ(license_messages_->api_version(),
|
|
core_request_.api_major_version);
|
|
EXPECT_EQ(license_messages_->core_request().nonce, core_request_.nonce);
|
|
EXPECT_EQ(license_messages_->core_request().session_id,
|
|
core_request_.session_id);
|
|
}
|
|
}
|
|
|
|
void RenewalRoundTrip::CreateDefaultResponse() {
|
|
if (is_release_) {
|
|
uint32_t control = 0;
|
|
uint32_t nonce = 0;
|
|
// A single key object with no key id should update all keys.
|
|
constexpr size_t index = 0;
|
|
response_data_.keys[index].key_id_length = 0;
|
|
response_data_.keys[index].key_id[0] = '\0';
|
|
const uint32_t renewal_api =
|
|
std::max<uint32_t>(core_request_.api_major_version, 15u);
|
|
std::string kcVersion = "kc" + std::to_string(renewal_api);
|
|
memcpy(response_data_.keys[index].control.verification, kcVersion.c_str(),
|
|
4);
|
|
const uint32_t duration = static_cast<uint32_t>(
|
|
license_messages_->core_response()
|
|
.timer_limits.initial_renewal_duration_seconds);
|
|
response_data_.keys[index].control.duration = htonl(duration);
|
|
response_data_.keys[index].control.nonce = htonl(nonce);
|
|
response_data_.keys[index].control.control_bits = htonl(control);
|
|
}
|
|
}
|
|
|
|
void RenewalRoundTrip::EncryptAndSignResponse() {
|
|
// Renewal messages are not encrypted.
|
|
encrypted_response_data_ = response_data_;
|
|
// Create a core response for a call to LoadRenewal.
|
|
// TODO(b/191724203): Test renewal server has different version from license
|
|
// server.
|
|
ASSERT_NE(license_messages_, nullptr);
|
|
CoreMessageFeatures features =
|
|
CoreMessageFeatures::DefaultFeatures(license_messages_->api_version());
|
|
ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreRenewalResponse(
|
|
features, core_request_, renewal_duration_seconds_,
|
|
&serialized_core_message_));
|
|
// Resize serialize core message to be just big enough or required core
|
|
// message size, whichever is larger.
|
|
serialized_core_message_.resize(
|
|
std::max(required_core_message_size_, serialized_core_message_.size()));
|
|
// Make the message buffer a just big enough, or the
|
|
// required size, whichever is larger.
|
|
const size_t message_size =
|
|
std::max(required_message_size_, serialized_core_message_.size() +
|
|
sizeof(encrypted_response_data_));
|
|
// Stripe the encrypted message.
|
|
encrypted_response_.resize(message_size);
|
|
for (size_t i = 0; i < encrypted_response_.size(); i++) {
|
|
encrypted_response_[i] = i % 0x100;
|
|
}
|
|
// Concatenate the core message and the response.
|
|
ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size());
|
|
memcpy(encrypted_response_.data(), serialized_core_message_.data(),
|
|
serialized_core_message_.size());
|
|
ASSERT_GE(encrypted_response_.size(),
|
|
serialized_core_message_.size() + sizeof(encrypted_response_data_));
|
|
memcpy(encrypted_response_.data() + serialized_core_message_.size(),
|
|
reinterpret_cast<const uint8_t*>(&encrypted_response_data_),
|
|
sizeof(encrypted_response_data_));
|
|
session()->key_deriver().ServerSignBuffer(encrypted_response_.data(),
|
|
encrypted_response_.size(),
|
|
&response_signature_);
|
|
SetEncryptAndSignResponseLengths();
|
|
}
|
|
|
|
void RenewalRoundTrip::InjectFuzzedResponseData(
|
|
OEMCrypto_Renewal_Response_Fuzz& fuzzed_data,
|
|
const uint8_t* renewal_response, const size_t renewal_response_size) {
|
|
// TODO(b/191724203): Test renewal server has different version from license
|
|
// server.
|
|
ASSERT_NE(license_messages_, nullptr);
|
|
CoreMessageFeatures features =
|
|
CoreMessageFeatures::DefaultFeatures(license_messages_->api_version());
|
|
// Serializing core message.
|
|
// This call also sets nonce in core response to match with session nonce.
|
|
oemcrypto_core_message::serialize::CreateCoreRenewalResponse(
|
|
features, fuzzed_data.core_request, fuzzed_data.renewal_duration_seconds,
|
|
&serialized_core_message_);
|
|
|
|
// Copy serialized core message and encrypted response from data and
|
|
// calculate signature. Now we will have a valid signature for data
|
|
// generated by fuzzer.
|
|
encrypted_response_.assign(serialized_core_message_.begin(),
|
|
serialized_core_message_.end());
|
|
encrypted_response_.insert(encrypted_response_.end(), renewal_response,
|
|
renewal_response + renewal_response_size);
|
|
session()->key_deriver().ServerSignBuffer(encrypted_response_.data(),
|
|
encrypted_response_.size(),
|
|
&response_signature_);
|
|
}
|
|
|
|
OEMCryptoResult RenewalRoundTrip::LoadResponse(Session* session) {
|
|
// Write corpus for oemcrypto_load_renewal_fuzz. Fuzz script expects
|
|
// encrypted response from Renewal server as input corpus data.
|
|
// Data will be signed again explicitly by fuzzer script after mutations.
|
|
if (ShouldGenerateCorpus()) {
|
|
const std::string file_name =
|
|
GetFileName("oemcrypto_load_renewal_fuzz_seed_corpus");
|
|
// Corpus for renewal response fuzzer should be in the format:
|
|
// OEMCrypto_Renewal_Response_Fuzz + license_renewal_response.
|
|
OEMCrypto_Renewal_Response_Fuzz renewal_response_fuzz;
|
|
renewal_response_fuzz.core_request = core_request_;
|
|
renewal_response_fuzz.renewal_duration_seconds = renewal_duration_seconds_;
|
|
AppendToFile(file_name,
|
|
reinterpret_cast<const char*>(&renewal_response_fuzz),
|
|
sizeof(renewal_response_fuzz));
|
|
AppendToFile(file_name,
|
|
reinterpret_cast<const char*>(&encrypted_response_data_),
|
|
sizeof(encrypted_response_data_));
|
|
}
|
|
VerifyEncryptAndSignResponseLengths();
|
|
return OEMCrypto_LoadRenewal(
|
|
session->session_id(), encrypted_response_.data(),
|
|
encrypted_response_.size(), serialized_core_message_.size(),
|
|
response_signature_.data(), response_signature_.size());
|
|
}
|
|
|
|
std::unordered_map<util::EccCurve, std::unique_ptr<util::EccPrivateKey>,
|
|
std::hash<int>>
|
|
Session::server_ephemeral_keys_;
|
|
std::mutex Session::ephemeral_key_map_lock_;
|
|
|
|
Session::Session() {}
|
|
|
|
Session::~Session() {
|
|
if (!forced_session_id_ && open_) close();
|
|
}
|
|
|
|
void Session::open() {
|
|
EXPECT_FALSE(forced_session_id_);
|
|
EXPECT_FALSE(open_);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_OpenSession(&session_id_));
|
|
open_ = true;
|
|
}
|
|
|
|
void Session::SetSessionId(uint32_t session_id) {
|
|
EXPECT_FALSE(open_);
|
|
session_id_ = session_id;
|
|
forced_session_id_ = true;
|
|
}
|
|
|
|
void Session::close() {
|
|
EXPECT_TRUE(open_ || forced_session_id_);
|
|
if (open_) {
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CloseSession(session_id_));
|
|
}
|
|
forced_session_id_ = false;
|
|
open_ = false;
|
|
}
|
|
|
|
void Session::GenerateNonce(int* error_counter) {
|
|
// We make one attempt. If it fails, we assume there was a nonce flood.
|
|
if (OEMCrypto_SUCCESS == OEMCrypto_GenerateNonce(session_id(), &nonce_)) {
|
|
return;
|
|
}
|
|
if (error_counter) {
|
|
(*error_counter)++;
|
|
} else {
|
|
wvutil::TestSleep::Sleep(1); // wait a second, then try again.
|
|
// The following is after a 1 second pause, so it cannot be from a nonce
|
|
// flood.
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_GenerateNonce(session_id(), &nonce_));
|
|
}
|
|
}
|
|
|
|
void Session::FillDefaultContext(vector<uint8_t>* mac_context,
|
|
vector<uint8_t>* enc_context) {
|
|
/* Context strings
|
|
* These context strings are normally created by the CDM layer
|
|
* from a license request message.
|
|
* They are used to test MAC and ENC key generation.
|
|
*/
|
|
*mac_context = wvutil::a2b_hex(
|
|
"41555448454e5449434154494f4e000a4c08001248000000020000101907d9ff"
|
|
"de13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e5873"
|
|
"4930acebe899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a"
|
|
"230a14080112100915007caa9b5931b76a3a85f046523e10011a093938373635"
|
|
"34333231180120002a0c31383836373837343035000000000200");
|
|
*enc_context = wvutil::a2b_hex(
|
|
"454e4352595054494f4e000a4c08001248000000020000101907d9ffde13aa95"
|
|
"c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930aceb"
|
|
"e899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a230a1408"
|
|
"0112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231"
|
|
"180120002a0c31383836373837343035000000000080");
|
|
}
|
|
|
|
// This should only be called if the device uses Provisioning 2.0. A failure in
|
|
// this function is probably caused by a bad keybox.
|
|
void Session::GenerateDerivedKeysFromKeybox(
|
|
const wvoec::WidevineKeybox& keybox) {
|
|
vector<uint8_t> mac_context;
|
|
vector<uint8_t> enc_context;
|
|
FillDefaultContext(&mac_context, &enc_context);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_GenerateDerivedKeys(
|
|
session_id(), mac_context.data(), mac_context.size(),
|
|
enc_context.data(), enc_context.size()));
|
|
key_deriver_.DeriveKeys(keybox.device_key_, sizeof(keybox.device_key_),
|
|
mac_context, enc_context);
|
|
}
|
|
|
|
void Session::GenerateDerivedKeysFromSessionKey() {
|
|
// Uses test certificate.
|
|
vector<uint8_t> session_key;
|
|
vector<uint8_t> enc_session_key;
|
|
ASSERT_TRUE(public_rsa_ || public_ec_)
|
|
<< "No public RSA/ECC key loaded in test code";
|
|
// A failure here probably indicates that there is something wrong with the
|
|
// test program and its dependency on BoringSSL.
|
|
ASSERT_TRUE(GenerateSessionKey(&session_key, &enc_session_key));
|
|
vector<uint8_t> mac_context;
|
|
vector<uint8_t> enc_context;
|
|
FillDefaultContext(&mac_context, &enc_context);
|
|
// A failure here is probably caused by having the wrong RSA key loaded.
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_DeriveKeysFromSessionKey(
|
|
session_id(), enc_session_key.data(), enc_session_key.size(),
|
|
mac_context.data(), mac_context.size(), enc_context.data(),
|
|
enc_context.size()));
|
|
|
|
key_deriver_.DeriveKeys(session_key.data(), session_key.size(), mac_context,
|
|
enc_context);
|
|
}
|
|
|
|
void Session::TestDecryptCTR(bool get_fresh_key_handle_first,
|
|
OEMCryptoResult expected_result,
|
|
size_t key_index) {
|
|
OEMCryptoResult getkeyhandle_result = OEMCrypto_SUCCESS;
|
|
if (get_fresh_key_handle_first) {
|
|
// Select the key (from FillSimpleMessage)
|
|
getkeyhandle_result = GetKeyHandle(key_handle_, key_index);
|
|
}
|
|
|
|
vector<uint8_t> unencrypted_data;
|
|
vector<uint8_t> output_buffer;
|
|
const OEMCryptoResult decrypt_result =
|
|
DecryptCTR(key_handle_, license_.keys[key_index].key_data,
|
|
&unencrypted_data, &output_buffer);
|
|
|
|
// We only have a few errors that we test are reported.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
TestDecryptResult(expected_result, getkeyhandle_result, decrypt_result))
|
|
<< "Either GetKeyHandle or DecryptCENC should return " << expected_result
|
|
<< ", but they returned " << getkeyhandle_result << " and "
|
|
<< decrypt_result << ", respectively.";
|
|
if (expected_result == OEMCrypto_SUCCESS) { // No error.
|
|
ASSERT_EQ(unencrypted_data, output_buffer);
|
|
} else {
|
|
ASSERT_NE(unencrypted_data, output_buffer);
|
|
}
|
|
}
|
|
|
|
void Session::TestDecryptEntitled(OEMCryptoResult expected_result,
|
|
OEMCrypto_SESSION session_id,
|
|
const uint8_t* content_key_id,
|
|
size_t content_key_id_length) {
|
|
// Select the key (from FillSimpleMessage)
|
|
const OEMCryptoResult getkeyhandle_result =
|
|
GetKeyHandleIntoVector(session_id, content_key_id, content_key_id_length,
|
|
OEMCrypto_CipherMode_CENC, key_handle_);
|
|
|
|
vector<uint8_t> unencrypted_data;
|
|
vector<uint8_t> output_buffer;
|
|
vector<uint8_t> encrypted_data(kTestSubsampleSectionSize);
|
|
|
|
vector<uint8_t> in_buffer(256);
|
|
vector<uint8_t> out_buffer(in_buffer.size());
|
|
OEMCrypto_SampleDescription sample_description;
|
|
OEMCrypto_SubSampleDescription subsample_description;
|
|
ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription(
|
|
in_buffer, out_buffer, &sample_description, &subsample_description));
|
|
OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0};
|
|
|
|
EncryptCTR(unencrypted_data, content_key_id, &sample_description.iv[0],
|
|
&encrypted_data);
|
|
// Try to decrypt the data with oemcrypto session id.
|
|
const OEMCryptoResult decrypt_result = OEMCrypto_DecryptCENC(
|
|
key_handle_.data(), key_handle_.size(), &sample_description, 1, &pattern);
|
|
|
|
// We only have a few errors that we test are reported.
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
TestDecryptResult(expected_result, getkeyhandle_result, decrypt_result))
|
|
<< "Either GetKeyHandle or DecryptCENC should return" << expected_result
|
|
<< ", but they returned " << getkeyhandle_result << " and "
|
|
<< decrypt_result << ", respectively.";
|
|
}
|
|
|
|
OEMCryptoResult Session::GetKeyHandle(vector<uint8_t>& key_handle,
|
|
size_t key_index,
|
|
OEMCryptoCipherMode cipher_mode) {
|
|
return GetKeyHandleIntoVector(session_id(), license_.keys[key_index].key_id,
|
|
license_.keys[key_index].key_id_length,
|
|
cipher_mode, key_handle);
|
|
}
|
|
|
|
void Session::TestDecryptResult(OEMCryptoResult expected_result,
|
|
OEMCryptoResult actual_getkeyhandle_result,
|
|
OEMCryptoResult actual_decrypt_result) {
|
|
// In most cases, we expect the result to come from either the select key or
|
|
// from the decrypt call.
|
|
if (expected_result == OEMCrypto_SUCCESS) { // No error.
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, actual_getkeyhandle_result);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, actual_decrypt_result);
|
|
} else if (expected_result == OEMCrypto_ERROR_KEY_EXPIRED ||
|
|
expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP ||
|
|
expected_result == OEMCrypto_ERROR_ANALOG_OUTPUT) {
|
|
// Key expired or output problems may be reported from select key or
|
|
// decrypt, but must be reported.
|
|
ASSERT_TRUE(actual_getkeyhandle_result == expected_result ||
|
|
actual_decrypt_result == expected_result);
|
|
} else if (expected_result == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION) {
|
|
// OEMCrypto is allowed to report either this warning or
|
|
// OEMCrypto_ERROR_INSUFFICIENT_HDCP depending on if it can disable
|
|
// restricted displays.
|
|
ASSERT_TRUE(
|
|
actual_getkeyhandle_result ==
|
|
OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION ||
|
|
actual_getkeyhandle_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP ||
|
|
actual_decrypt_result == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION ||
|
|
actual_decrypt_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP);
|
|
} else {
|
|
// OEM's can fine tune other error codes for debugging.
|
|
ASSERT_TRUE(actual_getkeyhandle_result != OEMCrypto_SUCCESS ||
|
|
actual_decrypt_result != OEMCrypto_SUCCESS);
|
|
}
|
|
}
|
|
|
|
void Session::TestGetKeyHandleExpired(size_t key_index) {
|
|
if (global_features.api_version >= 13) {
|
|
OEMCryptoResult status = GetKeyHandle(key_handle_, key_index);
|
|
// It is OK for GetKeyHandle to succeed with an expired key, but if there is
|
|
// an error, it must be OEMCrypto_ERROR_KEY_EXIRED.
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Session::LoadOEMCert(bool verify_cert) {
|
|
// Get the OEM Public Cert from OEMCrypto
|
|
vector<uint8_t> public_cert;
|
|
size_t public_cert_length = 0;
|
|
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER,
|
|
OEMCrypto_GetOEMPublicCertificate(nullptr, &public_cert_length));
|
|
ASSERT_LT(0u, public_cert_length);
|
|
public_cert.resize(public_cert_length);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetOEMPublicCertificate(
|
|
public_cert.data(), &public_cert_length));
|
|
public_cert.resize(public_cert_length);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadOEMPrivateKey(session_id()));
|
|
|
|
// The cert is a PKCS7 signed data type. First, parse it into an OpenSSL
|
|
// structure and find the certificate list.
|
|
//
|
|
// We must make defensive copies of public_cert's properties because of how
|
|
// d2i_PKCS7() works.
|
|
const unsigned char* cert_data =
|
|
reinterpret_cast<const unsigned char*>(public_cert.data());
|
|
long cert_size = static_cast<long>(public_cert.size());
|
|
boringssl_ptr<PKCS7, PKCS7_free> pkcs7(
|
|
d2i_PKCS7(nullptr, &cert_data, cert_size));
|
|
ASSERT_TRUE(pkcs7.NotNull()) << "Error parsing PKCS7 message";
|
|
ASSERT_TRUE(PKCS7_type_is_signed(pkcs7.get()))
|
|
<< "Unexpected PKCS7 message type";
|
|
|
|
STACK_OF(X509)* certs = pkcs7->d.sign->cert;
|
|
|
|
// Load the public cert's key into public_rsa_ and verify, if requested
|
|
for (size_t i = 0; certs && i < static_cast<size_t>(sk_X509_num(certs));
|
|
++i) {
|
|
X509* x509_cert = sk_X509_value(certs, static_cast<int>(i));
|
|
boringssl_ptr<EVP_PKEY, EVP_PKEY_free> pubkey(X509_get_pubkey(x509_cert));
|
|
ASSERT_TRUE(pubkey.NotNull());
|
|
if (i == 0) {
|
|
public_rsa_ =
|
|
util::RsaPublicKey::FromSslHandle(EVP_PKEY_get0_RSA(pubkey.get()));
|
|
ASSERT_TRUE(public_rsa_)
|
|
<< "Failed to extract public RSA key from OEM certificate";
|
|
return;
|
|
}
|
|
if (verify_cert) {
|
|
vector<char> buffer(80);
|
|
|
|
X509_NAME* name = X509_get_subject_name(x509_cert);
|
|
printf(" OEM Certificate Name: %s\n",
|
|
X509_NAME_oneline(name, buffer.data(),
|
|
static_cast<int>(buffer.size())));
|
|
boringssl_ptr<X509_STORE, X509_STORE_free> store(X509_STORE_new());
|
|
ASSERT_TRUE(store.NotNull());
|
|
boringssl_ptr<X509_STORE_CTX, X509_STORE_CTX_free> store_ctx(
|
|
X509_STORE_CTX_new());
|
|
ASSERT_TRUE(store_ctx.NotNull());
|
|
|
|
X509_STORE_CTX_init(store_ctx.get(), store.get(), x509_cert, nullptr);
|
|
|
|
// TODO(fredgc): Verify cert is signed by Google.
|
|
|
|
int result = X509_verify_cert(store_ctx.get());
|
|
ASSERT_GE(0, result) << " OEM Cert not valid. "
|
|
<< X509_verify_cert_error_string(
|
|
X509_STORE_CTX_get_error(store_ctx.get()));
|
|
if (result == 0) {
|
|
printf("Cert not verified: %s.\n",
|
|
X509_verify_cert_error_string(
|
|
X509_STORE_CTX_get_error(store_ctx.get())));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Session::SetTestRsaPublicKey() {
|
|
public_ec_.reset();
|
|
public_rsa_ = util::RsaPublicKey::LoadPrivateKeyInfo(
|
|
kTestRSAPKCS8PrivateKeyInfo2_2048,
|
|
sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048));
|
|
ASSERT_TRUE(public_rsa_) << "Could not parse test RSA public key #2";
|
|
}
|
|
|
|
void Session::SetPublicKeyFromPrivateKeyInfo(OEMCrypto_PrivateKeyType key_type,
|
|
const uint8_t* buffer,
|
|
size_t length) {
|
|
switch (key_type) {
|
|
case OEMCrypto_RSA_Private_Key:
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
SetRsaPublicKeyFromPrivateKeyInfo(buffer, length));
|
|
return;
|
|
case OEMCrypto_ECC_Private_Key:
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
SetEccPublicKeyFromPrivateKeyInfo(buffer, length));
|
|
return;
|
|
}
|
|
FAIL() << "Unknown key type: " << static_cast<int>(key_type);
|
|
}
|
|
|
|
void Session::SetRsaPublicKeyFromPrivateKeyInfo(const uint8_t* buffer,
|
|
size_t length) {
|
|
public_ec_.reset();
|
|
public_rsa_ = util::RsaPublicKey::LoadPrivateKeyInfo(buffer, length);
|
|
ASSERT_TRUE(public_rsa_) << "Could not parse RSA public key";
|
|
}
|
|
|
|
void Session::SetEccPublicKeyFromPrivateKeyInfo(const uint8_t* buffer,
|
|
size_t length) {
|
|
public_rsa_.reset();
|
|
public_ec_ = util::EccPublicKey::LoadPrivateKeyInfo(buffer, length);
|
|
ASSERT_TRUE(public_ec_) << "Could not parse ECC public key";
|
|
}
|
|
|
|
void Session::SetPublicKeyFromSubjectPublicKey(
|
|
OEMCrypto_PrivateKeyType key_type, const uint8_t* buffer, size_t length) {
|
|
switch (key_type) {
|
|
case OEMCrypto_RSA_Private_Key:
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
SetRsaPublicKeyFromSubjectPublicKey(buffer, length));
|
|
return;
|
|
case OEMCrypto_ECC_Private_Key:
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
SetEccPublicKeyFromSubjectPublicKey(buffer, length));
|
|
return;
|
|
}
|
|
FAIL() << "Unknown key type: " << static_cast<int>(key_type);
|
|
}
|
|
|
|
void Session::SetRsaPublicKeyFromSubjectPublicKey(const uint8_t* buffer,
|
|
size_t length) {
|
|
public_ec_.reset();
|
|
public_rsa_ = util::RsaPublicKey::Load(buffer, length);
|
|
ASSERT_TRUE(public_rsa_) << "Could not parse RSA public key";
|
|
}
|
|
|
|
void Session::SetEccPublicKeyFromSubjectPublicKey(const uint8_t* buffer,
|
|
size_t length) {
|
|
public_rsa_.reset();
|
|
public_ec_ = util::EccPublicKey::Load(buffer, length);
|
|
ASSERT_TRUE(public_ec_) << "Could not parse ECC public key";
|
|
}
|
|
|
|
void Session::VerifyRsaSignature(const vector<uint8_t>& message,
|
|
const uint8_t* signature,
|
|
size_t signature_length,
|
|
RSA_Padding_Scheme padding_scheme) {
|
|
ASSERT_TRUE(public_rsa_) << "No public RSA key loaded in test code";
|
|
if (padding_scheme != kSign_RSASSA_PSS &&
|
|
padding_scheme != kSign_PKCS1_Block1) {
|
|
FAIL() << "Padding scheme not supported: " << padding_scheme;
|
|
return;
|
|
}
|
|
const util::RsaSignatureAlgorithm algorithm =
|
|
padding_scheme == kSign_RSASSA_PSS ? util::kRsaPssDefault
|
|
: util::kRsaPkcs1Cast;
|
|
OEMCrypto_SignatureHashAlgorithm hash_algorithm = OEMCrypto_SHA1;
|
|
if (algorithm == util::kRsaPssDefault) {
|
|
ASSERT_THAT(
|
|
OEMCrypto_GetSignatureHashAlgorithm(session_id(), &hash_algorithm),
|
|
AnyOf(OEMCrypto_SUCCESS, OEMCrypto_ERROR_NOT_IMPLEMENTED));
|
|
}
|
|
const OEMCryptoResult result =
|
|
public_rsa_->VerifySignature(message.data(), message.size(), signature,
|
|
signature_length, algorithm, hash_algorithm);
|
|
ASSERT_EQ(result, OEMCrypto_SUCCESS) << "RSA signature check failed";
|
|
}
|
|
|
|
void Session::VerifyEccSignature(const vector<uint8_t>& message,
|
|
const uint8_t* signature,
|
|
size_t signature_length) {
|
|
ASSERT_TRUE(public_ec_) << "No public ECC key loaded in test code";
|
|
const OEMCryptoResult result = public_ec_->VerifySignature(
|
|
message.data(), message.size(), signature, signature_length);
|
|
ASSERT_EQ(result, OEMCrypto_SUCCESS) << "ECC signature check failed";
|
|
}
|
|
|
|
void Session::VerifySignature(const vector<uint8_t>& message,
|
|
const uint8_t* signature, size_t signature_length,
|
|
RSA_Padding_Scheme padding_scheme) {
|
|
if (public_rsa_ != nullptr) {
|
|
return VerifyRsaSignature(message, signature, signature_length,
|
|
padding_scheme);
|
|
} else if (public_ec_ != nullptr) {
|
|
return VerifyEccSignature(message, signature, signature_length);
|
|
}
|
|
FAIL() << "No public RSA or ECC key loaded in test code";
|
|
}
|
|
|
|
bool Session::GenerateRsaSessionKey(vector<uint8_t>* session_key,
|
|
vector<uint8_t>* enc_session_key) {
|
|
if (!public_rsa_) {
|
|
cerr << "No public RSA key loaded in test code\n";
|
|
return false;
|
|
}
|
|
*session_key = wvutil::a2b_hex("6fa479c731d2770b6a61a5d1420bb9d1");
|
|
*enc_session_key = public_rsa_->EncryptSessionKey(*session_key);
|
|
return !enc_session_key->empty();
|
|
}
|
|
|
|
bool Session::GenerateEccSessionKey(vector<uint8_t>* session_key,
|
|
vector<uint8_t>* ecdh_public_key_data) {
|
|
if (!public_ec_) {
|
|
cerr << "No public ECC key loaded in test code\n";
|
|
return false;
|
|
}
|
|
std::unique_lock<std::mutex> lock(Session::ephemeral_key_map_lock_);
|
|
const util::EccCurve curve = public_ec_->curve();
|
|
if (server_ephemeral_keys_.count(curve) == 0) {
|
|
server_ephemeral_keys_[curve] = util::EccPrivateKey::New(curve);
|
|
}
|
|
if (server_ephemeral_keys_.count(curve) == 0) {
|
|
cerr << "Failed to find/create server ECC key for curve "
|
|
<< util::EccCurveToString(curve) << std::endl;
|
|
return false;
|
|
}
|
|
*session_key = server_ephemeral_keys_[curve]->DeriveSessionKey(*public_ec_);
|
|
if (session_key->empty()) {
|
|
return false;
|
|
}
|
|
*ecdh_public_key_data = server_ephemeral_keys_[curve]->SerializeAsPublicKey();
|
|
if (ecdh_public_key_data->empty()) {
|
|
session_key->clear();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Session::GenerateSessionKey(vector<uint8_t>* session_key,
|
|
vector<uint8_t>* key_material) {
|
|
if (public_rsa_ != nullptr) {
|
|
return GenerateRsaSessionKey(session_key, key_material);
|
|
} else if (public_ec_ != nullptr) {
|
|
return GenerateEccSessionKey(session_key, key_material);
|
|
}
|
|
cerr << "No public RSA or ECC key loaded in test code\n";
|
|
return false;
|
|
}
|
|
|
|
void Session::LoadWrappedDrmKey(OEMCrypto_PrivateKeyType key_type,
|
|
const vector<uint8_t>& wrapped_drm_key) {
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadDRMPrivateKey(session_id(), key_type,
|
|
wrapped_drm_key.data(),
|
|
wrapped_drm_key.size()));
|
|
}
|
|
|
|
void Session::LoadWrappedRsaDrmKey(const vector<uint8_t>& wrapped_rsa_key) {
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
LoadWrappedDrmKey(OEMCrypto_RSA_Private_Key, wrapped_rsa_key));
|
|
}
|
|
|
|
void Session::LoadWrappedEccDrmKey(const vector<uint8_t>& wrapped_ecc_key) {
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
LoadWrappedDrmKey(OEMCrypto_ECC_Private_Key, wrapped_ecc_key));
|
|
}
|
|
|
|
void Session::CreateNewUsageEntry(OEMCryptoResult* status) {
|
|
OEMCryptoResult result =
|
|
OEMCrypto_CreateNewUsageEntry(session_id(), &usage_entry_number_);
|
|
if (status) {
|
|
*status = result;
|
|
return;
|
|
}
|
|
ASSERT_EQ(OEMCrypto_SUCCESS, result);
|
|
}
|
|
|
|
void Session::UpdateUsageEntry(std::vector<uint8_t>* header_buffer) {
|
|
size_t header_buffer_length = 0;
|
|
size_t entry_buffer_length = 0;
|
|
ASSERT_EQ(
|
|
OEMCrypto_ERROR_SHORT_BUFFER,
|
|
OEMCrypto_UpdateUsageEntry(session_id(), nullptr, &header_buffer_length,
|
|
nullptr, &entry_buffer_length));
|
|
ASSERT_LT(0u, header_buffer_length);
|
|
header_buffer->resize(header_buffer_length);
|
|
ASSERT_LT(0u, entry_buffer_length);
|
|
encrypted_usage_entry_.resize(entry_buffer_length);
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_UpdateUsageEntry(
|
|
session_id(), header_buffer->data(), &header_buffer_length,
|
|
encrypted_usage_entry_.data(), &entry_buffer_length));
|
|
header_buffer->resize(header_buffer_length);
|
|
encrypted_usage_entry_.resize(entry_buffer_length);
|
|
}
|
|
|
|
void Session::LoadUsageEntry(uint32_t index, const vector<uint8_t>& buffer) {
|
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
OEMCrypto_LoadUsageEntry(session_id(), index, buffer.data(),
|
|
buffer.size()));
|
|
}
|
|
|
|
void Session::MoveUsageEntry(uint32_t new_index,
|
|
std::vector<uint8_t>* header_buffer,
|
|
OEMCryptoResult expect_result) {
|
|
ASSERT_NO_FATAL_FAILURE(open());
|
|
ASSERT_NO_FATAL_FAILURE(ReloadUsageEntry());
|
|
ASSERT_EQ(expect_result, OEMCrypto_MoveEntry(session_id(), new_index));
|
|
if (expect_result == OEMCrypto_SUCCESS) {
|
|
usage_entry_number_ = new_index;
|
|
ASSERT_NO_FATAL_FAILURE(UpdateUsageEntry(header_buffer));
|
|
}
|
|
ASSERT_NO_FATAL_FAILURE(close());
|
|
}
|
|
|
|
void Session::GenerateReport(const std::string& pst,
|
|
OEMCryptoResult expected_result, Session* other) {
|
|
ASSERT_TRUE(open_);
|
|
if (other) { // If other is specified, copy mac keys.
|
|
key_deriver_ = other->key_deriver_;
|
|
}
|
|
size_t length = 0;
|
|
OEMCryptoResult sts = OEMCrypto_ReportUsage(
|
|
session_id(), reinterpret_cast<const uint8_t*>(pst.c_str()), pst.length(),
|
|
pst_report_buffer_.data(), &length);
|
|
if (expected_result == OEMCrypto_SUCCESS) {
|
|
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
|
|
}
|
|
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
pst_report_buffer_.assign(length, 0xFF); // Fill with garbage values.
|
|
}
|
|
sts = OEMCrypto_ReportUsage(session_id(),
|
|
reinterpret_cast<const uint8_t*>(pst.c_str()),
|
|
pst.length(), pst_report_buffer_.data(), &length);
|
|
ASSERT_EQ(expected_result, sts);
|
|
if (expected_result != OEMCrypto_SUCCESS) {
|
|
return;
|
|
}
|
|
pst_report_buffer_.resize(length);
|
|
EXPECT_EQ(wvutil::Unpacked_PST_Report::report_size(pst.length()), length);
|
|
vector<uint8_t> computed_signature(SHA_DIGEST_LENGTH);
|
|
key_deriver_.ClientSignPstReport(pst_report_buffer_, &computed_signature);
|
|
EXPECT_EQ(0, memcmp(computed_signature.data(), pst_report().signature(),
|
|
SHA_DIGEST_LENGTH));
|
|
EXPECT_GE(kInactiveUnused, pst_report().status());
|
|
EXPECT_GE(kHardwareSecureClock, pst_report().clock_security_level());
|
|
EXPECT_EQ(pst.length(), pst_report().pst_length());
|
|
EXPECT_EQ(0, memcmp(pst.c_str(), pst_report().pst(), pst.length()));
|
|
}
|
|
|
|
void Session::VerifyPST(const Test_PST_Report& expected) {
|
|
wvutil::Unpacked_PST_Report computed = pst_report();
|
|
EXPECT_EQ(expected.status, computed.status());
|
|
char* pst_ptr = reinterpret_cast<char*>(computed.pst());
|
|
std::string computed_pst(pst_ptr, pst_ptr + computed.pst_length());
|
|
ASSERT_EQ(expected.pst, computed_pst);
|
|
int64_t now = wvutil::Clock().GetCurrentTime();
|
|
int64_t age = now - expected.time_created; // How old is this report.
|
|
EXPECT_NEAR(expected.seconds_since_license_received + age,
|
|
computed.seconds_since_license_received(), kTimeTolerance);
|
|
// Decrypt times only valid on licenses that have been active.
|
|
if (expected.status == kActive || expected.status == kInactiveUsed) {
|
|
EXPECT_NEAR(expected.seconds_since_first_decrypt + age,
|
|
computed.seconds_since_first_decrypt(),
|
|
kUsageTableTimeTolerance);
|
|
EXPECT_NEAR(expected.seconds_since_last_decrypt + age,
|
|
computed.seconds_since_last_decrypt(),
|
|
kUsageTableTimeTolerance);
|
|
}
|
|
std::vector<uint8_t> signature(SHA_DIGEST_LENGTH);
|
|
key_deriver_.ClientSignPstReport(pst_report_buffer_, &signature);
|
|
EXPECT_EQ(0,
|
|
memcmp(computed.signature(), signature.data(), SHA_DIGEST_LENGTH));
|
|
}
|
|
|
|
void Session::VerifyReport(Test_PST_Report expected,
|
|
int64_t time_license_received,
|
|
int64_t time_first_decrypt,
|
|
int64_t time_last_decrypt) {
|
|
const int64_t now = wvutil::Clock().GetCurrentTime();
|
|
expected.seconds_since_license_received =
|
|
(time_license_received > 0 && time_license_received < now)
|
|
? now - time_license_received
|
|
: 0;
|
|
expected.seconds_since_first_decrypt =
|
|
(time_first_decrypt > 0 && time_first_decrypt < now)
|
|
? now - time_first_decrypt
|
|
: 0;
|
|
expected.seconds_since_last_decrypt =
|
|
(time_last_decrypt > 0 && time_last_decrypt < now)
|
|
? now - time_last_decrypt
|
|
: 0;
|
|
ASSERT_NO_FATAL_FAILURE(VerifyPST(expected));
|
|
}
|
|
|
|
bool ConvertByteToValidBoolean(const bool* in) {
|
|
const char* buf = reinterpret_cast<const char*>(in);
|
|
for (size_t i = 0; i < sizeof(bool); i++) {
|
|
if (buf[i]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <class CoreRequest>
|
|
void WriteRequestApiCorpus(size_t signature_length, size_t core_message_length,
|
|
vector<uint8_t>& data) {
|
|
std::string file_name;
|
|
if (std::is_same<CoreRequest,
|
|
oemcrypto_core_message::ODK_LicenseRequest>::value) {
|
|
file_name = GetFileName("oemcrypto_license_request_fuzz_seed_corpus");
|
|
} else if (std::is_same<
|
|
CoreRequest,
|
|
oemcrypto_core_message::ODK_ProvisioningRequest>::value) {
|
|
file_name = GetFileName("oemcrypto_provisioning_request_fuzz_seed_corpus");
|
|
} else if (std::is_same<CoreRequest,
|
|
oemcrypto_core_message::ODK_RenewalRequest>::value) {
|
|
file_name = GetFileName("oemcrypto_renewal_request_fuzz_seed_corpus");
|
|
} else {
|
|
LOGE("Invalid CoreRequest type while writing request api corups.");
|
|
}
|
|
// Corpus for request APIs should be signature_length + core_message_length +
|
|
// data pointer.
|
|
OEMCrypto_Request_Fuzz request_fuzz_struct;
|
|
request_fuzz_struct.core_message_length = core_message_length;
|
|
request_fuzz_struct.signature_length = signature_length;
|
|
AppendToFile(file_name, reinterpret_cast<const char*>(&request_fuzz_struct),
|
|
sizeof(OEMCrypto_Request_Fuzz));
|
|
AppendToFile(file_name, reinterpret_cast<const char*>(data.data()),
|
|
data.size());
|
|
}
|
|
|
|
OEMCryptoResult GetKeyHandleIntoVector(OEMCrypto_SESSION session,
|
|
const uint8_t* key_id,
|
|
size_t key_id_length,
|
|
OEMCryptoCipherMode cipher_mode,
|
|
vector<uint8_t>& key_handle) {
|
|
size_t key_handle_length = 0;
|
|
const OEMCryptoResult result = OEMCrypto_GetKeyHandle(
|
|
session, key_id, key_id_length, cipher_mode, nullptr, &key_handle_length);
|
|
if (result == OEMCrypto_SUCCESS) {
|
|
LOGE(
|
|
"OEMCrypto_GetKeyHandle returned SUCCESS despite getting no key handle "
|
|
"buffer");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
} else if (result != OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
return result;
|
|
}
|
|
key_handle.resize(key_handle_length);
|
|
return OEMCrypto_GetKeyHandle(session, key_id, key_id_length, cipher_mode,
|
|
key_handle.data(), &key_handle_length);
|
|
}
|
|
} // namespace wvoec
|