Merge "Merge recent doc changes for OEMCrypto"

This commit is contained in:
TreeHugger Robot
2020-10-21 23:15:27 +00:00
committed by Android (Google) Code Review
75 changed files with 5717 additions and 4488 deletions

View File

@@ -13,6 +13,7 @@ LOCAL_SRC_FILES:= \
oec_decrypt_fallback_chain.cpp \
oec_key_deriver.cpp \
oec_session_util.cpp \
oemcrypto_corpus_generator_helper.cpp \
oemcrypto_session_tests_helper.cpp \
oemcrypto_test.cpp \
oemcrypto_test_android.cpp \
@@ -21,6 +22,7 @@ LOCAL_SRC_FILES:= \
../../cdm/util/test/test_sleep.cpp \
LOCAL_C_INCLUDES += \
$(LOCAL_PATH)/fuzz_tests \
$(LOCAL_PATH)/../include \
$(LOCAL_PATH)/../odk/include \
$(LOCAL_PATH)/../odk/kdo/include \

View File

@@ -0,0 +1,247 @@
# OEMCRYPTO Fuzzing
## Objective
* Run fuzzing on OEMCrypto public APIs on linux using google supported
clusterfuzz infrastructure to find security vulnerabilities.
Design Document - https://docs.google.com/document/d/1mdSV2irJZz5Y9uYb5DmSIddBjrAIZU9q8G5Q_BGpA4I/edit?usp=sharing
Fuzzing at google -
[go/fuzzing](https://g3doc.corp.google.com/security/fuzzing/g3doc/fuzzing_resources.md?cl=head)
## Monitoring
### Cluster fuzz statistics
* Performance of OEMCrypto fuzz binaries running continuously using cluster
fuzz infrastructure can be monitored
[here](https://clusterfuzz.corp.google.com/fuzzer-stats).
The options to select are `Job type: libfuzzer_asan_oemcrypto` and `Fuzzer:
fuzzer name you are looking for`
Example: [load_license_fuzz](https://clusterfuzz.corp.google.com/fuzzer-stats?group_by=by-day&date_start=2020-07-11&date_end=2020-07-17&fuzzer=libFuzzer_oemcrypto_load_license_fuzz&job=libfuzzer_asan_oemcrypto)
### Issues filed by clusterfuzz - Fixing those issues
* Any issues found with the fuzz target under test are reported by clusterfuzz
[here](https://b.corp.google.com/hotlists/2442954).
* The bug will have a link to the test case that generated the bug. Download
the test case and follow the steps from
[testing fuzzer locally](#testing-fuzzer-locally) section to run the fuzzer
locally using the test case that caused the crash.
* Once the issue is fixed, consider adding the test case that caused the crash
to the seed corpus zip file. Details about seed corpus and their location
are mentioned in
[this section](#build-oemcrypto-unit-tests-to-generate-corpus).
## Corpus
* Once the fuzzer scripts are ready and running continuously using clusterfuzz
or android infrastructure, we can measure the efficiency of fuzzers by
looking at code coverage and number of new features that have been
discovered by fuzzer scripts here Fuzz script statistics.
A fuzzer which tries to start from random inputs and figure out intelligent
inputs to crash the libraries can be time consuming and not effective. A way
to make fuzzers more effective is by providing a set of valid and invalid
inputs of the library so that fuzzer can use those as a starting point.
These sets of valid and invalid inputs are called corpus.
The idea is to run OEMCrypto unit tests and read required data into binary
corpus files before calling into respective OEMCrypto APIs under test.
Writing corpus data to binary files is controlled by --generate_corpus flag.
### Build OEMCrypto unit tests to generate corpus
* Install Pre-requisites
```shell
$ sudo apt-get install gyp ninja-build
```
* download cdm source code (including ODK & OEMCrypto unit tests):
```shell
$ git clone sso://widevine-internal/cdm
```
* Build OEMCrypto unit tests and run with --generate_corpus flag to generate
corpus files:
```shell
$ cd /path/to/cdm/repo
$ export CDM_DIR=/path/to/cdm/repo
$ export PATH_TO_CDM_DIR=..
$ gyp --format=ninja --depth=$(pwd) oemcrypto/oemcrypto_unittests.gyp
$ ninja -C out/Default/
$ ./out/Default/oemcrypto_unittests --generate_corpus
```
* To avoid uploading huge binary files to git repository, the corpus files
will be saved in fuzzername_seed_corpus.zip format in blockbuster project's
oemcrypto_fuzzing_corpus GCS bucket using gsutil. If you need permissions
for blockbuster project, contact widevine-engprod@google.com.
```shell
$ gsutil cp gs://oemcrypto_fuzzing_corpus/<fuzzername_seed_corpus.zip> \
<destination_path>
```
## Testing fuzzer locally
* Corpus needed to run fuzz tests locally are available in blockbuster
project's oemcrypto_fuzzing_corpus GCS bucket. If you need permissions for
this project, contact widevine-engprod@google.com. Download corpus.
```shell
$ gsutil cp gs://oemcrypto_fuzzing_corpus/<fuzzername_seed_corpus.zip> \
<destination_path>
```
* Add flags to generate additional debugging information. Add '-g3' flag to
oemcrypto_fuzztests.gypi cflags_cc in order to generate additional debug
information locally.
* Build and test fuzz scripts locally using:
```shell
$ export CXX=clang++
$ export CC=clang
$ export GYP_DEFINES="clang=1"
$ cd /path/to/cdm/repo
$ export PATH_TO_CDM_DIR=.
$ gyp --format=ninja --depth=$(pwd) \
oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gyp
$ ninja -C out/Default/
$ mkdir /tmp/new_interesting_corpus
$ ./out/Default/fuzzer_binary /tmp/new_interesting_corpus \
/path/to/fuzz/seed/corpus/folder
```
* In order to run fuzz script against a crash input, follow the above steps
and run the fuzz binary against crash input rather than seed corpus.
```shell
$ ./out/Default/fuzzer_binary crash_input_file
```
## Adding a new OEMCrypto fuzz script
* In order to fuzz a new OEMCrypto API in future, a fuzz script can be added to
oemcrypto/test/fuzz_tests folder which ends with _fuzz.cc.
* In the program, define the function LLVMFuzzerTestOneInput with the following signature:
```
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
<your test code goes here>
return 0;
}
```
*Note*: Make sure LLVMFuzzerTestOneInput calls the function you want to fuzz.
* Add a new target to oemcrypto_fuzztests.gyp file and follow instructions in
[testing fuzzer locally](#testing-fuzzer-locally) to build and test locally.
## Building OEMCrypto fuzz scripts and uploading them to Google Cloud Storage:
* We are using Google Cloud Buid (GCB) in order to setup continuous
integration which uploads OEMCrypto fuzz binaries to Google Cloud Storage.
GCB expects build script in form of a docker image that is uploaded to
Google Container Registry(GCR).
The cloud build scripts (docker images) for widevine projects are
[here](https://widevine-internal.googlesource.com/cloud/+/refs/heads/master/docker/README.md)
Refer to README of the project to setup a new docker image and uploading
the image to GCR.
* Git on borg repository needs to be integrated with GCB and a git trigger
needs to be set up in order to achieve continuous integration. Git trigger
will mention which docker image the GCB needs to use in order to build fuzz
binaries. GCB searches for docker images from GCR.
Design document lists the steps to create a git trigger.
### Adding a new fuzz script to the build script:
* In order to update build script such as adding a new fuzzer to build script,
we need to update the build script in docker image from cloud repository.
[Build script.](https://widevine-internal.googlesource.com/cloud/+/refs/heads/master/docker
/cloud_build/oemcrypto/release/ubuntu/fuzz/build.sh)
Add the new fuzz script name to fuzzers variable and follow steps in README
to upload new docker image. Make sure you update the tag to be higher than
latest version in GCR.
Run the following command from your machine to update the docker image tag
in the git trigger.
```shell
stubby call --rpc_creds_file=/tmp/mint.txt \
blade:alphasource-ci-proctor-metadata-service-prod \
ProctorMetadataService.UpdateTrigger --proto2 <<EOF
trigger {
cloud_project_number: 257246079067
name: "cdm-git-trigger"
id: "e8939c9a-d971-4c05-91b5-e0544abf872b"
state: LIVE
git_trigger {
url: "https://widevine-internal.googlesource.com/cdm"
branch_name: "master"
}
build_configs {
build {
steps {
name: "gcr.io/google.com/blockbuster-1154/
cloud-build-oemcrypto-release-ubuntu-fuzz:LATEST_TAG_VERSION"
}
}
}
result_config {
email_config {
notify_condition {
condition: ON_FAILURE
}
to_address: "wideving-engprod@google.com"
}
}
}
EOF
```
## Generate code coverage reports locally
* Code coverage is a means of measuring fuzzer performance. We want to make
sure that our fuzzer covers all the paths in our code and make any tweeks to
fuzzer logic so we can maximize coverage to get better results.
Coverage reports for git on borg project is not automated and needs to be
generated manually. Future plan is to build a dashboard for git on borg
coverage reports.
### Generate code coverage reports using script from Google cloud build
* A docker image with script to generate code coverage reports for oemcrypto
fuzz scripts is linked with a GCB trigger
`oemcrypto-fuzzing-code-coverage-git-trigger`. More information about clang
source based coverage can be found
[here](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html).
* This trigger when invoked compiles oemcrypto fuzz scripts with clang source
based code coverage enabled, downloads latest corpus from cluster fuzz
for the respective fuzzer, generates and uploads code coverage html reports
to [GCS](https://pantheon.corp.google.com/storage/browser/oemcrypto_fuzzing_code_coverage_reports;tab=objects?forceOnBucketsSortingFiltering=false&project=google.com:blockbuster-1154&prefix=).
* The trigger can be invoked manually using cloud scheduler
`oemcrypto_fuzzing_code_coverage_reports`.
* In order to generate latest code coverage reports from master branch,
go to pantheon->cloud scheduler->oemcrypto_fuzzing_code_coverage_reports and
click on `RUN NOW` button.
* The above step should invoke a google cloud build. Go to cloud build console
and find latest build job with Trigger Name
`oemcrypto-fuzzing-code-coverage-git-trigger`.
* Once the build job is successful, latest code coverage reports can be
downloaded from [GCS](https://pantheon.corp.google.com/storage/browser/oemcrypto_fuzzing_code_coverage_reports;tab=objects?forceOnBucketsSortingFiltering=false&project=google.com:blockbuster-1154&prefix=).
The coverage report folder uploaded to GCS is appended with timestamp.

View File

@@ -0,0 +1,182 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "FuzzedDataProvider.h"
#include "OEMCryptoCENC.h"
#include "log.h"
#include "oemcrypto_fuzz_helper.h"
#include "oemcrypto_fuzz_structs.h"
namespace wvoec {
const size_t MAX_FUZZ_SAMPLE_SIZE = 5 * MB;
// Free dynamic memory allocated by fuzzer script.
void FreeOutputBuffers(OEMCrypto_SESSION session_id,
OEMCrypto_SampleDescription* sample_description,
size_t sample_index, int* secure_fd_array) {
for (size_t i = 0; i < sample_index; i++) {
OEMCrypto_DestBufferDesc fuzzed_output_descriptor =
sample_description[i].buffers.output_descriptor;
switch (fuzzed_output_descriptor.type) {
case OEMCrypto_BufferType_Clear: {
delete[] fuzzed_output_descriptor.buffer.clear.address;
break;
}
case OEMCrypto_BufferType_Secure: {
OEMCrypto_FreeSecureBuffer(session_id, &fuzzed_output_descriptor,
secure_fd_array[i]);
break;
}
case OEMCrypto_BufferType_Direct: {
break;
}
}
}
}
// Function to initialize output buffer pointers by allocating memory.
// Limiting output buffer size to 5 MB as 4 MB is maximum size specified
// by resource rating tier documentation.
bool InitializeOutputBuffers(OEMCrypto_SESSION session_id,
OEMCrypto_DestBufferDesc& output_descriptor,
size_t sample_index,
vector<int>& secure_fd_array) {
switch (output_descriptor.type) {
case OEMCrypto_BufferType_Clear: {
output_descriptor.buffer.clear
.address = new OEMCrypto_SharedMemory[std::min(
MAX_FUZZ_SAMPLE_SIZE, output_descriptor.buffer.clear.address_length)];
return true;
}
case OEMCrypto_BufferType_Secure: {
int* secure_fd;
OEMCryptoResult sts = OEMCrypto_AllocateSecureBuffer(
session_id,
std::min(MAX_FUZZ_SAMPLE_SIZE,
output_descriptor.buffer.secure.handle_length),
&output_descriptor, secure_fd);
if (sts == OEMCrypto_SUCCESS) secure_fd_array[sample_index] = *secure_fd;
return sts == OEMCrypto_SUCCESS;
}
case OEMCrypto_BufferType_Direct: {
return true;
}
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Redirect printf and log statements from oemcrypto functions to a file to
// reduce noise
RedirectStdoutToFile();
size_t samples_length;
// Split data using separator.
auto inputs = SplitInput(data, size);
if (inputs.size() < 2) {
return 0;
}
OEMCrypto_Decrypt_Cenc_Fuzz fuzzed_structure;
if (inputs[0].size() < sizeof(fuzzed_structure)) {
return 0;
}
// Copy OEMCrypto_Decrypt_Cenc_Fuzz from input data.
memcpy(&fuzzed_structure, data, sizeof(fuzzed_structure));
ConvertDataToValidEnum(OEMCrypto_CipherMode_MaxValue,
&fuzzed_structure.cipher_mode);
size_t remaining_size_for_samples =
inputs[0].size() - sizeof(fuzzed_structure);
// Initialize FDP structures to read data using inbuilt functions.
FuzzedDataProvider fuzzed_sample_data(data + sizeof(fuzzed_structure),
remaining_size_for_samples);
FuzzedDataProvider fuzzed_subsample_data(inputs[1].data(), inputs[1].size());
// Read subsamples from fuzzed data.
vector<OEMCrypto_SubSampleDescription> subsamples;
while (fuzzed_subsample_data.remaining_bytes() >
sizeof(OEMCrypto_SubSampleDescription)) {
OEMCrypto_SubSampleDescription subsample;
fuzzed_subsample_data.ConsumeData(&subsample,
sizeof(OEMCrypto_SubSampleDescription));
subsamples.push_back(subsample);
}
if (subsamples.size() == 0) {
return 0;
}
// Infer samples_length from fuzzed data.
size_t sample_description_size = sizeof(OEMCrypto_SampleDescription);
samples_length =
fuzzed_sample_data.remaining_bytes() / sample_description_size;
if (samples_length == 0) {
return 0;
}
// Initialize sample_descriptions array.
vector<OEMCrypto_SampleDescription> sample_descriptions(samples_length);
// Create array to maintain secure_fd buffer values for secure buffers.
vector<int> secure_fd_array(samples_length);
OEMCryptoLicenseAPIFuzz license_api_fuzz;
Session* session = license_api_fuzz.session();
// Copy samples from fuzzed data.
size_t input_subsample_index = 0;
size_t total_input_data_length = 0;
for (size_t i = 0; i < samples_length; i++) {
fuzzed_sample_data.ConsumeData(&sample_descriptions[i],
sample_description_size);
ConvertDataToValidEnum(
OEMCrypto_BufferType_MaxValue,
&sample_descriptions[i].buffers.output_descriptor.type);
// Copy random data into input sample data. Cap input data length at 5 MB,
// 1 MB higher than that described by resource rating tier.
total_input_data_length += std::min(
MAX_FUZZ_SAMPLE_SIZE, sample_descriptions[i].buffers.input_data_length);
// Copy sub sample data.
sample_descriptions[i].subsamples = &subsamples[input_subsample_index];
input_subsample_index += sample_descriptions[i].subsamples_length;
if (input_subsample_index > subsamples.size()) return 0;
} // Sample loop.
// Allocate input/output buffers for each sample description.
vector<OEMCrypto_SharedMemory> input_buffer(total_input_data_length);
RAND_bytes(input_buffer.data(), total_input_data_length);
size_t input_buffer_index = 0;
for (size_t i = 0; i < samples_length; i++) {
sample_descriptions[i].buffers.input_data =
&input_buffer[input_buffer_index];
input_buffer_index += std::min(
MAX_FUZZ_SAMPLE_SIZE, sample_descriptions[i].buffers.input_data_length);
// Create output buffer pointers. If secure buffer is not supported, we
// explicitly convert to clear buffer and fuzz.
if (!InitializeOutputBuffers(
session->session_id(),
sample_descriptions[i].buffers.output_descriptor, i,
secure_fd_array)) {
LOGI(
"[OEMCrypto decrypt CENC fuzz] Secure buffers are not supported. Use "
"clear buffer instead.");
sample_descriptions[i].buffers.output_descriptor.type =
OEMCrypto_BufferType_Clear;
InitializeOutputBuffers(session->session_id(),
sample_descriptions[i].buffers.output_descriptor,
i, secure_fd_array);
}
}
// Load license and call decrypt_cenc API.
license_api_fuzz.LoadLicense();
OEMCrypto_SelectKey(session->session_id(), session->license().keys[0].key_id,
session->license().keys[0].key_id_length,
fuzzed_structure.cipher_mode);
OEMCrypto_DecryptCENC(session->session_id(), sample_descriptions.data(),
samples_length, &fuzzed_structure.pattern);
FreeOutputBuffers(session->session_id(), sample_descriptions.data(),
samples_length, secure_fd_array.data());
return 0;
}
} // namespace wvoec

View File

@@ -0,0 +1,25 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "oemcrypto_fuzz_helper.h"
namespace wvoec {
void RedirectStdoutToFile() { freopen("log.txt", "a", stdout); }
std::vector<std::vector<uint8_t>> SplitInput(const uint8_t* data, size_t size) {
std::vector<std::vector<uint8_t>> result;
auto current_pos = data;
auto end = data + size;
// Using memmem to find separator
while (const uint8_t* pos = reinterpret_cast<const uint8_t*>(
memmem(current_pos, end - current_pos, kFuzzDataSeparator,
sizeof(kFuzzDataSeparator)))) {
result.push_back({current_pos, pos});
current_pos = pos + sizeof(kFuzzDataSeparator);
}
if (current_pos < end) {
result.push_back({current_pos, end});
}
return result;
}
} // namespace wvoec

View File

@@ -0,0 +1,106 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef OEMCRYPTO_FUZZ_HELPER_H_
#define OEMCRYPTO_FUZZ_HELPER_H_
#include <vector>
#include "FuzzedDataProvider.h"
#include "OEMCryptoCENC.h"
#include "oec_device_features.h"
#include "oemcrypto_corpus_generator_helper.h"
#include "oemcrypto_session_tests_helper.h"
namespace wvoec {
// Initial setup to create a valid OEMCrypto state such as initializing crypto
// firmware/hardware, installing golden key box etc. in order to fuzz
// OEMCrypto APIs.
class InitializeFuzz : public SessionUtil {
public:
InitializeFuzz() {
wvoec::global_features.Initialize();
OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox));
OEMCrypto_Initialize();
EnsureTestKeys();
}
~InitializeFuzz() { OEMCrypto_Terminate(); }
};
class OEMCryptoLicenseAPIFuzz : public InitializeFuzz {
public:
OEMCryptoLicenseAPIFuzz() : license_messages_(&session_) {
session_.open();
InstallTestRSAKey(&session_);
session_.GenerateNonce();
}
~OEMCryptoLicenseAPIFuzz() {
session_.close();
}
LicenseRoundTrip& license_messages() { return license_messages_; }
Session* session() { return &session_; }
void LoadLicense() {
license_messages_.SignAndVerifyRequest();
license_messages_.CreateDefaultResponse();
license_messages_.EncryptAndSignResponse();
license_messages_.LoadResponse();
}
private:
Session session_;
LicenseRoundTrip license_messages_;
};
class OEMCryptoProvisioningAPIFuzz : public InitializeFuzz {
public:
OEMCryptoProvisioningAPIFuzz()
: provisioning_messages_(&session_, encoded_rsa_key_) {
// Opens a session and Generates Nonce.
provisioning_messages_.PrepareSession(keybox_);
}
~OEMCryptoProvisioningAPIFuzz() { session_.close(); }
ProvisioningRoundTrip& provisioning_messages() {
return provisioning_messages_;
}
private:
Session session_;
ProvisioningRoundTrip provisioning_messages_;
};
// Initial setup to create a valid state such as creating session, installing
// golden key box etc. in order to fuzz Load Renewal API.
class OEMCryptoRenewalAPIFuzz : public OEMCryptoLicenseAPIFuzz {
public:
OEMCryptoRenewalAPIFuzz() : renewal_messages_(&license_messages()) {}
RenewalRoundTrip& renewal_messages() { return renewal_messages_; }
private:
RenewalRoundTrip renewal_messages_;
};
// Convert data to valid enum value.
template <typename T>
void ConvertDataToValidEnum(T max_enum_value, T* t) {
FuzzedDataProvider fuzzed_enum_data(reinterpret_cast<uint8_t*>(t), sizeof(T));
*t = static_cast<T>(fuzzed_enum_data.ConsumeIntegralInRange<uint32_t>(
0, static_cast<uint32_t>(max_enum_value)));
}
// Redirect printf and log statements from oemcrypto functions to a file to
// reduce noise
void RedirectStdoutToFile();
// Function to split fuzzer input using delimiter "-_^_".
std::vector<std::vector<uint8_t>> SplitInput(const uint8_t* data, size_t size);
} // namespace wvoec
#endif // OEMCRYPTO_FUZZ_HELPER_H_

View File

@@ -0,0 +1,42 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef OEMCRYPTO_FUZZ_STRUCTS_H_
#define OEMCRYPTO_FUZZ_STRUCTS_H_
namespace wvoec {
struct OEMCrypto_Renewal_Response_Fuzz {
// Timer limits in core license response needs to be fuzzed as load renewal
// depends on timer limits loaded from license response.
ODK_TimerLimits timer_limits;
// message(core_response + license_renewal_response) which mimics
// response from license renewal server needs to be fuzzed. core_request
// will be used to generate serialized core response.
oemcrypto_core_message::ODK_RenewalRequest core_request;
// Renewal duration seconds needs to be fuzzed which is part of serialized
// core message from license renewal server.
uint64_t renewal_duration_seconds;
// license_renewal_response is of variable length and not included in this
// structure.
};
struct OEMCrypto_Request_Fuzz {
// We would like to fuzz computed signature_length, input core_message_length
// that ODK parses and actual message buffer to the request APIs.
size_t signature_length;
size_t core_message_length;
// Request message is of variable length and not included in this structure.
};
struct OEMCrypto_Decrypt_Cenc_Fuzz {
// Corpus format is as below, let | be separator.
// cipher_mode + pattern + sample_data for all samples |
// subsample_data for all samples
OEMCryptoCipherMode cipher_mode;
OEMCrypto_CENCEncryptPatternDesc pattern;
// Sample data and subsample data are of variable length and not included in
// this structure.
};
} // namespace wvoec
#endif // OEMCRYPTO_FUZZ_STRUCTS_H_

View File

@@ -0,0 +1,36 @@
#include "properties.h"
#include "oemcrypto_session_tests_helper.h"
using namespace wvoec;
static bool is_init = false;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
SessionUtil session_helper;
if (!is_init) {
wvoec::global_features.Initialize();
wvoec::global_features.RestrictFilter("*");
wvcdm::Properties::Init();
is_init = true;
}
OEMCrypto_Initialize();
session_helper.EnsureTestKeys();
Session s;
s.open();
s.GenerateDerivedKeysFromKeybox(session_helper.keybox_);
static const uint32_t SignatureBufferMaxLength = size;
vector<uint8_t> signature(SignatureBufferMaxLength);
size_t signature_length = signature.size();
OEMCryptoResult sts;
sts = OEMCrypto_GenerateSignature(s.session_id(), data, size,
&signature[0], &signature_length);
s.close();
OEMCrypto_Terminate();
return 0;
}

View File

@@ -0,0 +1,28 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "oemcrypto_fuzz_helper.h"
#include "oemcrypto_fuzz_structs.h"
namespace wvoec {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Redirect printf and log statements from oemcrypto functions to a file to
// reduce noise
RedirectStdoutToFile();
// Reject the input if it is less than fuzz data structure size.
if (size < sizeof(OEMCrypto_Request_Fuzz)) {
return 0;
}
// Input for license request API will be modified by OEMCrypto, hence it
// cannot be a const. Fuzzer complains if const identifier is removed of data,
// hence copying data into a non const pointer.
uint8_t* input = new uint8_t[size];
memcpy(input, data, size);
OEMCryptoLicenseAPIFuzz license_api_fuzz;
license_api_fuzz.license_messages().InjectFuzzedRequestData(input, size);
delete[] input;
return 0;
}
} // namespace wvoec

View File

@@ -0,0 +1,66 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "FuzzedDataProvider.h"
#include "oemcrypto_fuzz_helper.h"
#include "oemcrypto_fuzz_structs.h"
namespace wvoec {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Redirect printf and log statements from oemcrypto functions to a file to
// reduce noise
RedirectStdoutToFile();
// Corpus format is as below, let | be separator.
// message buffer with key data | entitled content key object array with
// offsets and lengths to read key data from message buffer.
// Split data using separator.
auto inputs = SplitInput(data, size);
if (inputs.size() < 2) {
return 0;
}
FuzzedDataProvider fuzzed_entitled_content_key_array(inputs[1].data(),
inputs[1].size());
// Message to be verified. Return 0 if key data buffer is empty.
if (inputs[0].size() == 0) {
return 0;
}
// Copy data to OEMCrypto_EntitledContentKeyObject array.
size_t entitled_content_key_object_size =
sizeof(OEMCrypto_EntitledContentKeyObject);
size_t entitled_content_key_array_length =
fuzzed_entitled_content_key_array.remaining_bytes() /
entitled_content_key_object_size;
if (entitled_content_key_array_length == 0) {
return 0;
}
OEMCrypto_EntitledContentKeyObject* entitled_content_key_array =
new OEMCrypto_EntitledContentKeyObject[entitled_content_key_array_length];
for (size_t i = 0; i < entitled_content_key_array_length; i++) {
fuzzed_entitled_content_key_array.ConsumeData(
&entitled_content_key_array[i], entitled_content_key_object_size);
}
OEMCryptoLicenseAPIFuzz license_api_fuzz;
// Setting up state. Load default entitlement license to load entitlement
// keys into sessions key table.
license_api_fuzz.license_messages().set_license_type(
OEMCrypto_EntitlementLicense);
license_api_fuzz.LoadLicense();
// Call OEMCrypto_LoadEntitledContentKeys with fuzzed buffers.
Session* session = license_api_fuzz.session();
uint8_t* fuzzed_key_data = inputs[0].data();
size_t fuzzed_key_data_size = inputs[0].size();
OEMCrypto_LoadEntitledContentKeys(
session->session_id(), fuzzed_key_data, fuzzed_key_data_size,
entitled_content_key_array_length, entitled_content_key_array);
delete[] entitled_content_key_array;
return 0;
}
} // namespace wvoec

View File

@@ -0,0 +1,30 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "oemcrypto_fuzz_helper.h"
namespace wvoec {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Redirect printf and log statements from oemcrypto functions to a file to
// reduce noise
RedirectStdoutToFile();
if (size < sizeof(ODK_ParsedLicense) + sizeof(MessageData)) {
return 0;
}
OEMCryptoLicenseAPIFuzz license_api_fuzz;
license_api_fuzz.license_messages().SignAndVerifyRequest();
// Interpreting input fuzz data as unencrypted (core_response + license
// message data) from license server.
license_api_fuzz.license_messages().InjectFuzzedResponseData(data, size);
// Convert OEMCrypto_LicenseType in core_response to a valid enum value.
ConvertDataToValidEnum(
OEMCrypto_LicenstType_MaxValue,
&license_api_fuzz.license_messages().core_response().license_type);
license_api_fuzz.license_messages().EncryptAndSignResponse();
license_api_fuzz.license_messages().LoadResponse();
return 0;
}
} // namespace wvoec

View File

@@ -0,0 +1,27 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "oemcrypto_fuzz_helper.h"
namespace wvoec {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Redirect printf and log statements from oemcrypto functions to a file to
// reduce noise
RedirectStdoutToFile();
if (size < sizeof(ODK_ParsedProvisioning) + sizeof(RSAPrivateKeyMessage)) {
return 0;
}
OEMCryptoProvisioningAPIFuzz provisioning_api_fuzz;
provisioning_api_fuzz.provisioning_messages().SignAndVerifyRequest();
// Interpreting input fuzz data as unencrypted(core_response + provisioning
// message data) from provisioning server.
provisioning_api_fuzz.provisioning_messages().InjectFuzzedResponseData(data,
size);
provisioning_api_fuzz.provisioning_messages().EncryptAndSignResponse();
provisioning_api_fuzz.provisioning_messages().LoadResponse();
return 0;
}
} // namespace wvoec

View File

@@ -0,0 +1,42 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "oemcrypto_fuzz_helper.h"
#include "oemcrypto_fuzz_structs.h"
namespace wvoec {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Redirect printf and log statements from oemcrypto functions to a file to
// reduce noise
RedirectStdoutToFile();
if (size < sizeof(OEMCrypto_Renewal_Response_Fuzz)) {
return 0;
}
// Copy input data to OEMCrypto_Renewal_Response_Fuzz and rest of message
// into encrypted license_renewal_response.
OEMCrypto_Renewal_Response_Fuzz fuzzed_data;
memcpy(&fuzzed_data, data, sizeof(fuzzed_data));
const uint8_t* renewal_response =
data + sizeof(OEMCrypto_Renewal_Response_Fuzz);
const size_t renewal_response_size =
size - sizeof(OEMCrypto_Renewal_Response_Fuzz);
OEMCryptoRenewalAPIFuzz renewal_response_fuzz;
renewal_response_fuzz.license_messages().SignAndVerifyRequest();
renewal_response_fuzz.license_messages().CreateDefaultResponse();
// Inject timer limits from fuzzed input to timer_limits field from
// core license response.
renewal_response_fuzz.license_messages().InjectFuzzedTimerLimits(fuzzed_data);
renewal_response_fuzz.license_messages().EncryptAndSignResponse();
renewal_response_fuzz.license_messages().LoadResponse();
// Call renewal response API using fuzzed data.
renewal_response_fuzz.renewal_messages().SignAndVerifyRequest();
renewal_response_fuzz.renewal_messages().InjectFuzzedResponseData(
fuzzed_data, renewal_response, renewal_response_size);
renewal_response_fuzz.renewal_messages().LoadResponse();
return 0;
}
} // namespace wvoec

View File

@@ -0,0 +1,28 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "oemcrypto_fuzz_helper.h"
#include "oemcrypto_fuzz_structs.h"
namespace wvoec {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Redirect printf and log statements from oemcrypto functions to a file to
// reduce noise
RedirectStdoutToFile();
// If input size is less than fuzz data structure size, reject the input.
if (size < sizeof(OEMCrypto_Request_Fuzz)) {
return 0;
}
// Input for provisioning request API will be modified by OEMCrypto, hence it
// cannot be a const. Fuzzer complains if const identifier is removed of data,
// hence copying data into a non const pointer.
uint8_t* input = new uint8_t[size];
memcpy(input, data, size);
OEMCryptoProvisioningAPIFuzz provisioning_api_fuzz;
provisioning_api_fuzz.provisioning_messages().InjectFuzzedRequestData(input,
size);
delete[] input;
return 0;
}
} // namespace wvoec

View File

@@ -0,0 +1,27 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "oemcrypto_fuzz_helper.h"
#include "oemcrypto_fuzz_structs.h"
namespace wvoec {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Redirect printf and log statements from oemcrypto functions to a file to
// reduce noise
RedirectStdoutToFile();
// If input size is less than fuzz data structure, reject the input.
if (size < sizeof(OEMCrypto_Request_Fuzz)) {
return 0;
}
// Input for renewal request API will be modified by OEMCrypto, hence it
// cannot be a const. Fuzzer complains if const identifier is removed of data,
// hence copying data into a non const pointer.
uint8_t* input = new uint8_t[size];
memcpy(input, data, size);
OEMCryptoRenewalAPIFuzz renewal_api_fuzz;
renewal_api_fuzz.renewal_messages().InjectFuzzedRequestData(input, size);
delete[] input;
return 0;
}
} // namespace wvoec

View File

@@ -0,0 +1,10 @@
#include <stddef.h>
#include <stdint.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size > 0 && data[0] == 'H')
if (size > 1 && data[1] == 'I')
if (size > 2 && data[2] == '!')
__builtin_trap();
return 0;
}

