We are receiving reports from partners in the field that they are failing the OEMCrypto tests only because the tests assume the BCC will fit into 5k of memory but their BCC is nearly 8k in size. This patch increases the buffer to 10k. Bug: 354834629 Test: x86-64 Merged from https://widevine-internal-review.googlesource.com/204773 Change-Id: I360196518b7651139c003505253d1aed6a0c3907
2345 lines
98 KiB
C++
2345 lines
98 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_;
|
|
};
|
|
|
|
// 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
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
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),
|
|
seconds_since_license_received(0),
|
|
seconds_since_first_decrypt(0),
|
|
seconds_since_last_decrypt(0),
|
|
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;
|
|
const vector<uint8_t> context = session()->GetDefaultContext();
|
|
const size_t small_size = context.size(); // 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);
|
|
memcpy(&data[core_message_length], context.data(), context.size());
|
|
for (size_t i = context.size() + core_message_length; 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) {
|
|
keybox_ = &keybox;
|
|
} else if (global_features.provisioning_method ==
|
|
OEMCrypto_BootCertificateChain) {
|
|
// TODO(chelu): change this to CSR provisioning.
|
|
session_->LoadOEMCert(true);
|
|
session_->GenerateRsaSessionKey();
|
|
encryptor_.set_enc_key(session_->session_key());
|
|
} else {
|
|
EXPECT_EQ(global_features.provisioning_method, OEMCrypto_OEMCertificate);
|
|
session_->LoadOEMCert(true);
|
|
session_->GenerateRsaSessionKey();
|
|
encryptor_.set_enc_key(session_->session_key());
|
|
}
|
|
}
|
|
|
|
void ProvisioningRoundTrip::VerifyRequestSignature(
|
|
const vector<uint8_t>& data, const vector<uint8_t>& generated_signature,
|
|
size_t core_message_length) {
|
|
if (keybox_ == nullptr) {
|
|
session()->VerifyRsaSignature(data, generated_signature.data(),
|
|
generated_signature.size(), kSign_RSASSA_PSS);
|
|
} else {
|
|
// Setup the derived keys using the proto message (ignoring the core
|
|
// message).
|
|
ASSERT_LE(core_message_length, data.size());
|
|
const std::vector<uint8_t> base_message(data.begin() + core_message_length,
|
|
data.end());
|
|
session()->GenerateDerivedKeysFromKeybox(*keybox_, base_message);
|
|
encryptor_ = session()->key_deriver();
|
|
request_ = base_message;
|
|
|
|
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 (session_->enc_session_key().size() > 0) {
|
|
ASSERT_LE(session_->enc_session_key().size(), kMaxTestRSAKeyLength);
|
|
memcpy(response_data_.enc_message_key, session_->enc_session_key().data(),
|
|
session_->enc_session_key().size());
|
|
response_data_.enc_message_key_length = session_->enc_session_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_));
|
|
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();
|
|
if (allowed_schemes_ == kSign_RSASSA_PSS) {
|
|
return OEMCrypto_LoadProvisioning(
|
|
session->session_id(), request_.data(), request_.size(),
|
|
encrypted_response_.data(), encrypted_response_.size(),
|
|
serialized_core_message_.size(), response_signature_.data(),
|
|
response_signature_.size(), wrapped_rsa_key_.data(),
|
|
wrapped_key_length);
|
|
} else {
|
|
// TODO(b/316053127): Clean this up a lot.
|
|
const uint8_t* derivation_key = nullptr;
|
|
const size_t derivation_key_length = 0;
|
|
return OEMCrypto_LoadProvisioningCast(
|
|
session->session_id(), derivation_key, derivation_key_length,
|
|
request_.data(), request_.size(), 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 = 10240; // 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_ = std::move(wrapped_private_key);
|
|
oem_public_key_ = std::move(public_key);
|
|
oem_key_type_ = key_type;
|
|
} else {
|
|
wrapped_drm_key_ = std::move(wrapped_private_key);
|
|
drm_public_key_ = std::move(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 = 10240; // 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_ = std::move(wrapped_private_key);
|
|
drm_public_key_ = std::move(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();
|
|
const std::vector<uint8_t> context = session->GetDefaultContext();
|
|
return OEMCrypto_LoadProvisioningCast(
|
|
session->session_id(), session->enc_session_key().data(),
|
|
session->enc_session_key().size(), context.data(), context.size(),
|
|
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;
|
|
|
|
vector<uint8_t> sign_source;
|
|
if (global_features.api_version < 17) {
|
|
sign_source.assign(data.begin() + core_message_length, data.end());
|
|
} else if (global_features.api_version < 19) {
|
|
sign_source = data;
|
|
} else {
|
|
sign_source.resize(SHA512_DIGEST_LENGTH);
|
|
SHA512(data.data(), data.size(), sign_source.data());
|
|
}
|
|
session()->VerifySignature(sign_source, 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);
|
|
}
|
|
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) {
|
|
const auto context = session_->GetDefaultContext(!skip_request_hash_);
|
|
ASSERT_NO_FATAL_FAILURE(session_->GenerateDerivedKeysFromSessionKey(context));
|
|
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));
|
|
}
|
|
|
|
const vector<uint8_t> context =
|
|
session->GetDefaultContext(!skip_request_hash_);
|
|
|
|
// 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(), context.data(), context.size(),
|
|
session->enc_session_key().data(), session->enc_session_key().size(),
|
|
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));
|
|
|
|
EXPECT_EQ(1,
|
|
GetRandBytes(key_data->content_iv, sizeof(key_data->content_iv)));
|
|
key_data->content_iv_length = sizeof(key_data->content_iv);
|
|
offsets->content_iv =
|
|
FindSubstring(key_data->content_iv, key_data->content_iv_length);
|
|
}
|
|
|
|
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 = entitled_key_array_[0].content_iv;
|
|
even_key.cipher_mode = OEMCrypto_CipherMode_CBC;
|
|
}
|
|
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 = entitled_key_array_[1].content_iv;
|
|
odd_key.cipher_mode = OEMCrypto_CipherMode_CBC;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Nothing is needed for this function but it needs a definition since it's
|
|
// declared as a virtual function in the RoundTrip class.
|
|
void RenewalRoundTrip::CreateDefaultResponse() {}
|
|
|
|
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());
|
|
}
|
|
|
|
void ReleaseRoundTrip::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 ReleaseRoundTrip::FillAndVerifyCoreRequest(
|
|
const std::string& core_message_string) {
|
|
EXPECT_TRUE(
|
|
oemcrypto_core_message::deserialize::CoreReleaseRequestFromMessage(
|
|
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);
|
|
}
|
|
|
|
// Nothing is needed for this function but it needs a definition since it's
|
|
// declared as a virtual function in the RoundTrip class.
|
|
void ReleaseRoundTrip::CreateDefaultResponse() {}
|
|
|
|
void ReleaseRoundTrip::EncryptAndSignResponse() {
|
|
// Release messages are not encrypted.
|
|
encrypted_response_data_ = response_data_;
|
|
// Create a core response for a call to LoadRelease.
|
|
// TODO(b/191724203): Test release 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::CreateCoreReleaseResponse(
|
|
features, core_request_, seconds_since_license_received_,
|
|
seconds_since_first_decrypt_, &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();
|
|
}
|
|
|
|
OEMCryptoResult ReleaseRoundTrip::LoadResponse(Session* session) {
|
|
// TODO(vickymin): Write corpus for oemcrypto_load_release_fuzz.
|
|
VerifyEncryptAndSignResponseLengths();
|
|
return OEMCrypto_LoadRelease(
|
|
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_));
|
|
}
|
|
}
|
|
|
|
vector<uint8_t> Session::GetDefaultContext(bool do_hash) {
|
|
/* Context string
|
|
* This context string is normally created by the CDM layer
|
|
* from a license request message.
|
|
* They are used to test MAC and ENC key generation.
|
|
*/
|
|
auto ret = wvutil::a2b_hex(
|
|
"0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840"
|
|
"8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202"
|
|
"fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931"
|
|
"b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637"
|
|
"38373430350000");
|
|
if (do_hash) {
|
|
uint8_t hash[SHA512_DIGEST_LENGTH];
|
|
SHA512(ret.data(), ret.size(), hash);
|
|
ret.assign(hash, hash + sizeof(hash));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// 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) {
|
|
return GenerateDerivedKeysFromKeybox(keybox, GetDefaultContext());
|
|
}
|
|
|
|
void Session::GenerateDerivedKeysFromKeybox(
|
|
const wvoec::WidevineKeybox& keybox, const std::vector<uint8_t>& context) {
|
|
key_deriver_.DeriveKeys(keybox.device_key_, sizeof(keybox.device_key_),
|
|
context);
|
|
}
|
|
|
|
void Session::GenerateDerivedKeysFromSessionKey() {
|
|
GenerateDerivedKeysFromSessionKey(GetDefaultContext());
|
|
}
|
|
|
|
void Session::GenerateDerivedKeysFromSessionKey(
|
|
const std::vector<uint8_t>& context) {
|
|
// Uses test certificate.
|
|
ASSERT_TRUE(GenerateSessionKey());
|
|
key_deriver_.DeriveKeys(session_key_.data(), session_key_.size(), 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";
|
|
}
|
|
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() {
|
|
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() {
|
|
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;
|
|
}
|
|
enc_session_key_ = server_ephemeral_keys_[curve]->SerializeAsPublicKey();
|
|
if (enc_session_key_.empty()) {
|
|
session_key_.clear();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Session::GenerateSessionKey() {
|
|
if (public_rsa_ != nullptr) {
|
|
return GenerateRsaSessionKey();
|
|
} else if (public_ec_ != nullptr) {
|
|
return GenerateEccSessionKey();
|
|
}
|
|
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
|