View File

@@ -7,6 +7,7 @@
#include <stdint.h>
#include <string.h>
#include "oemcrypto_corpus_generator_helper.h"
#include "oemcrypto_types.h"
#include "string_conversions.h"
@@ -55,7 +56,12 @@ OEMCryptoResult DecryptFallbackChain::Decrypt(
OEMCrypto_DecryptCENC(session_id, samples, samples_length, pattern);
// No need for a fallback. Abort early.
if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) return sts;
if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
if (ShouldGenerateCorpus()) {
WriteDecryptCencCorpus(cipher_mode, samples, pattern, samples_length);
}
return sts;
}
// Fall back to decrypting individual samples.
for (size_t i = 0; i < samples_length; ++i) {
@@ -75,7 +81,12 @@ OEMCryptoResult DecryptFallbackChain::DecryptSample(
OEMCryptoResult sts = OEMCrypto_DecryptCENC(session_id, &sample, 1, pattern);
// No need for a fallback. Abort early.
if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) return sts;
if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
if (ShouldGenerateCorpus()) {
WriteDecryptCencCorpus(cipher_mode, &sample, pattern, 1);
}
return sts;
}
// Fall back to decrypting individual subsamples.
OEMCrypto_SampleDescription fake_sample = sample;
@@ -88,7 +99,7 @@ OEMCryptoResult DecryptFallbackChain::DecryptSample(
fake_sample.subsamples = &subsample;
fake_sample.subsamples_length = 1;
sts = DecryptSubsample(session_id, fake_sample, pattern);
sts = DecryptSubsample(session_id, fake_sample, pattern, cipher_mode);
if (sts != OEMCrypto_SUCCESS) return sts;
fake_sample.buffers.input_data += length;
@@ -106,11 +117,17 @@ OEMCryptoResult DecryptFallbackChain::DecryptSample(
// OEMCrypto implementation does not accept full subsamples.
OEMCryptoResult DecryptFallbackChain::DecryptSubsample(
OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample,
const OEMCrypto_CENCEncryptPatternDesc* pattern) {
const OEMCrypto_CENCEncryptPatternDesc* pattern,
OEMCryptoCipherMode cipher_mode) {
OEMCryptoResult sts = OEMCrypto_DecryptCENC(session_id, &sample, 1, pattern);
// No need for a fallback. Abort early.
if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) return sts;
if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
if (ShouldGenerateCorpus()) {
WriteDecryptCencCorpus(cipher_mode, &sample, pattern, 1);
}
return sts;
}
// Fall back to decrypting individual subsample halves.
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
@@ -132,7 +149,7 @@ OEMCryptoResult DecryptFallbackChain::DecryptSubsample(
subsample.num_bytes_encrypted == 0)
fake_subsample.subsample_flags |= OEMCrypto_LastSubsample;
sts = DecryptSubsampleHalf(session_id, fake_sample, pattern);
sts = DecryptSubsampleHalf(session_id, fake_sample, pattern, cipher_mode);
if (sts != OEMCrypto_SUCCESS) return sts;
// Advance the buffers for the other half, in case they're needed.
@@ -154,7 +171,7 @@ OEMCryptoResult DecryptFallbackChain::DecryptSubsample(
if (subsample.subsample_flags & OEMCrypto_LastSubsample)
fake_subsample.subsample_flags |= OEMCrypto_LastSubsample;
sts = DecryptSubsampleHalf(session_id, fake_sample, pattern);
sts = DecryptSubsampleHalf(session_id, fake_sample, pattern, cipher_mode);
if (sts != OEMCrypto_SUCCESS) return sts;
}
@@ -166,7 +183,11 @@ OEMCryptoResult DecryptFallbackChain::DecryptSubsample(
// caller.
OEMCryptoResult DecryptFallbackChain::DecryptSubsampleHalf(
OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample,
const OEMCrypto_CENCEncryptPatternDesc* pattern) {
const OEMCrypto_CENCEncryptPatternDesc* pattern,
OEMCryptoCipherMode cipher_mode) {
if (ShouldGenerateCorpus()) {
WriteDecryptCencCorpus(cipher_mode, &sample, pattern, 1);
}
return OEMCrypto_DecryptCENC(session_id, &sample, 1, pattern);
// In a real CDM, you would want some fallback here to handle the case where
// the buffer is too big for the OEMCrypto implementation. But in the case of
@@ -175,4 +196,40 @@ OEMCryptoResult DecryptFallbackChain::DecryptSubsampleHalf(
// here.
}
// Used for OEMCrypto Fuzzing: Corpus format is as below, let | be separator.
// cipher_mode + pattern + sample_data for all samples |
// subsample_data for all samples
void WriteDecryptCencCorpus(
OEMCryptoCipherMode cipher_mode,
const OEMCrypto_SampleDescription* samples_description,
const OEMCrypto_CENCEncryptPatternDesc* pattern, size_t samples_length) {
const std::string file_name =
GetFileName("oemcrypto_decrypt_cenc_fuzz_seed_corpus");
// Cipher mode.
AppendToFile(file_name, reinterpret_cast<const char*>(&cipher_mode),
sizeof(OEMCryptoCipherMode));
// Pattern.
AppendToFile(file_name, reinterpret_cast<const char*>(pattern),
sizeof(OEMCrypto_CENCEncryptPatternDesc));
// Sample data for all samples.
for (size_t i = 0; i < samples_length; i++) {
AppendToFile(file_name,
reinterpret_cast<const char*>(&samples_description[i]),
sizeof(OEMCrypto_SampleDescription));
}
AppendSeparator(file_name);
// Subsample data for all samples.
for (size_t i = 0; i < samples_length; i++) {
for (size_t j = 0; j < samples_description[i].subsamples_length; j++) {
AppendToFile(
file_name,
reinterpret_cast<const char*>(&samples_description[i].subsamples[j]),
sizeof(OEMCrypto_SubSampleDescription));
}
}
}
} // namespace wvoec

View File

@@ -43,17 +43,24 @@ class DecryptFallbackChain {
static OEMCryptoResult DecryptSubsample(
OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample,
const OEMCrypto_CENCEncryptPatternDesc* pattern);
const OEMCrypto_CENCEncryptPatternDesc* pattern,
OEMCryptoCipherMode cipher_mode);
static OEMCryptoResult DecryptSubsampleHalf(
OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample,
const OEMCrypto_CENCEncryptPatternDesc* pattern);
const OEMCrypto_CENCEncryptPatternDesc* pattern,
OEMCryptoCipherMode cipher_mode);
// There is no reason to have an instance of this class.
DecryptFallbackChain() = delete;
CORE_DISALLOW_COPY_AND_ASSIGN(DecryptFallbackChain);
};
void WriteDecryptCencCorpus(OEMCryptoCipherMode cipher_mode,
const OEMCrypto_SampleDescription* samples,
const OEMCrypto_CENCEncryptPatternDesc* pattern,
size_t samples_length);
} // namespace wvoec
#endif // CDM_OEC_DECRYPT_FALLBACK_CHAIN_H_

View File

@@ -32,6 +32,7 @@ void DeviceFeatures::Initialize() {
return;
}
uint8_t buffer[1];
uint8_t iv[16] = {};
size_t size = 0;
provisioning_method = OEMCrypto_GetProvisioningMethod();
printf("provisioning_method = %s\n",
@@ -58,12 +59,12 @@ void DeviceFeatures::Initialize() {
printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false");
generic_crypto =
(OEMCrypto_ERROR_NOT_IMPLEMENTED !=
OEMCrypto_Generic_Encrypt(session, buffer, 0, buffer,
OEMCrypto_Generic_Encrypt(session, buffer, 0, iv,
OEMCrypto_AES_CBC_128_NO_PADDING, buffer));
printf("generic_crypto = %s.\n", generic_crypto ? "true" : "false");
OEMCrypto_CloseSession(session);
api_version = OEMCrypto_APIVersion();
printf("api_version = %d.\n", api_version);
printf("api_version = %u.\n", api_version);
// These unit tests only work with new usage tables. We do not test v12
// usage tables.
if (api_version > 12) usage_table = OEMCrypto_SupportsUsageTable();
@@ -80,7 +81,7 @@ void DeviceFeatures::Initialize() {
}
printf("cast_receiver = %s.\n", cast_receiver ? "true" : "false");
resource_rating = OEMCrypto_ResourceRatingTier();
printf("resource_rating = %d, security level %s.\n", resource_rating,
printf("resource_rating = %u, security level %s.\n", resource_rating,
OEMCrypto_SecurityLevel());
uint32_t decrypt_hash_type = OEMCrypto_SupportsDecryptHash();
supports_crc = (decrypt_hash_type == OEMCrypto_CRC_Clear_Buffer);
@@ -114,8 +115,7 @@ void DeviceFeatures::Initialize() {
std::string security_level = OEMCrypto_SecurityLevel();
supports_level_1 = (security_level == "L1");
printf("SecurityLevel is %s (%s)\n",
supports_level_1 ? "Level 1" : "Not Level 1",
security_level.c_str());
supports_level_1 ? "Level 1" : "Not Level 1", security_level.c_str());
CheckSecureBuffers();
OEMCrypto_Terminate();
initialized_ = true;

View File

@@ -61,6 +61,12 @@ void Encryptor::PadAndEncryptProvisioningMessage(
EXPECT_EQ(1, GetRandBytes(data->rsa_key_iv, KEY_IV_SIZE));
ASSERT_EQ(enc_key_.size(), KEY_SIZE);
*encrypted = *data;
if (data->rsa_key_length > sizeof(data->rsa_key)) {
// OEMCrypto Fuzzing: fuzzed |rsa_key_length| overflows the allocated
// buffer. Skip encryption in that case.
return;
}
size_t padding = AES_BLOCK_SIZE - (data->rsa_key_length % AES_BLOCK_SIZE);
memset(data->rsa_key + data->rsa_key_length, static_cast<uint8_t>(padding),
padding);

View File

@@ -31,8 +31,11 @@
#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"
@@ -162,6 +165,10 @@ void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
std::max(required_message_size_, core_message_length + small_size);
data.resize(message_size);
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
if (ShouldGenerateCorpus()) {
WriteRequestApiCorpus<CoreRequest>(gen_signature_length,
core_message_length, data);
}
vector<uint8_t> gen_signature(gen_signature_length);
ASSERT_EQ(PrepAndSignRequest(session()->session_id(), data.data(),
@@ -177,6 +184,27 @@ void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
VerifyRequestSignature(data, gen_signature, core_message_length);
}
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,
@@ -309,8 +337,35 @@ void ProvisioningRoundTrip::EncryptAndSignResponse() {
&response_signature_);
}
void ProvisioningRoundTrip::InjectFuzzedResponseData(const uint8_t* data,
size_t size UNUSED) {
// Interpreting fuzz data as unencrypted core_response + message_data
const size_t core_response_size = sizeof(ODK_ParsedProvisioning);
// Copy core_response from data and serialize.
memcpy(&core_response_, data, core_response_size);
// Copy provisioning message data into response_data.
memcpy(&response_data_, data + core_response_size, 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;
const OEMCryptoResult sts = LoadResponseNoRetry(session, &wrapped_key_length);
if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts;
@@ -472,6 +527,69 @@ void LicenseRoundTrip::CreateDefaultResponse() {
FillCoreResponseSubstrings();
}
void LicenseRoundTrip::ConvertDataToValidBools(ODK_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 UNUSED) {
// Interpreting fuzz data as unencrypted core_response + message_data
const size_t core_response_size = sizeof(ODK_ParsedLicense);
// Copy core_response from data.
memcpy(&core_response_, data, core_response_size);
// Maximum number of keys could be kMaxNumKeys(30). key_array_length can be
// any random value as it is read from fuzz data.
// Key data array(MessageKeyData keys[kMaxNumKeys]) will be looped over
// key_array_length number of times during LoadLicense. If key_array_length is
// more than kMaxNumKeys, setting it to max value of kMaxNumKeys as we should
// not go out of bounds of this array length. 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);
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);
}
}
// Copy response_data from data and set nonce to match one in request to pass
// nonce validations.
memcpy(&response_data_, data + core_response_size, sizeof(response_data_));
for (uint32_t i = 0; i < num_keys_; ++i) {
response_data_.keys[i].control.nonce = session()->nonce();
}
}
void LicenseRoundTrip::CreateResponseWithGenericCryptoKeys() {
CreateDefaultResponse();
response_data_.keys[0].control.control_bits |=
@@ -540,17 +658,28 @@ void LicenseRoundTrip::EncryptAndSignResponse() {
2 * MAC_KEY_SIZE, response_data_.mac_key_iv);
for (unsigned int i = 0; i < num_keys_; i++) {
memcpy(iv_buffer, &response_data_.keys[i].control_iv[0], KEY_IV_SIZE);
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);
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);
// 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 (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);
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);
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);
}
}
if (api_version_ < kCoreMessagesAPI) {
serialized_core_message_.resize(0);
@@ -588,6 +717,21 @@ void LicenseRoundTrip::EncryptAndSignResponse() {
OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) {
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.
AppendToFile(file_name, reinterpret_cast<const char*>(&core_response_),
sizeof(ODK_ParsedLicense));
AppendToFile(file_name, reinterpret_cast<const char*>(&response_data_),
sizeof(response_data_));
}
// 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
@@ -757,6 +901,17 @@ void EntitledMessage::LoadKeys(OEMCryptoResult expected_sts) {
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_),
sizeof(entitled_key_data_));
AppendSeparator(file_name);
AppendToFile(file_name, reinterpret_cast<const char*>(entitled_key_array_),
num_keys_);
}
ASSERT_EQ(expected_sts,
OEMCrypto_LoadEntitledContentKeys(
license_messages_->session()->session_id(),
@@ -906,7 +1061,46 @@ void RenewalRoundTrip::EncryptAndSignResponse() {
&response_signature_);
}
void RenewalRoundTrip::InjectFuzzedResponseData(
OEMCrypto_Renewal_Response_Fuzz& fuzzed_data,
const uint8_t* renewal_response, const size_t renewal_response_size) {
// Serializing core message.
// This call also sets nonce in core response to match with session nonce.
oemcrypto_core_message::serialize::CreateCoreRenewalResponse(
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_));
}
if (license_messages_->api_version() < kCoreMessagesAPI) {
return OEMCrypto_RefreshKeys(
session->session_id(), encrypted_response_.data(),
@@ -1129,10 +1323,9 @@ void Session::TestDecryptResult(OEMCryptoResult expected_result,
void Session::TestSelectExpired(unsigned int key_index) {
if (global_features.api_version >= 13) {
OEMCryptoResult status =
OEMCrypto_SelectKey(session_id(), license().keys[key_index].key_id,
license().keys[key_index].key_id_length,
OEMCrypto_CipherMode_CTR);
OEMCryptoResult status = OEMCrypto_SelectKey(
session_id(), license().keys[key_index].key_id,
license().keys[key_index].key_id_length, OEMCrypto_CipherMode_CTR);
// It is OK for SelectKey to succeed with an expired key, but if there is
// an error, it must be OEMCrypto_ERROR_KEY_EXIRED.
if (status != OEMCrypto_SUCCESS) {
@@ -1200,9 +1393,9 @@ void Session::LoadOEMCert(bool verify_cert) {
// 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()));
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(
@@ -1258,7 +1451,7 @@ bool Session::VerifyPSSSignature(EVP_PKEY* pkey, const uint8_t* message,
}
if (EVP_PKEY_CTX_set_signature_md(pkey_ctx,
const_cast<EVP_MD *>(EVP_sha1())) != 1) {
const_cast<EVP_MD*>(EVP_sha1())) != 1) {
LOGE("EVP_PKEY_CTX_set_signature_md failed in VerifyPSSSignature");
goto err;
}
@@ -1307,18 +1500,17 @@ void Session::VerifyRSASignature(const vector<uint8_t>& message,
boringssl_ptr<EVP_PKEY, EVP_PKEY_free> pkey(EVP_PKEY_new());
ASSERT_EQ(1, EVP_PKEY_set1_RSA(pkey.get(), public_rsa_));
const bool ok = VerifyPSSSignature(
pkey.get(), message.data(), message.size(), signature,
signature_length);
const bool ok =
VerifyPSSSignature(pkey.get(), message.data(), message.size(),
signature, signature_length);
EXPECT_TRUE(ok) << "PSS signature check failed.";
} else if (padding_scheme == kSign_PKCS1_Block1) {
vector<uint8_t> padded_digest(signature_length);
int size;
// RSA_public_decrypt decrypts the signature, and then verifies that
// it was padded with RSA PKCS1 padding.
size = RSA_public_decrypt(
signature_length, signature, padded_digest.data(), public_rsa_,
RSA_PKCS1_PADDING);
size = RSA_public_decrypt(signature_length, signature, padded_digest.data(),
public_rsa_, RSA_PKCS1_PADDING);
EXPECT_GT(size, 0);
padded_digest.resize(size);
EXPECT_EQ(message, padded_digest);
@@ -1384,16 +1576,14 @@ void Session::UpdateUsageEntry(std::vector<uint8_t>* header_buffer) {
void Session::LoadUsageEntry(uint32_t index, const vector<uint8_t>& buffer) {
usage_entry_number_ = index;
encrypted_usage_entry_ = buffer;
ASSERT_EQ(
OEMCrypto_SUCCESS,
OEMCrypto_LoadUsageEntry(
session_id(), index, buffer.data(), buffer.size()));
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));
@@ -1405,8 +1595,7 @@ void Session::MoveUsageEntry(uint32_t new_index,
}
void Session::GenerateReport(const std::string& pst,
OEMCryptoResult expected_result,
Session* other) {
OEMCryptoResult expected_result, Session* other) {
ASSERT_TRUE(open_);
if (other) { // If other is specified, copy mac keys.
key_deriver_ = other->key_deriver_;
@@ -1442,14 +1631,13 @@ void Session::GenerateReport(const std::string& pst,
void Session::VerifyPST(const Test_PST_Report& expected) {
wvcdm::Unpacked_PST_Report computed = pst_report();
EXPECT_EQ(expected.status, computed.status());
char* pst_ptr = reinterpret_cast<char *>(computed.pst());
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 = wvcdm::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);
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,
@@ -1461,8 +1649,8 @@ void Session::VerifyPST(const Test_PST_Report& expected) {
}
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));
EXPECT_EQ(0,
memcmp(computed.signature(), signature.data(), SHA_DIGEST_LENGTH));
}
void Session::VerifyReport(Test_PST_Report expected,
@@ -1484,4 +1672,41 @@ void Session::VerifyReport(Test_PST_Report expected,
: 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.
AppendToFile(file_name, reinterpret_cast<const char*>(&signature_length),
sizeof(signature_length));
AppendToFile(file_name, reinterpret_cast<const char*>(&core_message_length),
sizeof(core_message_length));
AppendToFile(file_name, reinterpret_cast<const char*>(data.data()),
data.size());
}
} // namespace wvoec

View File

@@ -18,6 +18,7 @@
#include "odk.h"
#include "oec_device_features.h"
#include "oec_key_deriver.h"
#include "oemcrypto_fuzz_structs.h"
#include "oemcrypto_types.h"
#include "pst_report.h"
@@ -32,6 +33,8 @@ void PrintTo(const vector<uint8_t>& value, ostream* os);
} // namespace std
namespace wvoec {
// OEMCrypto Fuzzing: Set max signture length to 1mb.
const size_t MB = 1024 * 1024;
// Make sure this is larger than kMaxKeysPerSession, in oemcrypto_test.cpp
constexpr size_t kMaxNumKeys = 30;
@@ -158,6 +161,9 @@ class RoundTrip {
// Have OEMCrypto sign a request message and then verify the signature and the
// core message.
virtual void SignAndVerifyRequest();
// Used for OEMCrypto Fuzzing: Function to convert fuzzer data to valid
// License/Provisioning/Renwal request data that can be serialized.
virtual void InjectFuzzedRequestData(uint8_t* data, size_t size);
// Create a default |response_data| and |core_response|.
virtual void CreateDefaultResponse() = 0;
// Copy fields from |response_data| to |padded_response_data|, encrypting
@@ -241,6 +247,11 @@ class ProvisioningRoundTrip
void set_allowed_schemes(uint32_t allowed_schemes) {
allowed_schemes_ = allowed_schemes;
}
// Used for OEMCrypto Fuzzing: Function to convert fuzzer data to valid
// provisioning response data that can be parsed. Calculates signature for
// data generated by fuzzer, so that signature validation passes when parsing
// provisioning response.
void InjectFuzzedResponseData(const uint8_t* data, size_t size);
protected:
void VerifyRequestSignature(const vector<uint8_t>& data,
@@ -286,6 +297,18 @@ class LicenseRoundTrip
license_type_(OEMCrypto_ContentLicense),
request_hash_() {}
void CreateDefaultResponse() override;
// Used for OEMCrypto Fuzzing: Function to inject fuzzed timer limits
// into timer_limits field from core_response. We need to fuzz timer
// limits in order to efficiently fuzz load renewal response API.
void InjectFuzzedTimerLimits(OEMCrypto_Renewal_Response_Fuzz& fuzzed_data);
// Used for OEMCrypto Fuzzing: Function to convert fuzzer data to valid
// License response data that can be parsed. Calculates signature for data
// generated by fuzzer, so that signature validation passes when parsing
// license response.
void InjectFuzzedResponseData(const uint8_t* data, size_t size);
// Used for OEMCrypto Fuzzing: Convert boolean flags in parsed_license to
// valid bytes to avoid errors from msan.
void ConvertDataToValidBools(ODK_ParsedLicense* t);
// Create a license with four keys. Each key is responsible for one of generic
// encrypt (key 0), decrypt (key 1), sign (key 2) and verify (key 3). Each key
// is allowed only one type of operation.
@@ -386,6 +409,9 @@ class RenewalRoundTrip
is_release_(false) {}
void CreateDefaultResponse() override;
void EncryptAndSignResponse() override;
void InjectFuzzedResponseData(OEMCrypto_Renewal_Response_Fuzz& fuzzed_data,
const uint8_t* renewal_response,
const size_t renewal_response_size);
OEMCryptoResult LoadResponse() override { return LoadResponse(session_); }
OEMCryptoResult LoadResponse(Session* session) override;
uint64_t renewal_duration_seconds() const {
@@ -557,15 +583,14 @@ class Session {
// Verify the Usage Report. If any time is greater than 10 minutes, it is
// assumed to be an absolute time, and time_since will be computed relative to
// now.
void VerifyReport(Test_PST_Report report,
int64_t time_license_received = 0,
void VerifyReport(Test_PST_Report report, int64_t time_license_received = 0,
int64_t time_first_decrypt = 0,
int64_t time_last_decrypt = 0);
// Create an entry in the old usage table based on the given report.
void CreateOldEntry(const Test_PST_Report &report);
void CreateOldEntry(const Test_PST_Report& report);
// Create a new entry and copy the old entry into it. Then very the report
// is right.
void CopyAndVerifyOldEntry(const Test_PST_Report &report,
void CopyAndVerifyOldEntry(const Test_PST_Report& report,
std::vector<uint8_t>* header_buffer);
// The unencrypted license response or license renewal response.
@@ -599,6 +624,13 @@ class Session {
string pst_;
};
// Used for OEMCrypto Fuzzing: Convert byte to a valid boolean to avoid errors
// generated by msan.
bool ConvertByteToValidBoolean(const bool* in);
// Used for OEMCrypto Fuzzing: Generates corpus for request APIs.
template <class CoreRequest>
void WriteRequestApiCorpus(size_t signature_length, size_t core_message_length,
vector<uint8_t>& data);
} // namespace wvoec
#endif // CDM_OEC_SESSION_UTIL_H_

View File

@@ -0,0 +1,44 @@
/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary */
/* source code may only be used and distributed under the Widevine Master */
/* License Agreement. */
#include "oemcrypto_corpus_generator_helper.h"
#include <fstream>
#include <iostream>
namespace wvoec {
bool g_generate_corpus;
void AppendToFile(const std::string& file_name, const char* message,
const size_t message_size) {
std::ofstream filebuf(file_name.c_str(), std::ios::app | std::ios::binary);
if (!filebuf) {
std::cout << "Cannot open file " << file_name.c_str() << std::endl;
}
filebuf.write(message, message_size);
filebuf.close();
}
void AppendSeparator(const std::string& file_name) {
std::ofstream filebuf(file_name.c_str(), std::ios::app | std::ios::binary);
if (!filebuf) {
std::cout << "Cannot open file " << file_name.c_str() << std::endl;
}
filebuf.write(reinterpret_cast<const char*>(&kFuzzDataSeparator),
sizeof(kFuzzDataSeparator));
filebuf.close();
}
std::string GetFileName(const char* directory) {
std::string file_name(PATH_TO_CORPUS);
file_name += directory;
file_name += "/";
file_name += std::to_string(rand());
return file_name;
}
void SetGenerateCorpus(bool should_generate_corpus) {
g_generate_corpus = should_generate_corpus;
}
bool ShouldGenerateCorpus() { return g_generate_corpus; }
} // namespace wvoec

View File

@@ -0,0 +1,30 @@
/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary */
/* source code may only be used and distributed under the Widevine Master */
/* License Agreement. */
#ifndef CDM_OEMCRYPTO_CORPUS_GENERATOR_HELPER_H_
#define CDM_OEMCRYPTO_CORPUS_GENERATOR_HELPER_H_
#define PATH_TO_CORPUS "./oemcrypto/test/fuzz_tests/corpus/"
#include <stdio.h>
#include <stdlib.h>
#include <string>
namespace wvoec {
const uint8_t kFuzzDataSeparator[] = {'-', '_', '^', '_'};
void AppendToFile(const std::string& file_name, const char* message,
const size_t message_size);
// Function to append separator "-_^_" between contents of corpus file.
void AppendSeparator(const std::string& file_name);
std::string GetFileName(const char* directory);
void SetGenerateCorpus(bool should_generate_corpus);
// Output of this function decides if binary data needs to be written
// to corpus files or not. Controlled by --generate_corpus flag.
bool ShouldGenerateCorpus();
} // namespace wvoec
#endif // CDM_OEMCRYPTO_CORPUS_GENERATOR_HELPER_H_

View File

@@ -2,8 +2,29 @@
// source code may only be used and distributed under the Widevine Master
// License Agreement.
//
// OEMCrypto unit tests
//
/**
* @mainpage OEMCrypto Unit Tests
*
* The OEMCrypto unit tests are designed to verify that an implementation of
* OEMCrypto is correctly supporting the OEMCrypto API.
*
* @defgroup basic Basic Functionality Tests
* Basic functionality tests.
*
* @defgroup license License Request Tests
* Test for requesting and loading licenses.
*
* @defgroup renewal License Renewal Tests
* Tests for renewing licenses.
*
* @defgroup decrypt Decrypt Tests
* Tests for decrypting content.
*
* @defgroup usage_table Usage Table Tests
* Tests that use the usage table.
*/
#include <ctype.h>
#include <openssl/aes.h>
#include <openssl/err.h>
@@ -17,6 +38,7 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <chrono>
#include <functional>
#include <iostream>
#include <map>
#include <string>
@@ -82,6 +104,9 @@ constexpr size_t kBufferOverrunPadding = 16;
// Resource tiers:
constexpr size_t KiB = 1024;
constexpr size_t MiB = 1024 * 1024;
// Huge input buffer length used for OEMCryptoMemory* tests.
constexpr size_t kHugeInputBufferLength = 100 * MiB;
constexpr bool kCheckStatus = true;
// With OEMCrypto v15 and above, we have different resource requirements
// depending on the resource rating reported by OEMCrypto. This function looks
// up the required value for the specified resource for the target OEMCrypto
@@ -93,6 +118,32 @@ T GetResourceValue(T (&resource_values)[N]) {
return resource_values[global_features.resource_rating - 1];
}
// Used for testing oemcrypto APIs with huge buffers.
typedef const std::function<OEMCryptoResult(size_t)> oemcrypto_function;
// Function to test APIs that expect a buffer length as input
// by passing huge buffer lengths upto end_buffer_length and test that the API
// doesn't crash.
void TestHugeLengthDoesNotCrashAPI(oemcrypto_function f,
size_t start_buffer_length,
size_t end_buffer_length,
bool check_status) {
OEMCryptoResult sts = OEMCrypto_SUCCESS;
for (size_t buffer_length = start_buffer_length;
buffer_length < end_buffer_length &&
(sts == OEMCrypto_SUCCESS || sts == OEMCrypto_ERROR_SHORT_BUFFER ||
!check_status);
buffer_length *= 2) {
sts = f(buffer_length);
}
}
// Function to test APIs that expect a buffer length as input
// by passing huge buffer lengths upto kHugeInputBufferLength and test that
// the API doesn't crash.
void TestHugeLengthDoesNotCrashAPI(oemcrypto_function f, bool check_status) {
TestHugeLengthDoesNotCrashAPI(f, 1, kHugeInputBufferLength, check_status);
}
// After API 16, we require 300 entries in the usage table. Before API 16, we
// required 200.
size_t RequiredUsageSize() {
@@ -148,10 +199,14 @@ class OEMCryptoClientTest : public ::testing::Test, public SessionUtil {
}
};
//
// General tests.
// This test is first, becuase it might give an idea why other
// tests are failing when the device has the wrong keybox installed.
/// @addtogroup basic
/// @{
/**
* Verifies initialization and logs version information.
* This test is first, because it might give an idea why other
* tests are failing when the device has the wrong keybox installed.
*/
TEST_F(OEMCryptoClientTest, VersionNumber) {
const std::string log_message =
"OEMCrypto unit tests for API 16.3. Tests last updated 2020-06-01";
@@ -186,14 +241,18 @@ TEST_F(OEMCryptoClientTest, VersionNumber) {
ASSERT_LE(version, kCurrentAPI);
}
// The resource rating is a number from 1 to 4. The first three levels were
// initially defined in API 15 and they were expanded in API 16.
/**
* The resource rating is a number from 1 to 4. The first three levels
* were initially defined in API 15 and they were expanded in API 16.
*/
TEST_F(OEMCryptoClientTest, ResourceRatingAPI15) {
ASSERT_GE(OEMCrypto_ResourceRatingTier(), 1u);
ASSERT_LE(OEMCrypto_ResourceRatingTier(), 4u);
}
// OEMCrypto must declare what type of provisioning scheme it uses.
/**
* OEMCrypto must declare what type of provisioning scheme it uses.
*/
TEST_F(OEMCryptoClientTest, ProvisioningDeclaredAPI12) {
OEMCrypto_ProvisioningMethod provisioning_method =
OEMCrypto_GetProvisioningMethod();
@@ -228,10 +287,10 @@ TEST_F(OEMCryptoClientTest, CheckHDCPCapabilityAPI09) {
OEMCrypto_HDCP_Capability current, maximum;
sts = OEMCrypto_GetHDCPCapability(&current, &maximum);
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
printf(" Current HDCP Capability: 0x%02x = %s.\n", current,
HDCPCapabilityAsString(current));
printf(" Maximum HDCP Capability: 0x%02x = %s.\n", maximum,
HDCPCapabilityAsString(maximum));
printf(" Current HDCP Capability: 0x%02x = %s.\n",
static_cast<unsigned int>(current), HDCPCapabilityAsString(current));
printf(" Maximum HDCP Capability: 0x%02x = %s.\n",
static_cast<unsigned int>(maximum), HDCPCapabilityAsString(maximum));
}
TEST_F(OEMCryptoClientTest, CheckSRMCapabilityV13) {
@@ -285,6 +344,15 @@ TEST_F(OEMCryptoClientTest, NormalInitTermination) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize());
}
// Test that set sandbox doesn't crash for a large sandbox id leangth.
TEST_F(OEMCryptoClientTest, OEMCryptoMemorySetSandboxForLargeSandboxIdLength) {
auto oemcrypto_function = [](size_t buffer_length) {
vector<uint8_t> buffer(buffer_length);
return OEMCrypto_SetSandbox(buffer.data(), buffer.size());
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus);
}
//
// Session Tests
//
@@ -375,6 +443,17 @@ TEST_F(OEMCryptoClientTest, GetRandomLargeBuffer) {
ASSERT_LE(count, 3); // P(count > 3) = 1/256^3 = 6e-8.
}
// Verify that GetRandom doesn't crash for large input lengths.
TEST_F(OEMCryptoClientTest, OEMCryptoMemoryGetRandomForLargeBuffer) {
auto oemcrypto_function = [](size_t buffer_length) {
vector<uint8_t> buffer(buffer_length);
// TODO(ellurubharath, fredgc): Need to re-evaluate this on a real device
// as GetRandom can be slow.
return OEMCrypto_GetRandom(buffer.data(), buffer.size());
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus);
}
TEST_F(OEMCryptoClientTest, GenerateNonce) {
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
@@ -532,6 +611,33 @@ class OEMCryptoKeyboxTest : public OEMCryptoClientTest {
}
};
// Test that OEMCrypto_InstallKeyboxOrOEMCert doesn't crash for large keybox.
TEST_F(OEMCryptoKeyboxTest, OEMCryptoMemoryInstallKeyboxForHugeKeyboxBuffer) {
auto f = [](size_t keybox_length) {
vector<uint8_t> keybox(keybox_length);
memcpy(keybox.data(), &kTestKeybox, sizeof(kTestKeybox));
return OEMCrypto_InstallKeyboxOrOEMCert(keybox.data(), keybox.size());
};
// Starting at sizeof(kTestKeybox) as we are copying valid keybox
// at beginning of generated buffers.
TestHugeLengthDoesNotCrashAPI(f, sizeof(kTestKeybox), kHugeInputBufferLength,
kCheckStatus);
}
// This test verifies that load test key box doesn't crash for large
// buffer length.
TEST_F(OEMCryptoKeyboxTest, OEMCryptoMemoryLoadTestKeyBoxForHugeKeyboxBuffer) {
auto f = [](size_t keybox_length) {
vector<uint8_t> keybox(keybox_length);
memcpy(keybox.data(), &kTestKeybox, sizeof(kTestKeybox));
return OEMCrypto_LoadTestKeybox(keybox.data(), keybox.size());
};
// Starting at sizeof(kTestKeybox) as we are copying valid keybox
// at beginning of generated buffers.
TestHugeLengthDoesNotCrashAPI(f, sizeof(kTestKeybox), kHugeInputBufferLength,
kCheckStatus);
}
// This test is used to print the device ID to stdout.
TEST_F(OEMCryptoKeyboxTest, NormalGetDeviceId) {
OEMCryptoResult sts;
@@ -543,6 +649,15 @@ TEST_F(OEMCryptoKeyboxTest, NormalGetDeviceId) {
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
}
TEST_F(OEMCryptoKeyboxTest, OEMCryptoMemoryGetDeviceIdForHugeIdLength) {
auto oemcrypto_function = [](size_t input_length) {
size_t device_id_length = input_length;
vector<uint8_t> device_id(device_id_length);
return OEMCrypto_GetDeviceID(device_id.data(), &device_id_length);
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus);
}
TEST_F(OEMCryptoKeyboxTest, GetDeviceIdShortBuffer) {
OEMCryptoResult sts;
uint8_t dev_id[128];
@@ -570,11 +685,20 @@ TEST_F(OEMCryptoKeyboxTest, NormalGetKeyData) {
sts = OEMCrypto_GetKeyData(key_data, &key_data_len);
uint32_t* data = reinterpret_cast<uint32_t*>(key_data);
printf(" NormalGetKeyData: system_id = %d = 0x%04X, version=%d\n",
printf(" NormalGetKeyData: system_id = %u = 0x%04X, version=%u\n",
htonl(data[1]), htonl(data[1]), htonl(data[0]));
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
}
TEST_F(OEMCryptoKeyboxTest, OEMCryptoMemoryGetKeyIdForLargeIdLength) {
auto oemcrypto_function = [](size_t input_length) {
size_t key_data_length = input_length;
vector<uint8_t> key_data(key_data_length);
return OEMCrypto_GetKeyData(key_data.data(), &key_data_length);
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus);
}
TEST_F(OEMCryptoKeyboxTest, GetKeyDataNullPointer) {
OEMCryptoResult sts;
uint8_t key_data[256];
@@ -606,6 +730,42 @@ TEST_F(OEMCryptoKeyboxTest, GenerateDerivedKeysFromKeyboxLargeBuffer) {
enc_context.data(), enc_context.size()));
}
TEST_F(OEMCryptoKeyboxTest,
OEMCryptoMemoryGenerateDerivedKeysForLargeMacContextLength) {
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
vector<uint8_t> mac_context;
vector<uint8_t> enc_context;
s.FillDefaultContext(&mac_context, &enc_context);
auto oemcrypto_function = [&s, &mac_context,
&enc_context](size_t buffer_length) {
mac_context.resize(buffer_length);
return OEMCrypto_GenerateDerivedKeys(s.session_id(), mac_context.data(),
mac_context.size(), enc_context.data(),
enc_context.size());
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus);
}
TEST_F(OEMCryptoKeyboxTest,
OEMCryptoMemoryGenerateDerivedKeysForLargeEncContextLength) {
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
vector<uint8_t> mac_context;
vector<uint8_t> enc_context;
s.FillDefaultContext(&mac_context, &enc_context);
auto oemcrypto_function = [&s, &mac_context,
&enc_context](size_t buffer_length) {
enc_context.resize(buffer_length);
return OEMCrypto_GenerateDerivedKeys(s.session_id(), mac_context.data(),
mac_context.size(), enc_context.data(),
enc_context.size());
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus);
}
// This class is for tests that have an OEM Certificate instead of a keybox.
class OEMCryptoProv30Test : public OEMCryptoClientTest {};
@@ -644,6 +804,11 @@ TEST_F(OEMCryptoProv30Test, OEMCertValid) {
ASSERT_NO_FATAL_FAILURE(s.LoadOEMCert(kVerify)); // Load and verify.
}
/// @}
/// @addtogroup license
/// @{
// This verifies that the OEM Certificate cannot be used for other RSA padding
// schemes. Those schemes should only be used by cast receiver certificates.
TEST_F(OEMCryptoProv30Test, OEMCertForbiddenPaddingScheme) {
@@ -711,6 +876,27 @@ TEST_F(OEMCryptoProv30Test, GetCertOnlyAPI16) {
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse());
}
TEST_F(OEMCryptoProv30Test, OEMCryptoMemoryGetOEMPublicCertForLargeCertLength) {
if (wrapped_rsa_key_.size() == 0) {
// If we don't have a wrapped key yet, create one.
// This wrapped key will be shared by all sessions in the test.
ASSERT_NO_FATAL_FAILURE(CreateWrappedRSAKey());
}
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
// Install the DRM Cert's RSA key.
ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_));
ASSERT_NO_FATAL_FAILURE(s.PreparePublicKey());
auto oemcrypto_function = [](size_t input_length) {
size_t public_cert_length = input_length;
vector<uint8_t> public_cert(public_cert_length);
return OEMCrypto_GetOEMPublicCertificate(public_cert.data(),
&public_cert_length);
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus);
}
//
// AddKey Tests
//
@@ -836,6 +1022,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyNoNonce) {
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(true, OEMCrypto_SUCCESS));
}
// Verify that a preloaded license may be loaded without first signing the
@@ -849,6 +1036,8 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithNoRequest) {
license_messages_.set_api_version(license_api_version_);
}
license_messages_.set_control(0);
// Notice that we do not call SignAndVerifyRequest -- we do not need a request
// in order to generate a response for a preloaded license.
// The test code uses the core request to create the core response.
license_messages_.core_request().api_major_version = ODK_MAJOR_VERSION;
license_messages_.core_request().api_minor_version = ODK_MINOR_VERSION;
@@ -861,6 +1050,34 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithNoRequest) {
ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session2));
ASSERT_NO_FATAL_FAILURE(session2.GenerateDerivedKeysFromSessionKey());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse(&session2));
ASSERT_NO_FATAL_FAILURE(session2.TestDecryptCTR(true, OEMCrypto_SUCCESS));
}
// Verify that a license may be reloaded without a nonce, but with a nonzero
// rental duration. In order to start the rental clock, we sign a dummy license
// instead.
TEST_P(OEMCryptoLicenseTest, LoadKeyWithNoRequestRentalDuration) {
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
license_messages_.set_control(0);
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
// It is not recommended for a license without a nonce to have a nonzero
// rental duration. But there are content providers that have licenses with
// this policy.
license_messages_.core_response().timer_limits.rental_duration_seconds =
kDuration;
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
// Load license in a different session, which did not create the request.
Session session2;
ASSERT_NO_FATAL_FAILURE(session2.open());
ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session2));
// However, in order to start the rental clock, we have to sign something. So
// we will sign a dummy license request.
LicenseRoundTrip dummy_license(&session2);
ASSERT_NO_FATAL_FAILURE(dummy_license.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(session2.GenerateDerivedKeysFromSessionKey());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse(&session2));
ASSERT_NO_FATAL_FAILURE(session2.TestDecryptCTR(true, OEMCrypto_SUCCESS));
}
// Verify that a license may be loaded with a nonce.
@@ -870,6 +1087,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonce) {
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(true, OEMCrypto_SUCCESS));
}
// Verify that a second license may not be loaded in a session.
@@ -1336,6 +1554,10 @@ TEST_F(OEMCryptoLicenseTestAPI16, BadCoreHashAPI16) {
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
}
/// @}
/// @addtogroup decrypt
/// @{
// LoadKeys should fail if we try to load keys with no keys.
TEST_P(OEMCryptoLicenseTest, LoadKeyNoKeys) {
@@ -1520,6 +1742,58 @@ TEST_P(OEMCryptoLicenseTest, QueryKeyControl) {
strlen(key_id), reinterpret_cast<uint8_t*>(&block), &size));
}
// Test OEMCrypto_QueryKeyControl doesn't crash for huge key_id_length.
TEST_F(OEMCryptoLicenseTestAPI16,
OEMCryptoMemoryQueryKeyControlForHugeKeyIdLength) {
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
OEMCrypto_SESSION session_id = session_.session_id();
vector<uint8_t> valid_key_id(
license_messages_.response_data().keys[0].key_id,
license_messages_.response_data().keys[0].key_id + kTestKeyIdMaxLength);
auto oemcrypto_function = [&session_id,
&valid_key_id](size_t additional_key_id_length) {
vector<uint8_t> key_id(valid_key_id);
key_id.resize(valid_key_id.size() + additional_key_id_length);
KeyControlBlock block;
size_t size = sizeof(block);
return OEMCrypto_QueryKeyControl(session_id, key_id.data(), key_id.size(),
reinterpret_cast<uint8_t*>(&block), &size);
};
// We do not want to stop as soon as API results in an error as it would
// return error on first iteration as key_id is invalid.
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus);
}
// Test OEMCrypto_QueryKeyControl doesn't crash for huge key_control_block
// length.
TEST_F(OEMCryptoLicenseTestAPI16,
OEMCryptoMemoryQueryKeyControlForHugeKeyControlBlockLength) {
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
OEMCrypto_SESSION session_id = session_.session_id();
uint8_t* key_id = license_messages_.response_data().keys[0].key_id;
size_t key_id_length =
license_messages_.response_data().keys[0].key_id_length;
auto oemcrypto_function = [&session_id, &key_id,
&key_id_length](size_t buffer_length) {
size_t key_control_block_length = buffer_length;
vector<uint8_t> key_control_block(key_control_block_length);
return OEMCrypto_QueryKeyControl(session_id, key_id, key_id_length,
key_control_block.data(),
&key_control_block_length);
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus);
}
// If the device says it supports anti-rollback in the hardware, then it should
// accept a key control block with the anti-rollback hardware bit set.
// Otherwise, it should reject that key control block.
@@ -1718,6 +1992,11 @@ TEST_P(OEMCryptoSessionTestDecryptWithHDCP, DecryptAPI09) {
INSTANTIATE_TEST_CASE_P(TestHDCP, OEMCryptoSessionTestDecryptWithHDCP,
Range(1, 6));
/// @}
/// @addtogroup renewal
/// @{
//
// Load, Refresh Keys Test
//
@@ -1903,6 +2182,30 @@ TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) {
OEMCrypto_GetHashErrorCode(session_.session_id(), &frame_number));
}
// This test verifies that OEMCrypto_SetDecryptHash doesn't crash for a very
// large hash buffer.
TEST_F(OEMCryptoLicenseTestAPI16,
OEMCryptoMemoryDecryptHashForLargeHashBuffer) {
uint32_t session_id = session_.session_id();
auto f = [session_id](size_t hash_length) {
uint32_t frame_number = 1;
vector<uint8_t> hash_buffer(hash_length);
return OEMCrypto_SetDecryptHash(session_id, frame_number,
hash_buffer.data(), hash_buffer.size());
};
TestHugeLengthDoesNotCrashAPI(f, sizeof(uint32_t), kHugeInputBufferLength,
kCheckStatus);
}
// This test verifies OEMCrypto_SetDecryptHash for out of range frame number.
TEST_P(OEMCryptoLicenseTest, DecryptHashForOutOfRangeFrameNumber) {
uint32_t frame_number = 40;
uint32_t hash = 42;
ASSERT_NO_FATAL_FAILURE(OEMCrypto_SetDecryptHash(
session_.session_id(), frame_number,
reinterpret_cast<const uint8_t*>(&hash), sizeof(hash)));
}
//
// Decrypt Tests -- these test Decrypt CTR mode only.
//
@@ -2582,18 +2885,6 @@ TEST_P(OEMCryptoLicenseTest, DecryptSecureToClear) {
session_.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE));
}
// If analog is forbidden, then decrypt to a clear buffer should be forbidden.
TEST_P(OEMCryptoLicenseTest, DecryptNoAnalogToClearAPI13) {
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
license_messages_.set_control(wvoec::kControlDisableAnalogOutput);
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
ASSERT_NO_FATAL_FAILURE(
session_.TestDecryptCTR(true, OEMCrypto_ERROR_ANALOG_OUTPUT));
}
// Test that key duration is honored.
TEST_P(OEMCryptoLicenseTest, KeyDuration) {
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
@@ -3087,6 +3378,50 @@ TEST_F(OEMCryptoUsesCertificate, GenerateDerivedKeysLargeBuffer) {
enc_context.data(), enc_context.size()));
}
TEST_F(OEMCryptoUsesCertificate,
OEMCryptoMemoryDeriveKeysFromSessionKeyForHugeMacContext) {
vector<uint8_t> session_key;
vector<uint8_t> enc_session_key;
ASSERT_NO_FATAL_FAILURE(session_.PreparePublicKey(encoded_rsa_key_.data(),
encoded_rsa_key_.size()));
ASSERT_TRUE(session_.GenerateRSASessionKey(&session_key, &enc_session_key));
vector<uint8_t> mac_context;
vector<uint8_t> enc_context;
session_.FillDefaultContext(&mac_context, &enc_context);
OEMCrypto_SESSION session_id = session_.session_id();
auto oemcrypto_function = [&session_id, &enc_context, &mac_context,
&enc_session_key](size_t buffer_length) {
mac_context.resize(buffer_length);
return OEMCrypto_DeriveKeysFromSessionKey(
session_id, enc_session_key.data(), enc_session_key.size(),
mac_context.data(), mac_context.size(), enc_context.data(),
enc_context.size());
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus);
}
TEST_F(OEMCryptoUsesCertificate,
OEMCryptoMemoryDeriveKeysFromSessionKeyForLargeEncContext) {
vector<uint8_t> session_key;
vector<uint8_t> enc_session_key;
ASSERT_NO_FATAL_FAILURE(session_.PreparePublicKey(encoded_rsa_key_.data(),
encoded_rsa_key_.size()));
ASSERT_TRUE(session_.GenerateRSASessionKey(&session_key, &enc_session_key));
vector<uint8_t> mac_context;
vector<uint8_t> enc_context;
session_.FillDefaultContext(&mac_context, &enc_context);
OEMCrypto_SESSION session_id = session_.session_id();
auto oemcrypto_function = [&session_id, &enc_context, &mac_context,
&enc_session_key](size_t buffer_length) {
enc_context.resize(buffer_length);
return OEMCrypto_DeriveKeysFromSessionKey(
session_id, enc_session_key.data(), enc_session_key.size(),
mac_context.data(), mac_context.size(), enc_context.data(),
enc_context.size());
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus);
}
// This test attempts to use alternate algorithms for loaded device certs.
class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate {
protected:
@@ -4369,6 +4704,17 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptSameBufferAPI12) {
ASSERT_EQ(expected_encrypted, buffer);
}
TEST_P(OEMCryptoGenericCryptoTest, OEMCryptoMemorySelectKeyForHugeKeyIdLength) {
EncryptAndLoadKeys();
OEMCrypto_SESSION session_id = session_.session_id();
auto oemcrypto_function = [session_id](size_t key_id_length) {
vector<uint8_t> key_id(key_id_length);
return OEMCrypto_SelectKey(session_id, key_id.data(), key_id.size(),
OEMCrypto_CipherMode_CTR);
};
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus);
}
// Test Generic_Decrypt works correctly.
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecrypt) {
EncryptAndLoadKeys();
@@ -4833,6 +5179,11 @@ INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoGenericCryptoTest,
INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoGenericCryptoKeyIdLengthTest,
Range<uint32_t>(kCurrentAPI - 1, kCurrentAPI + 1));
/// @}
/// @addtogroup usage_table
/// @{
// Test usage table functionality.
class LicenseWithUsageEntry {
public:
@@ -6218,9 +6569,7 @@ TEST_P(OEMCryptoUsageTableTest, VerifyUsageTimes) {
// on a device that allows an application to set the clock.
class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest {
public:
void SetUp() override {
OEMCryptoUsageTableTest::SetUp();
}
void SetUp() override { OEMCryptoUsageTableTest::SetUp(); }
void TearDown() override {
wvcdm::TestSleep::ResetRollback();
@@ -6364,4 +6713,5 @@ INSTANTIATE_TEST_CASE_P(TestAPI16, OEMCryptoUsageTableDefragTest,
INSTANTIATE_TEST_CASE_P(TestAPI16, OEMCryptoUsageTableTestWallClock,
Values<uint32_t>(kCurrentAPI));
/// @}
} // namespace wvoec

View File

@@ -4,6 +4,7 @@
#include "OEMCryptoCENC.h"
#include "log.h"
#include "oec_device_features.h"
#include "oemcrypto_corpus_generator_helper.h"
#include "test_sleep.h"
static void acknowledge_cast() {
@@ -23,6 +24,9 @@ int main(int argc, char** argv) {
// Skip the first element, which is the program name.
const std::vector<std::string> args(argv + 1, argv + argc);
for (const std::string& arg : args) {
if (arg == "--generate_corpus") {
wvoec::SetGenerateCorpus(true);
}
if (arg == "--verbose" || arg == "-v") {
++verbosity;
} else if (arg == "--cast") {