Source release 19.1.0

This commit is contained in:
Matt Feddersen
2024-03-28 19:21:54 -07:00
parent 28ec8548c6
commit b8bdfccebe
182 changed files with 10645 additions and 2040 deletions

View File

@@ -370,8 +370,58 @@ OEMCryptoResult _oecc140(void);
// OEMCrypto_FactoryInstallBCCSignature defined in v18.3
OEMCryptoResult _oecc142(const uint8_t* signature, size_t signature_length);
// OEMCrypto_GetEmbeddedDrmCertificate defined in v18.5
OEMCryptoResult _oecc143(uint8_t* public_cert, size_t* public_cert_length);
// OEMCrypto_PrepAndSignReleaseRequest defined in v19.0
OEMCryptoResult _oecc147(OEMCrypto_SESSION session, uint8_t* message,
size_t message_length, size_t* core_message_size,
uint8_t* signature, size_t* signature_length);
// OEMCrypto_UseSecondaryKey defined in v18.5
OEMCryptoResult _oecc144(OEMCrypto_SESSION session_id, bool dual_key);
// OEMCrypto_LoadLicense defined in v19.0
OEMCryptoResult _oecc144(OEMCrypto_SESSION session, const uint8_t* context,
size_t context_length, const uint8_t* derivation_key,
size_t derivation_key_length, const uint8_t* message,
size_t message_length, size_t core_message_length,
const uint8_t* signature, size_t signature_length);
// OEMCrypto_LoadRelease defined in v19.0
OEMCryptoResult _oecc150(OEMCrypto_SESSION session, const uint8_t* message,
size_t message_length, size_t core_message_length,
const uint8_t* signature, size_t signature_length);
// OEMCrypto_GetBCCType defined in v19.0
OEMCryptoResult _oecc149(OEMCrypto_BCCType* bcc_type);
// OEMCrypto_LoadProvisioning defined in v19.0
OEMCryptoResult _oecc145(OEMCrypto_SESSION session,
const uint8_t* provision_request,
size_t provision_request_length,
const uint8_t* message, size_t message_length,
size_t core_message_length, const uint8_t* signature,
size_t signature_length, uint8_t* wrapped_private_key,
size_t* wrapped_private_key_length);
// OEMCrypto_LoadProvisioningCast defined in v19.0
OEMCryptoResult _oecc146(OEMCrypto_SESSION session,
const uint8_t* derivation_key,
size_t derivation_key_length,
const uint8_t* provision_request,
size_t provision_request_length,
const uint8_t* message, size_t message_length,
size_t core_message_length, const uint8_t* signature,
size_t signature_length, uint8_t* wrapped_private_key,
size_t* wrapped_private_key_length);
// OEMCrypto_GetUsageEntryInfo defined in v19.0
OEMCryptoResult _oecc148(OEMCrypto_SESSION session,
OEMCrypto_Usage_Entry_Status* status,
int64_t* seconds_since_license_received,
int64_t* seconds_since_first_decrypt);
// OEMCrypto_SetDecryptHash defined in v19.0
OEMCryptoResult _oecc143(OEMCrypto_SESSION session, uint32_t frame_number,
uint32_t crc32);
// OEMCrypto_GetEmbeddedDrmCertificate defined in v19.1
OEMCryptoResult _oecc151(uint8_t* public_cert, size_t* public_cert_length);
// OEMCrypto_UseSecondaryKey defined in v19.1
OEMCryptoResult _oecc152(OEMCrypto_SESSION session_id, bool dual_key);

View File

@@ -1,229 +1,140 @@
# OEMCRYPTO Fuzzing
# OEMCrypto fuzzing
Refer to [Setting up Clusterfuzz](build_clusterfuzz.md) if you are interested
in setting up a local instance of cluster fuzz to run fuzzing on your own
OEMCrypto implementations on linux.
ClusterFuzz and Google Cloud infrastructure continuously runs OEMCrypto fuzz
tests and reports crashes. To create a new automated fuzzing setup, refer to
[*ClusterFuzz setup*][1].
## Objective
## Run fuzz tests locally
* 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=2022-07-11&date_end=2022-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
1. Build the fuzz tests:
```shell
$ sudo apt-get install gyp ninja-build
$ cd <cdm_repo_path>
$ oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests
```
* download cdm source code (including ODK & OEMCrypto unit tests):
2. Run the fuzz test:
```shell
$ git clone sso://widevine-internal/cdm
$ out/Default/<fuzz_test> [<corpus_dir>]
```
* Build OEMCrypto unit tests and run with --generate_corpus flag to generate
corpus files:
The corpus directory is optional and can either be a seed corpus from the
`corpus` subdirectory or be an empty directory. The corpus will be extended
with new inputs while the fuzz test is running.
## Triage crashes
To reproduce a crash locally for debugging:
1. Download the minimized testcase from the ClusterFuzz report.
2. Build the fuzz tests:
```shell
$ cd /path/to/cdm/repo
$ export CDM_DIR=/path/to/cdm/repo
$ oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests
```
3. Debug the crash:
```shell
$ gdb --args <fuzz_test_path> -timeout=0 <testcase_path>
```
Example after substituting fuzz test and test case paths:
```shell
$ gdb --args out/Default/oemcrypto_opk_decrypt_cenc_fuzz -timeout=0 \
clusterfuzz-testcase-minimized-oemcrypto_v17_opk_decrypt_cenc_fuzz-6727459932078080
```
4. If reproducing the crash is unsuccessful, download the unminimized testcase
from the ClusterFuzz report and try again. If still unsuccessful, this may
indicate there is a persistent state issue with the fuzz test.
Once the root cause of the crash is identified, its severity and complexity
should be assessed. The [*SEI CERT C Coding Standard*][2] is a good resource for
risk assessment. The ClusterFuzz report will also provide input in the Security
field. For complex fixes with a longer timeline, ClusterFuzz may report
duplicate crashes with the same root cause.
## Write fuzz tests
While fuzzing has random elements, input data mutations are heavily influenced
by coverage feedback. Since discovering new control flow edges is a time
consuming process, input bytes should map to control flow edges in a simple,
predictable way. [`FuzzedDataProvider`][3], a class supplied with LLVMs
libFuzzer, can be used to easily split input data:
```cpp
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
FuzzedDataProvider fuzzed_data(data, size);
// One bit of input data maps to this control flow edge:
if (fuzzed_data.ConsumeBool()) {
// ...
}
// ...
}
```
Fuzzing API methods with complex, structured input, may benefit from a seed
corpus containing a representative set of starting inputs. Unfortunately,
`FuzzedDataProvider` is not suitable for fuzz tests utilizing a seed corpus
since there is no equivalent serialization functionality for generating the
corpus. OEMCrypto fuzz tests have previously used struct-based serialization,
but this is no longer recommended due to portability issues. Protocol Buffers or
another portable serialization format should be considered instead.
Fuzz tests must be deterministic to reproduce and debug a crash. A common
pitfall is not resetting the OEMCrypto API state between calls to
`LLVMFuzzerTestOneInput`. Fully terminating OEMCrypto between inputs is
preferred, but in some cases, it may be necessary to implement careful
optimizations to achieve acceptable performance. Candidates for optimization
typically have less than 1000 executions per second (exec/s).
`LLVMFuzzerInitialize` can be used for global initialization, but there is no
corresponding termination method.
A good starting example is [`oemcrypto_install_oem_private_key_fuzz.cc`][4].
Targets should be added to `oemcrypto_opk_fuzztests.gyp` and, if the fuzz test
applies to partner OEMCrypto implementations, `partner_oemcrypto_fuzztests.gyp`.
The infrastructure expects that the target name starts with *oemcrypto* and ends
with *fuzz*.
For additional information about writing fuzz tests, see
[*What makes a good fuzz target*][5].
## Generate corpus with OEMCrypto unit tests
1. Build the unit tests:
```shell
$ cd <cdm_repo_path>
$ export CDM_DIR=${PWD}
$ export PATH_TO_CDM_DIR=..
$ gyp --format=ninja --depth=$(pwd) oemcrypto/oemcrypto_unittests.gyp
$ ninja -C out/Default/
$ mkdir oemcrypto/test/fuzz_tests/corpus/<fuzzername>_seed_corpus
# Generate corpus by excluding buffer overflow tests.
$ ./out/Default/oemcrypto_unittests --generate_corpus \
--gtest_filter=-"*Huge*"
$ gyp --format=ninja --depth=${PWD} oemcrypto/oemcrypto_unittests.gyp
$ ninja -C out/Default
```
* There can be lot of duplicate corpus files that are generated from unit
tests. We can minimize the corpus files to only a subset of files that
cover unique paths within the API when run using fuzzer. Run following
command to minimize corpus.
2. Run the unit tests with the `--generate_corpus` flag:
```shell
$ cd /path/to/cdm/repo
# build fuzzer binaries
$ ./oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests
$ mkdir oemcrypto/test/fuzz_tests/corpus/<fuzz_test>_seed_corpus
$ out/Default/oemcrypto_unittests --generate_corpus --gtest_filter='-*Huge*'
```
3. The unit tests can generate many duplicate corpus files. To minimize the
corpus to only the subset of inputs that cover unique paths within the API:
```shell
$ oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests
$ mkdir /tmp/minimized_corpus
# minimize corpus
$ ./out/Default/<fuzz_target_binary> -merge=1 /tmp/minimized_corpus \
<FULL_CORPUS_DIR>
$ out/Default/<fuzz_test> -merge=1 /tmp/minimized_corpus <full_corpus_dir>
```
* To avoid uploading huge binary files to git repository, the minimized 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 following commands. The build
script builds fuzz binaries for opk implementation.
```shell
$ cd PATH_TO_CDM_DIR
$ ./oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests
$ 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 starts with oemcrypto and ends
with fuzz.cc(GCB build script for oemcrypto fuzzers expects the format).
* 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:
* As long as a new fuzz script is added which starts with oemcrypto and ends
with fuzz, the build command can be added to build_oemcrypto_fuzztests.
GCB script uses build_oemcrypto_fuzztests script to build fuzz binaries
and make them available for clusterfuzz to run continuously.
* If the new fuzzer cannot follow the naming convention OR GCB script needs
to be updated for any other reason, refer to [this section](https://docs.google.com/document/d/1mdSV2irJZz5Y9uYb5DmSIddBjrAIZU9q8G5Q_BGpA4I/edit#heading=h.bu9yfftdonkg)
section.
## 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.
[1]: clusterfuzz_setup.md
[2]: https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard
[3]: https://github.com/llvm/llvm-project/blob/main/compiler-rt/include/fuzzer/FuzzedDataProvider.h
[4]: oemcrypto_install_oem_private_key_fuzz.cc
[5]: https://github.com/google/fuzzing/blob/master/docs/good-fuzz-target.md

View File

@@ -0,0 +1,171 @@
# ClusterFuzz setup
[ClusterFuzz][1]
## Objective
* Run fuzzing on OEMCrypto public APIs on Linux by building open sourced
ClusterFuzz source code in order to find security vulnerabilities.
* Partners who implement OEMCrypto can follow these instructions to build
ClusterFuzz, the fuzzing framework and run fuzzing using fuzzer scripts
provided by the Widevine team at Google.
## Glossary
* Fuzzing - Fuzzing is a methodology where random, interesting, unexpected
inputs are fed to APIs in order to crash those, thereby catching any
security vulnerabilities with the code.
* Fuzzing engines - [libFuzzer][4], AFL, Honggfuzz, etc. are the actual
fuzzing engines that get the coverage information from API, use that to
generate more interesting inputs which can be passed to fuzzer.
* Seed corpus - Fuzzing engine trying to generate interesting inputs from an
empty file is not efficient. Seed corpus is the initial input that a fuzzer
can accept and call the API with that. Fuzzing engine can then mutate this
seed corpus to generate more inputs to fuzzer.
* ClusterFuzz - ClusterFuzz is a scalable fuzzing infrastructure that finds
security and stability issues in software. Google uses ClusterFuzz to fuzz
all Google products. ClusterFuzz provides us with the capability, tools to
upload fuzz binaries and make use of the fuzzing engines to run fuzzing,
find crashes and organizes the information. ClusterFuzz framework is open
sourced, the source code can be downloaded and framework can be built
locally or by using Google Cloud.
* Fuzzing output - Fuzzing is used to pass random inputs to API in order to
ensure that API is crash resistant. We are not testing functionality via
fuzzing. Fuzz scripts run continuously until they find a crash with the API
under test.
## Build fuzz scripts
This section outlines the steps to build fuzz binaries that can be run
continuously using ClusterFuzz.
> **Note:** All the directories mentioned below are relative to cdm repository
> root directory.
1. Fuzz scripts for OEMCrypto APIs are provided by the Widevine team at Google
located under `oemcrypto/test/fuzz_tests` directory.
> **Note:** Prerequisites to run the following step are [here][10]. We also
> need to install Ninja.
2. Build a static library of your OEMCrypto implementation.
* Compile and link your OEMCrypto implementation source with
`-fsanitize=address,fuzzer` flag as per these [instructions][9] when
building a static library.
* Run `./oemcrypto/test/fuzz_tests/build_partner_oemcrypto_fuzztests
<oemcrypto_static_library_path>` script from cdm repository root
directory.
* This will generate fuzz binaries under the `out/Default` directory.
> **Note:** Alternatively, you can use your own build systems, for which you
> will need to define your own build files with the OEMCrypto fuzz source
> files included. You can find the the fuzz source files in
> `oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gyp` and
> `oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gypi`.
3. Seed corpus for each fuzz script can be found under
`oemcrypto/test/fuzz_tests/corpus` directory. Some fuzzers are simple and do
not have seed corpus associated with them.
4. Create a zip file `oemcrypto_fuzzers_yyyymmddhhmmss.zip` with fuzz binaries
and respective seed corpus zip files. Structure of a sample zip file with
fuzzer binaries and seed corpus would look like this:
```
* fuzzerA
* fuzzerA_seed_corpus.zip
* fuzzerB
* fuzzerB_seed_corpus.zip
* fuzzerC (fuzzerC doesn't have seed corpus associated with it)
```
## Build ClusterFuzz
OEMCrypto implementation can be fuzzed by building ClusterFuzz code, which is
open source, and using it to run fuzzing. Use a Linux VM to build ClusterFuzz.
> **Note:** You may see some issues with Python modules missing. Please install
> those modules if you see errors. If you have multiple versions of Python on
> the VM, then use `python<version> -m pipenv shell` when you are at [this][3]
> step.
Follow these [instructions][2] in order to download the ClusterFuzz repository,
build it locally or create a continuous fuzz infrastructure setup using Google
Cloud.
## Run fuzzers on local ClusterFuzz instance
If you prefer to run fuzzing on a local machine instead of having a production
setup using Google Cloud, then follow these [instructions][5] to add a job to
the local ClusterFuzz instance.
> **Note:** Job name should have a fuzzing engine and sanitizer as part of it. A
> libFuzzer and AddressSanitizer job should have libfuzzer_asan in the job name.
* Create a job e:g:`libfuzzer_asan_oemcrypto` and upload previously created
`oemcrypto_fuzzers_yyyymmddhhmmss.zip` as a custom build. Future uploads of
zip file should have a name greater than current name. Following the above
naming standard will ensure zip file names are always in ascending order.
* Once the job is added and ClusterFuzz bot is running, fuzzing should be up
and running. Results can be monitored as mentioned [here][6].
* On a local ClusterFuzz instance, only one fuzzer is being fuzzed at a time.
> **Note:** Fuzzing is time consuming. Finding issues as well as ClusterFuzz
> regressing and fixing the issues can take time. We need fuzzing to run at
> least for a couple of weeks to have good coverage.
## Find fuzz crashes
* Once the ClusterFuzz finds an issue, it logs crash information such as the
build, test case and stack trace for the crash.
* Test cases tab should show the fuzz crash and test case that caused the
crash. Run `./fuzz_binary <test_case>` in order to debug the crash locally.
More information about different types of logs is below:
* [Bot logs][7] will show information related to fuzzing, number of crashes
that a particular fuzzer finds, number of new crashes, number of known
crashes etc.
* [Local GCS][8] in your ClusterFuzz checkout folder will store the fuzz
binaries that are being fuzzed, seed corpus etc.
* `local_gcs/test-fuzz-logs-bucket` will store information related to fuzz
crashes if any were found by the fuzzing engine. It will store crash
information categorized by fuzzer and by each day. It will also store test
case that caused the crash.
* `/path/to/my-bot/clusterfuzz/log.txt` will have any log information from
fuzzer script and OEMCrypto implementation.
## Fix issues
1. Once you are able to debug using the crash test case, apply fix to the
implementation, create `oemcrypto_fuzzers_yyyymmddhhmmss.zip` with latest
fuzz binaries.
2. Upload the latest fuzz binary to the fuzz job that was created earlier.
Fuzzer will recognize the fix and mark the crash as fixed in test cases tab
once the regression finishes. You do not need to update crashes as fixed,
ClusterFuzz will do that.
[1]: https://google.github.io/clusterfuzz/
[2]: https://google.github.io/clusterfuzz/getting-started/
[3]: https://google.github.io/clusterfuzz/getting-started/prerequisites/#loading-pipenv
[4]: https://llvm.org/docs/LibFuzzer.html
[5]: https://google.github.io/clusterfuzz/setting-up-fuzzing/libfuzzer-and-afl/
[6]: https://google.github.io/clusterfuzz/setting-up-fuzzing/libfuzzer-and-afl/#checking-results
[7]: https://google.github.io/clusterfuzz/getting-started/local-instance/#viewing-logs
[8]: https://google.github.io/clusterfuzz/getting-started/local-instance/#local-google-cloud-storage
[9]: https://google.github.io/clusterfuzz/setting-up-fuzzing/libfuzzer-and-afl/#libfuzzer
[10]: https://google.github.io/clusterfuzz/setting-up-fuzzing/libfuzzer-and-afl/#prerequisites

View File

@@ -55,15 +55,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
uint32_t* const failed_frame_number =
fuzzed_data.ConsumeBool() ? &failed_frame_number_data : nullptr;
const std::vector<uint8_t> hash =
fuzzed_data.ConsumeRemainingBytes<uint8_t>();
const uint32_t crc32 = fuzzed_data.ConsumeIntegral<uint32_t>();
license_api_fuzz.LoadLicense();
std::vector<uint8_t> key_handle;
wvoec::GetKeyHandleIntoVector(session_id, content_key_id.data(),
content_key_id.size(),
OEMCrypto_CipherMode_CENC, key_handle);
OEMCrypto_SetDecryptHash(session_id, frame_number, hash.data(), hash.size());
OEMCrypto_SetDecryptHash(session_id, frame_number, crc32);
OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(), &sample, 1,
&pattern);
OEMCrypto_GetHashErrorCode(session_id, failed_frame_number);

View File

@@ -63,7 +63,7 @@
'-D_POSIX_C_SOURCE=200809L',
],
'cflags_cc': [
'-std=c++14',
'-std=c++17',
],
'ldflags': [
'-fPIC',

View File

@@ -32,7 +32,7 @@
],
'cflags_cc' : [
'-frtti',
'-std=c++14',
'-std=c++17',
],
'ldflags': [
'-fPIC',

View File

@@ -69,7 +69,7 @@
'-D_POSIX_C_SOURCE=200809L',
],
'cflags_cc': [
'-std=c++14',
'-std=c++17',
'-frtti',
],
'ldflags': [

View File

@@ -0,0 +1,233 @@
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine
// License Agreement.
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "OEMCryptoCENC.h"
#include "string_conversions.h"
namespace {
// Size of a valid keybox.
constexpr size_t kKeyboxSize = 128;
// Size of a valid keybox token section.
constexpr size_t kKeyDataSize = 72;
// Offset of the system ID in key data.
constexpr size_t kSystemIdOffset = 4;
// Offset of the keybox version in key data.
constexpr size_t kVersionOffset = 0;
// == Utils ==
bool IsRegularFile(const std::string& path) {
struct stat st;
if (stat(path.c_str(), &st) != 0) {
if (errno == ENOENT || errno == ENOTDIR) {
std::cerr << "File does not exist: path = " << path << std::endl;
return false;
}
std::cerr << "Failed to call stat: path = " << path;
std::cerr << ", errno = " << errno << std::endl;
return false;
}
if (!S_ISREG(st.st_mode)) {
std::cerr << "Not a regular file: path = " << path << ", mode = ";
std::cerr << std::setfill('0') << std::setw(7) << std::oct << st.st_mode;
std::cerr << std::endl;
return false;
}
return true;
}
void PrintDeviceId(const uint8_t* device_id_u8, size_t device_id_length) {
std::string device_id(reinterpret_cast<const char*>(device_id_u8),
device_id_length);
// Trim null bytes.
while (!device_id.empty() && device_id.back() == '\0') device_id.pop_back();
// Check if empty.
if (device_id.empty()) {
std::cerr << "Device ID was all null bytes: ";
std::cerr << "length = " << device_id_length << std::endl;
std::cout << "device_id = <empty>" << std::endl;
return;
}
// Check if printable.
if (!std::all_of(device_id.begin(), device_id.end(), ::isprint)) {
device_id = wvutil::b2a_hex(device_id);
}
std::cout << "device_id = " << device_id << std::endl;
}
void PrintSystemId(const uint8_t* key_data) {
// Assumes that |key_data| length as already been verified.
const uint32_t* system_id_ptr =
reinterpret_cast<const uint32_t*>(&key_data[kSystemIdOffset]);
const uint32_t system_id = ntohl(*system_id_ptr);
std::cout << "system_id = " << system_id << std::endl;
std::cout << "hex(system_id) = 0x";
std::cout << std::setfill('0') << std::setw(8) << std::hex << system_id;
std::cout << std::endl;
}
void PrintKeyboxVersion(const uint8_t* key_data) {
// Assumes that |key_data| length as already been verified.
const uint32_t* version_ptr =
reinterpret_cast<const uint32_t*>(&key_data[kVersionOffset]);
const uint32_t version = ntohl(*version_ptr);
std::cout << "version = " << version << std::endl;
}
// == Primary ==
bool RetrieveKeybox(const std::string& path, std::vector<uint8_t>* keybox) {
using StreamIter = std::istreambuf_iterator<char>;
using PosType = std::iostream::pos_type;
std::ifstream fin(path, std::ios::in | std::ios::binary);
if (!fin) {
std::cerr << "Failed to open input file: path = " << path << std::endl;
return false;
}
// Verify size.
fin.seekg(0, std::ios::end);
if (!fin) {
std::cerr << "Failed to seek to end of file: path = " << path << std::endl;
return false;
}
const PosType keybox_size_pt = fin.tellg();
if (keybox_size_pt == PosType(-1)) {
std::cerr << "Failed to obtain the file size: path = " << path << std::endl;
return false;
}
const size_t keybox_size = static_cast<size_t>(keybox_size_pt);
fin.seekg(0, std::ios::beg);
if (!fin) {
std::cerr << "Failed to seek to beginning of file: path = ";
std::cerr << path << std::endl;
return false;
}
if (keybox_size != kKeyboxSize) {
std::cerr << "Unexpected keybox size: ";
std::cerr << "size = " << keybox_size;
std::cerr << ", expected = " << kKeyboxSize;
std::cerr << ", path = " << path << std::endl;
return false;
}
// Read keybox data.
keybox->clear();
keybox->reserve(kKeyboxSize);
keybox->assign(StreamIter(fin), StreamIter());
if (keybox->size() != kKeyboxSize) {
std::cerr << "Failed to read entire keybox: ";
std::cerr << "read = " << keybox->size();
std::cerr << ", expected = " << kKeyboxSize;
std::cerr << ", path = " << path << std::endl;
keybox->clear();
return false;
}
return true;
}
bool InstallKeyboxAndPrintInfo(const std::vector<uint8_t>& keybox) {
std::cout << "Install keybox: " << wvutil::b2a_hex(keybox) << std::endl;
// Step 1: Initialize.
OEMCryptoResult result = OEMCrypto_Initialize();
if (result != OEMCrypto_SUCCESS) {
std::cerr << "Failed to initialize: result = " << result << std::endl;
return false;
}
std::cout << "OEMCrypto initialized" << std::endl;
// Step 2: Install keybox.
const OEMCrypto_ProvisioningMethod method = OEMCrypto_GetProvisioningMethod();
if (method != OEMCrypto_Keybox) {
std::cerr << "OEMCrypto is not keybox type: method = ";
std::cerr << method << std::endl;
OEMCrypto_Terminate();
return false;
}
result = OEMCrypto_InstallKeyboxOrOEMCert(keybox.data(), keybox.size());
if (result != OEMCrypto_SUCCESS) {
std::cerr << "Failed to install keybox: result = " << result << std::endl;
OEMCrypto_Terminate();
return false;
}
std::cout << "OEMCrypto keybox installed" << std::endl;
// Step 3: Verify device ID.
uint8_t buffer[128] = {};
memset(buffer, 0, sizeof(buffer));
size_t buffer_length = sizeof(buffer);
result = OEMCrypto_GetDeviceID(buffer, &buffer_length);
if (result != OEMCrypto_SUCCESS) {
std::cerr << "Failed to get device ID: result = " << result << std::endl;
OEMCrypto_Terminate();
return false;
}
PrintDeviceId(buffer, buffer_length);
// Step 4: Verify system ID and keybox version.
memset(buffer, 0, sizeof(buffer));
buffer_length = sizeof(buffer);
result = OEMCrypto_GetKeyData(buffer, &buffer_length);
if (result != OEMCrypto_SUCCESS) {
std::cerr << "Failed to get key data: result = " << result << std::endl;
OEMCrypto_Terminate();
return false;
}
if (buffer_length != kKeyDataSize) {
std::cerr << "Unexpected key data size: ";
std::cerr << "size = " << buffer_length;
std::cerr << ", expected = " << kKeyDataSize << std::endl;
OEMCrypto_Terminate();
return false;
}
PrintSystemId(buffer);
PrintKeyboxVersion(buffer);
// Step 5: Cleanup.
result = OEMCrypto_Terminate();
if (result != OEMCrypto_SUCCESS) {
std::cerr << "Failed to terminate: result = " << result << std::endl;
return false;
}
std::cout << "OEMCrypto terminated" << std::endl;
return true;
}
} // namespace
int main(int argc, char** argv) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <filename>" << std::endl;
return 1;
}
const std::string keybox_path = argv[1];
if (!IsRegularFile(keybox_path)) {
std::cerr << "Bad keybox path: " << keybox_path << std::endl;
return 1;
}
std::vector<uint8_t> keybox;
if (!RetrieveKeybox(keybox_path, &keybox)) {
std::cerr << "Failed to retrieve keybox" << std::endl;
return 1;
}
if (!InstallKeyboxAndPrintInfo(keybox)) {
std::cerr << "Failed to install keybox" << std::endl;
return 1;
}
return 0;
}

View File

@@ -145,28 +145,6 @@ void DeviceFeatures::Initialize() {
initialized_ = true;
}
std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) {
std::string filter = initial_filter;
// clang-format off
if (api_version < 17) FilterOut(&filter, "*API17*");
if (api_version < 18) FilterOut(&filter, "*API18*");
// clang-format on
// Some tests may require root access. If user is not root, filter these tests
// out.
if (!wvutil::TestSleep::CanChangeSystemTime()) {
printf("Filtering out TimeRollbackPrevention.\n");
FilterOut(&filter, "*TimeRollbackPrevention*");
} else {
printf("Can change time. I will run TimeRollbackPrevention.\n");
}
// Performance tests take a long time. Filter them out if they are not
// specifically requested.
if (filter.find("Performance") == std::string::npos) {
FilterOut(&filter, "*Performance*");
}
return filter;
}
void DeviceFeatures::PickDerivedKey() {
switch (provisioning_method) {
case OEMCrypto_OEMCertificate:

View File

@@ -10,7 +10,7 @@
namespace wvoec {
// These tests are designed to work for this version:
constexpr unsigned int kCurrentAPI = 18;
constexpr unsigned int kCurrentAPI = 19;
// The API version when Core Messages were introduced.
constexpr unsigned int kCoreMessagesAPI = 16;
// The API version when we stopped encrypting key control blocks.
@@ -61,11 +61,6 @@ class DeviceFeatures {
void set_cast_receiver(bool is_cast_receiver) {
cast_receiver = is_cast_receiver;
}
// Generate a GTest filter of tests that should not be run. This should be
// called after Initialize. Tests are filtered out based on which features
// are not supported. For example, a device that uses Provisioning 3.0 will
// have all keybox tests filtered out.
std::string RestrictFilter(const std::string& initial_filter);
// Get a list of output types that should be tested.
const std::vector<OutputType>& GetOutputTypes();

View File

@@ -37,6 +37,23 @@ using namespace std;
namespace wvoec {
namespace {
std::vector<uint8_t> CreateContext(const char* prefix,
const std::vector<uint8_t>& context,
uint32_t suffix) {
std::vector<uint8_t> ret;
// +1 to include the null-terminator
ret.insert(ret.end(), prefix, prefix + strlen(prefix) + 1);
ret.insert(ret.end(), context.begin(), context.end());
const uint32_t suffix_net = htonl(suffix);
auto* ptr = reinterpret_cast<const uint8_t*>(&suffix_net);
ret.insert(ret.end(), ptr, ptr + sizeof(suffix_net));
return ret;
}
} // namespace
void Encryptor::set_enc_key(const std::vector<uint8_t>& enc_key) {
enc_key_ = enc_key;
}
@@ -119,8 +136,21 @@ void KeyDeriver::DeriveKey(const uint8_t* key, size_t master_key_size,
// this function, then there is something wrong with the test program and its
// dependency on BoringSSL.
void KeyDeriver::DeriveKeys(const uint8_t* master_key, size_t master_key_size,
const vector<uint8_t>& mac_key_context,
const vector<uint8_t>& enc_key_context) {
const vector<uint8_t>& context) {
// TODO: Use ODK constants instead
DeriveKeys(master_key, master_key_size, context, "AUTHENTICATION",
"ENCRYPTION");
}
void KeyDeriver::DeriveKeys(const uint8_t* master_key, size_t master_key_size,
const vector<uint8_t>& context,
const char* mac_label, const char* enc_label) {
// TODO: Use ODK constants instead
const std::vector<uint8_t> mac_key_context =
CreateContext(mac_label, context, 0x200);
const std::vector<uint8_t> enc_key_context =
CreateContext(enc_label, context, 0x80);
// Generate derived key for mac key
std::vector<uint8_t> mac_key_part2;
DeriveKey(master_key, master_key_size, mac_key_context, 1, &mac_key_server_);

View File

@@ -73,8 +73,10 @@ class KeyDeriver : public Encryptor {
// Generate mac and enc keys give the master key.
void DeriveKeys(const uint8_t* master_key, size_t master_key_size,
const std::vector<uint8_t>& mac_key_context,
const std::vector<uint8_t>& enc_key_context);
const std::vector<uint8_t>& context);
void DeriveKeys(const uint8_t* master_key, size_t master_key_size,
const std::vector<uint8_t>& context, const char* mac_label,
const char* enc_label);
// Sign the buffer with server's mac key.
void ServerSignBuffer(const uint8_t* data, size_t data_length,
std::vector<uint8_t>* signature) const;

View File

@@ -218,9 +218,12 @@ class boringssl_ptr {
Test_PST_Report::Test_PST_Report(const std::string& pst_in,
OEMCrypto_Usage_Entry_Status status_in)
: status(status_in), pst(pst_in) {
time_created = wvutil::Clock().GetCurrentTime();
}
: 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>
@@ -231,7 +234,8 @@ RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse, ResponseData>::
// verified by the server. This simulates that.
size_t gen_signature_length = 0;
size_t core_message_length = 0;
constexpr size_t small_size = 42; // arbitrary.
const vector<uint8_t> context = session()->GetDefaultContext();
const size_t small_size = context.size(); // arbitrary.
if (RequestHasNonce()) {
session()->GenerateNonce();
}
@@ -249,7 +253,10 @@ RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse, ResponseData>::
size_t message_size =
std::max(required_message_size_, core_message_length + small_size);
vector<uint8_t> data(message_size);
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
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);
@@ -345,29 +352,37 @@ void ProvisioningRoundTrip::PrepareSession(
const wvoec::WidevineKeybox& keybox) {
ASSERT_NO_FATAL_FAILURE(session_->open());
if (global_features.provisioning_method == OEMCrypto_Keybox) {
session_->GenerateDerivedKeysFromKeybox(keybox);
encryptor_ = session_->key_deriver();
keybox_ = &keybox;
} else if (global_features.provisioning_method ==
OEMCrypto_BootCertificateChain) {
// TODO(chelu): change this to CSR provisioning.
session_->LoadOEMCert(true);
session_->GenerateRsaSessionKey(&message_key_, &encrypted_message_key_);
encryptor_.set_enc_key(message_key_);
session_->GenerateRsaSessionKey();
encryptor_.set_enc_key(session_->session_key());
} else {
EXPECT_EQ(global_features.provisioning_method, OEMCrypto_OEMCertificate);
session_->LoadOEMCert(true);
session_->GenerateRsaSessionKey(&message_key_, &encrypted_message_key_);
encryptor_.set_enc_key(message_key_);
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 (global_features.provisioning_method == OEMCrypto_OEMCertificate) {
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;
@@ -400,11 +415,11 @@ void ProvisioningRoundTrip::CreateDefaultResponse() {
response_data_.rsa_key_length = encoded_rsa_key_.size();
}
response_data_.nonce = session_->nonce();
if (encrypted_message_key_.size() > 0) {
ASSERT_LE(encrypted_message_key_.size(), kMaxTestRSAKeyLength);
memcpy(response_data_.enc_message_key, encrypted_message_key_.data(),
encrypted_message_key_.size());
response_data_.enc_message_key_length = encrypted_message_key_.size();
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;
}
@@ -460,9 +475,6 @@ void ProvisioningRoundTrip::SignResponse() {
memcpy(encrypted_response_.data() + serialized_core_message_.size(),
reinterpret_cast<const uint8_t*>(&encrypted_response_data_),
sizeof(encrypted_response_data_));
if (global_features.provisioning_method == OEMCrypto_OEMCertificate) {
session()->GenerateDerivedKeysFromSessionKey();
}
session()->key_deriver().ServerSignBuffer(encrypted_response_.data(),
encrypted_response_.size(),
&response_signature_);
@@ -528,11 +540,24 @@ OEMCryptoResult ProvisioningRoundTrip::LoadResponseNoRetry(
Session* session, size_t* wrapped_key_length) {
EXPECT_NE(session, nullptr);
VerifyEncryptAndSignResponseLengths();
return OEMCrypto_LoadProvisioning(
session->session_id(), encrypted_response_.data(),
encrypted_response_.size(), serialized_core_message_.size(),
response_signature_.data(), response_signature_.size(),
wrapped_rsa_key_.data(), wrapped_key_length);
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() {
@@ -560,12 +585,12 @@ void Provisioning40RoundTrip::PrepareSession(bool is_oem_key) {
public_key.resize(public_key_size);
if (is_oem_key) {
wrapped_oem_key_ = wrapped_private_key;
oem_public_key_ = public_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_ = wrapped_private_key;
drm_public_key_ = public_key;
wrapped_drm_key_ = std::move(wrapped_private_key);
drm_public_key_ = std::move(public_key);
drm_key_type_ = key_type;
}
}
@@ -621,8 +646,8 @@ void Provisioning40CastRoundTrip::PrepareSession() {
wrapped_private_key.resize(wrapped_private_key_size);
public_key.resize(public_key_size);
wrapped_drm_key_ = wrapped_private_key;
drm_public_key_ = public_key;
wrapped_drm_key_ = std::move(wrapped_private_key);
drm_public_key_ = std::move(public_key);
drm_key_type_ = key_type;
}
@@ -751,11 +776,13 @@ OEMCryptoResult Provisioning40CastRoundTrip::LoadResponseNoRetry(
Session* session, size_t* wrapped_key_length) {
EXPECT_NE(session, nullptr);
VerifyEncryptAndSignResponseLengths();
return OEMCrypto_LoadProvisioning(
session->session_id(), encrypted_response_.data(),
encrypted_response_.size(), serialized_core_message_.size(),
response_signature_.data(), response_signature_.size(),
wrapped_rsa_key_.data(), wrapped_key_length);
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(
@@ -768,17 +795,18 @@ void LicenseRoundTrip::VerifyRequestSignature(
if (api_version_ > global_features.api_version)
api_version_ = global_features.api_version;
vector<uint8_t> sign_source;
if (global_features.api_version < 17) {
const std::vector<uint8_t> subdata(data.begin() + core_message_length,
data.end());
session()->VerifyRsaSignature(subdata, generated_signature.data(),
generated_signature.size(), kSign_RSASSA_PSS);
SHA256(data.data(), core_message_length, request_hash_);
sign_source.assign(data.begin() + core_message_length, data.end());
} else if (global_features.api_version < 19) {
sign_source = data;
} else {
session()->VerifySignature(data, generated_signature.data(),
generated_signature.size(), kSign_RSASSA_PSS);
SHA256(data.data(), core_message_length, request_hash_);
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(
@@ -984,7 +1012,8 @@ void LicenseRoundTrip::FillCoreResponseSubstrings() {
}
void LicenseRoundTrip::EncryptResponse(bool force_clear_kcb) {
ASSERT_NO_FATAL_FAILURE(session_->GenerateDerivedKeysFromSessionKey());
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);
@@ -1115,6 +1144,9 @@ OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session,
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
@@ -1131,7 +1163,9 @@ OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session,
reinterpret_cast<const uint8_t*>(&encrypted_response_data_) +
sizeof(encrypted_response_data_));
OEMCryptoResult result = OEMCrypto_LoadLicense(
session->session_id(), double_message.data(), encrypted_response_.size(),
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) {
@@ -1495,27 +1529,9 @@ void RenewalRoundTrip::FillAndVerifyCoreRequest(
}
}
void RenewalRoundTrip::CreateDefaultResponse() {
if (is_release_) {
uint32_t control = 0;
uint32_t nonce = 0;
// A single key object with no key id should update all keys.
constexpr size_t index = 0;
response_data_.keys[index].key_id_length = 0;
response_data_.keys[index].key_id[0] = '\0';
const uint32_t renewal_api =
std::max<uint32_t>(core_request_.api_major_version, 15u);
std::string kcVersion = "kc" + std::to_string(renewal_api);
memcpy(response_data_.keys[index].control.verification, kcVersion.c_str(),
4);
const uint32_t duration = static_cast<uint32_t>(
license_messages_->core_response()
.timer_limits.initial_renewal_duration_seconds);
response_data_.keys[index].control.duration = htonl(duration);
response_data_.keys[index].control.nonce = htonl(nonce);
response_data_.keys[index].control.control_bits = htonl(control);
}
}
// 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.
@@ -1593,7 +1609,7 @@ OEMCryptoResult RenewalRoundTrip::LoadResponse(Session* session) {
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;
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,
@@ -1610,6 +1626,81 @@ OEMCryptoResult RenewalRoundTrip::LoadResponse(Session* session) {
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) {
(void)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_;
@@ -1659,63 +1750,48 @@ void Session::GenerateNonce(int* error_counter) {
}
}
void Session::FillDefaultContext(vector<uint8_t>* mac_context,
vector<uint8_t>* enc_context) {
/* Context strings
* These context strings are normally created by the CDM layer
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.
*/
*mac_context = wvutil::a2b_hex(
"41555448454e5449434154494f4e000a4c08001248000000020000101907d9ff"
"de13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e5873"
"4930acebe899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a"
"230a14080112100915007caa9b5931b76a3a85f046523e10011a093938373635"
"34333231180120002a0c31383836373837343035000000000200");
*enc_context = wvutil::a2b_hex(
"454e4352595054494f4e000a4c08001248000000020000101907d9ffde13aa95"
"c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930aceb"
"e899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a230a1408"
"0112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231"
"180120002a0c31383836373837343035000000000080");
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) {
vector<uint8_t> mac_context;
vector<uint8_t> enc_context;
FillDefaultContext(&mac_context, &enc_context);
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_GenerateDerivedKeys(
session_id(), mac_context.data(), mac_context.size(),
enc_context.data(), enc_context.size()));
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_),
mac_context, enc_context);
context);
}
void Session::GenerateDerivedKeysFromSessionKey() {
// Uses test certificate.
vector<uint8_t> session_key;
vector<uint8_t> enc_session_key;
ASSERT_TRUE(public_rsa_ || public_ec_)
<< "No public RSA/ECC key loaded in test code";
// A failure here probably indicates that there is something wrong with the
// test program and its dependency on BoringSSL.
ASSERT_TRUE(GenerateSessionKey(&session_key, &enc_session_key));
vector<uint8_t> mac_context;
vector<uint8_t> enc_context;
FillDefaultContext(&mac_context, &enc_context);
// A failure here is probably caused by having the wrong RSA key loaded.
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_DeriveKeysFromSessionKey(
session_id(), enc_session_key.data(), enc_session_key.size(),
mac_context.data(), mac_context.size(), enc_context.data(),
enc_context.size()));
GenerateDerivedKeysFromSessionKey(GetDefaultContext());
}
key_deriver_.DeriveKeys(session_key.data(), session_key.size(), mac_context,
enc_context);
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,
@@ -1872,7 +1948,6 @@ void Session::LoadOEMCert(bool verify_cert) {
util::RsaPublicKey::FromSslHandle(EVP_PKEY_get0_RSA(pubkey.get()));
ASSERT_TRUE(public_rsa_)
<< "Failed to extract public RSA key from OEM certificate";
return;
}
if (verify_cert) {
vector<char> buffer(80);
@@ -2017,19 +2092,17 @@ void Session::VerifySignature(const vector<uint8_t>& message,
FAIL() << "No public RSA or ECC key loaded in test code";
}
bool Session::GenerateRsaSessionKey(vector<uint8_t>* session_key,
vector<uint8_t>* enc_session_key) {
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();
session_key_ = wvutil::a2b_hex("6fa479c731d2770b6a61a5d1420bb9d1");
enc_session_key_ = public_rsa_->EncryptSessionKey(session_key_);
return !enc_session_key_.empty();
}
bool Session::GenerateEccSessionKey(vector<uint8_t>* session_key,
vector<uint8_t>* ecdh_public_key_data) {
bool Session::GenerateEccSessionKey() {
if (!public_ec_) {
cerr << "No public ECC key loaded in test code\n";
return false;
@@ -2044,24 +2117,23 @@ bool Session::GenerateEccSessionKey(vector<uint8_t>* session_key,
<< util::EccCurveToString(curve) << std::endl;
return false;
}
*session_key = server_ephemeral_keys_[curve]->DeriveSessionKey(*public_ec_);
if (session_key->empty()) {
session_key_ = server_ephemeral_keys_[curve]->DeriveSessionKey(*public_ec_);
if (session_key_.empty()) {
return false;
}
*ecdh_public_key_data = server_ephemeral_keys_[curve]->SerializeAsPublicKey();
if (ecdh_public_key_data->empty()) {
session_key->clear();
enc_session_key_ = server_ephemeral_keys_[curve]->SerializeAsPublicKey();
if (enc_session_key_.empty()) {
session_key_.clear();
return false;
}
return true;
}
bool Session::GenerateSessionKey(vector<uint8_t>* session_key,
vector<uint8_t>* key_material) {
bool Session::GenerateSessionKey() {
if (public_rsa_ != nullptr) {
return GenerateRsaSessionKey(session_key, key_material);
return GenerateRsaSessionKey();
} else if (public_ec_ != nullptr) {
return GenerateEccSessionKey(session_key, key_material);
return GenerateEccSessionKey();
}
cerr << "No public RSA or ECC key loaded in test code\n";
return false;

View File

@@ -276,7 +276,7 @@ class ProvisioningRoundTrip
const std::vector<uint8_t>& encoded_rsa_key)
: RoundTrip(session),
allowed_schemes_(kSign_RSASSA_PSS),
encryptor_(),
keybox_(nullptr),
encoded_rsa_key_(encoded_rsa_key) {}
// Prepare the session for signing the request.
virtual void PrepareSession(const wvoec::WidevineKeybox& keybox);
@@ -317,9 +317,9 @@ class ProvisioningRoundTrip
uint32_t allowed_schemes_;
Encryptor encryptor_;
std::vector<uint8_t> request_;
const wvoec::WidevineKeybox* keybox_;
// The message key used for Prov 3.0.
std::vector<uint8_t> message_key_;
std::vector<uint8_t> encrypted_message_key_;
std::vector<uint8_t> encoded_rsa_key_;
std::vector<uint8_t> wrapped_rsa_key_;
};
@@ -337,8 +337,8 @@ class Provisioning40RoundTrip
// Not used. Use Load*CertResponse() below to load OEM/DRM response
// respectively.
void CreateDefaultResponse() override{};
void EncryptAndSignResponse() override{};
void CreateDefaultResponse() override {};
void EncryptAndSignResponse() override {};
OEMCryptoResult LoadResponse(Session* session) override {
(void)session;
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
@@ -383,8 +383,10 @@ class Provisioning40CastRoundTrip
/* ResponseData */ RSAPrivateKeyMessage> {
public:
Provisioning40CastRoundTrip(Session* session,
const std::vector<uint8_t>& encoded_rsa_key)
: RoundTrip(session), encryptor_(),
const std::vector<uint8_t>& encoded_rsa_key)
: RoundTrip(session),
allowed_schemes_(kSign_RSASSA_PSS),
encryptor_(),
encoded_rsa_key_(encoded_rsa_key) {}
void PrepareSession();
@@ -394,7 +396,8 @@ class Provisioning40CastRoundTrip
void EncryptAndSignResponse() override;
OEMCryptoResult LoadResponse() override { return LoadResponse(session_); }
OEMCryptoResult LoadResponse(Session* session) override;
OEMCryptoResult LoadResponseNoRetry(Session* session, size_t* wrapped_key_length) ;
OEMCryptoResult LoadResponseNoRetry(Session* session,
size_t* wrapped_key_length);
// Returned
const std::vector<uint8_t>& wrapped_drm_key() { return wrapped_drm_key_; }
@@ -442,6 +445,7 @@ class LicenseRoundTrip
update_mac_keys_(true),
api_version_(kCurrentAPI),
expect_request_has_correct_nonce_(true),
skip_request_hash_(global_features.api_version < 19),
license_type_(OEMCrypto_ContentLicense),
request_hash_() {}
void CreateDefaultResponse() override;
@@ -516,6 +520,8 @@ class LicenseRoundTrip
}
// Skip the nonce check when verifying the license request.
void skip_nonce_check() { expect_request_has_correct_nonce_ = false; }
// Skip hashing license request before signing/KDF.
void skip_request_hash() { skip_request_hash_ = true; }
// This sets the key id of the specified key to the specified string.
// This is used to test with different key id lengths.
void SetKeyId(size_t index, const string& key_id);
@@ -547,6 +553,9 @@ class LicenseRoundTrip
// session. This is usually true, but when we are testing how OEMCrypto
// handles a bad nonce, we don't want to.
bool expect_request_has_correct_nonce_;
// Whether to skip hashing the request before signing and KDF; this is used
// for license protocol 2.2.
bool skip_request_hash_;
// Whether this is a content license or an entitlement license. Used in
// CreateDefaultResponse.
OEMCrypto_LicenseType license_type_;
@@ -601,6 +610,48 @@ class RenewalRoundTrip
bool is_release_; // If this is a license release, and not a real renewal.
};
class ReleaseRoundTrip
: public RoundTrip<
/* CoreRequest */ oemcrypto_core_message::ODK_ReleaseRequest,
OEMCrypto_PrepAndSignReleaseRequest,
// Release response info is same as request:
/* CoreResponse */ oemcrypto_core_message::ODK_ReleaseRequest,
/* ResponseData */ MessageData> {
public:
ReleaseRoundTrip(LicenseRoundTrip* license_messages)
: RoundTrip(license_messages->session()),
license_messages_(license_messages) {}
void CreateDefaultResponse() override;
void EncryptAndSignResponse() override;
OEMCryptoResult LoadResponse() override { return LoadResponse(session_); }
OEMCryptoResult LoadResponse(Session* session) override;
int64_t seconds_since_license_received() const {
return seconds_since_license_received_;
}
void set_seconds_since_license_received(
int64_t seconds_since_license_received) {
seconds_since_license_received_ = seconds_since_license_received;
}
int64_t seconds_since_first_decrypt() const {
return seconds_since_first_decrypt_;
}
void set_seconds_since_first_decrypt(int64_t seconds_since_first_decrypt) {
seconds_since_first_decrypt_ = seconds_since_first_decrypt;
}
protected:
bool RequestHasNonce() override { return false; }
void VerifyRequestSignature(const vector<uint8_t>& data,
const vector<uint8_t>& generated_signature,
size_t core_message_length) override;
// Verify the values of the core response.
virtual void FillAndVerifyCoreRequest(
const std::string& core_message_string) override;
LicenseRoundTrip* license_messages_;
int64_t seconds_since_license_received_;
int64_t seconds_since_first_decrypt_;
};
class EntitledMessage {
public:
EntitledMessage(LicenseRoundTrip* license_messages)
@@ -671,15 +722,17 @@ class Session {
// and try again if a nonce flood has been detected. If error_counter is
// not null, it will be incremented when a nonce flood is detected.
void GenerateNonce(int* error_counter = nullptr);
// Fill the vectors with test context which generate known mac and enc keys.
void FillDefaultContext(vector<uint8_t>* mac_context,
vector<uint8_t>* enc_context);
// Fill the vector with test context which generate known mac and enc keys.
std::vector<uint8_t> GetDefaultContext(bool do_hash = false);
// Generate known mac and enc keys using OEMCrypto_GenerateDerivedKeys and
// also fill out enc_key_, mac_key_server_, and mac_key_client_.
void GenerateDerivedKeysFromKeybox(const wvoec::WidevineKeybox& keybox);
void GenerateDerivedKeysFromKeybox(const wvoec::WidevineKeybox& keybox,
const std::vector<uint8_t>& context);
// Generate known mac and enc keys using OEMCrypto_DeriveKeysFromSessionKey
// and also fill out enc_key_, mac_key_server_, and mac_key_client_.
void GenerateDerivedKeysFromSessionKey();
void GenerateDerivedKeysFromSessionKey(const std::vector<uint8_t>& context);
// Encrypt some data and pass to OEMCrypto_DecryptCENC to verify decryption.
void TestDecryptCTR(bool get_fresh_key_handle_first = true,
OEMCryptoResult expected_result = OEMCrypto_SUCCESS,
@@ -745,17 +798,14 @@ class Session {
// Encrypts a known session key with public_rsa_ for use in future calls to
// OEMCrypto_DeriveKeysFromSessionKey or OEMCrypto_RewrapDeviceRSAKey30.
// The unencrypted session key is stored in session_key.
bool GenerateRsaSessionKey(vector<uint8_t>* session_key,
vector<uint8_t>* enc_session_key);
bool GenerateRsaSessionKey();
// Derives a session key with public_ec_ and a ephemeral "server" ECC key
// for use in future calls to OEMCrypto_DeriveKeysFromSessionKey.
// The unencrypted session key is stored in session_key.
bool GenerateEccSessionKey(vector<uint8_t>* session_key,
vector<uint8_t>* ecdh_public_key_data);
bool GenerateEccSessionKey();
// Based on the key type installed, call GenerateRsaSessionKey or
// GenerateEccSessionKey.
bool GenerateSessionKey(vector<uint8_t>* session_key,
vector<uint8_t>* key_material);
bool GenerateSessionKey();
// Calls OEMCrypto_RewrapDeviceRSAKey30 with the given provisioning response
// message. If force is true, we assert that the key loads successfully.
@@ -838,6 +888,11 @@ class Session {
// functions.
vector<uint8_t>& key_handle() { return key_handle_; }
const std::vector<uint8_t>& session_key() const { return session_key_; }
const std::vector<uint8_t>& enc_session_key() const {
return enc_session_key_;
}
const KeyDeriver& key_deriver() const { return key_deriver_; }
void set_mac_keys(const uint8_t* mac_keys) {
key_deriver_.set_mac_keys(mac_keys);
@@ -880,6 +935,8 @@ class Session {
vector<uint8_t> pst_report_buffer_;
MessageData license_ = {};
vector<uint8_t> key_handle_;
std::vector<uint8_t> session_key_;
std::vector<uint8_t> enc_session_key_;
vector<uint8_t> encrypted_usage_entry_;
uint32_t usage_entry_number_ = 0;

View File

@@ -120,22 +120,46 @@ const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value) {
// Return a printable string from data. If all the characters are printable,
// then just use the string. Otherwise, convert to hex.
std::string MaybeHex(const uint8_t* data, size_t length) {
for (size_t i = 0; i < length; i++) {
// Check for a early null termination. This is common for the device
// id in a keybox, which is padded with 0s.
const size_t c_len = strnlen(reinterpret_cast<const char*>(data), length);
// If there is any nonzero after the first zero, then just use hex.
for (size_t i = c_len; i < length; i++) {
if (data[i] != 0) return "0x" + wvutil::HexEncode(data, length);
}
for (size_t i = 0; i < c_len; i++) {
if (!isprint(data[i])) return "0x" + wvutil::HexEncode(data, length);
}
return std::string(reinterpret_cast<const char*>(data), length);
return std::string(reinterpret_cast<const char*>(data), c_len);
}
std::string MaybeHex(const std::vector<uint8_t>& data) {
return MaybeHex(data.data(), data.size());
}
// Get the Device's ID and return it in a printable format.
std::string GetDeviceId() {
OEMCryptoResult sts;
std::vector<uint8_t> dev_id(128, 0);
size_t dev_id_len = dev_id.size();
sts = OEMCrypto_GetDeviceID(dev_id.data(), &dev_id_len);
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
if (dev_id_len <= 0) return "NO DEVICE ID";
dev_id.resize(dev_id_len);
sts = OEMCrypto_GetDeviceID(dev_id.data(), &dev_id_len);
}
if (sts != OEMCrypto_SUCCESS) return "NO DEVICE ID";
dev_id.resize(dev_id_len);
return MaybeHex(dev_id);
}
/// @addtogroup basic
/// @{
TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) {
Session s;
s.open();
OEMCrypto_DestBufferDesc output_descriptor;
OEMCrypto_DestBufferDesc output_descriptor = {};
int secure_fd = kHugeRandomNumber;
ASSERT_NE(OEMCrypto_SUCCESS,
OEMCrypto_FreeSecureBuffer(s.session_id(), &output_descriptor,
@@ -156,7 +180,7 @@ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) {
*/
TEST_F(OEMCryptoClientTest, VersionNumber) {
const std::string log_message =
"OEMCrypto unit tests for API 18.5. Tests last updated 2024-03-21";
"OEMCrypto unit tests for API 19.1. Tests last updated 2024-03-25";
cout << " " << log_message << "\n";
cout << " "
<< "These tests are part of Android U."
@@ -164,8 +188,8 @@ TEST_F(OEMCryptoClientTest, VersionNumber) {
LOGI("%s", log_message.c_str());
// If any of the following fail, then it is time to update the log message
// above.
EXPECT_EQ(ODK_MAJOR_VERSION, 18);
EXPECT_EQ(ODK_MINOR_VERSION, 5);
EXPECT_EQ(ODK_MAJOR_VERSION, 19);
EXPECT_EQ(ODK_MINOR_VERSION, 1);
EXPECT_EQ(kCurrentAPI, static_cast<unsigned>(ODK_MAJOR_VERSION));
OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel();
EXPECT_GT(level, OEMCrypto_Level_Unknown);
@@ -175,6 +199,9 @@ TEST_F(OEMCryptoClientTest, VersionNumber) {
uint32_t minor_version = OEMCrypto_MinorAPIVersion();
cout << " OEMCrypto API version is " << version << "."
<< minor_version << endl;
cout << " OEMCrypto Device ID is '" << GetDeviceId() << "'"
<< endl;
if (OEMCrypto_SupportsUsageTable()) {
cout << " OEMCrypto supports usage tables" << endl;
} else {
@@ -261,6 +288,9 @@ TEST_F(OEMCryptoClientTest, CheckSRMCapabilityV13) {
}
TEST_F(OEMCryptoClientTest, CheckNullBuildInformationAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
OEMCryptoResult sts;
std::string build_info;
sts = OEMCrypto_BuildInformation(&build_info[0], nullptr);
@@ -287,6 +317,9 @@ TEST_F(OEMCryptoClientTest, CheckNullBuildInformationAPI17) {
}
TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) {
if (wvoec::global_features.api_version < 18) {
GTEST_SKIP() << "Test for versions 18 and up only.";
}
std::string build_info;
OEMCryptoResult sts = OEMCrypto_BuildInformation(&build_info[0], nullptr);
ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
@@ -332,7 +365,7 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) {
// check for existence in map
// check if value matches expectation
// remove from map
for (int i = 0; i < jsmn_result; i++) {
for (int32_t i = 0; i < jsmn_result; i++) {
jsmntok_t token = tokens[i];
std::string key = build_info.substr(token.start, token.end - token.start);
if (expected.find(key) != expected.end()) {
@@ -345,7 +378,7 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) {
// if map is not empty, return false
if (expected.size() > 0) {
std::string missing;
for (auto e : expected) {
for (const auto& e : expected) {
missing.append(e.first);
missing.append(" ");
}
@@ -389,6 +422,9 @@ TEST_F(OEMCryptoClientTest, NormalInitTermination) {
}
TEST_F(OEMCryptoClientTest, CheckDTCP2CapabilityAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
OEMCryptoResult sts;
OEMCrypto_DTCP2_Capability capability;
sts = OEMCrypto_GetDTCP2Capability(&capability);

View File

@@ -11,6 +11,9 @@ using ::testing::Range;
namespace wvoec {
/// @addtogroup cast
/// @{
/** If a device can load a private key with the alternate padding schemes, it
* should support signing with the alternate scheme. */
TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1) {
@@ -20,22 +23,21 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1) {
// If the device is a cast receiver, then this scheme is required.
if (global_features.cast_receiver) {
ASSERT_TRUE(key_loaded_);
// A signature with a valid size should succeed.
TestSignature(kSign_PKCS1_Block1, 83);
TestSignature(kSign_PKCS1_Block1, 50);
}
// If the key loaded with no error, then we will verify that it is not used
// for forbidden padding schemes.
// for forbidden padding schemes. This should be tested for both devices that
// are cast receivers and devices that are not.
if (key_loaded_) {
if (global_features.cast_receiver) {
// A signature with a valid size should succeed.
TestSignature(kSign_PKCS1_Block1, 83);
TestSignature(kSign_PKCS1_Block1, 50);
}
// A signature with padding that is too big should fail.
DisallowForbiddenPaddingDRMKey(kSign_PKCS1_Block1, 84); // too big.
}
}
/** The alternate padding is only required for cast receivers, but if a device
* does load an alternate certificate, it should NOT be used as a DRM cert
* does load an alternate certificate, it should NOT be used to as a DRM cert
* key. */
TEST_F(OEMCryptoLoadsCertificateAlternates, ForbidUseAsDRMCert) {
// Try to load an RSA key with alternative padding schemes. This signing
@@ -50,7 +52,6 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, ForbidUseAsDRMCert) {
if (key_loaded_) {
// The other padding scheme should fail.
DisallowForbiddenPaddingDRMKey(kSign_RSASSA_PSS, 83);
DisallowDeriveKeys();
}
}
@@ -81,10 +82,7 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, ForbidPrepAndSign) {
OEMCryptoResult result = OEMCrypto_PrepAndSignLicenseRequest(
s.session_id(), message.data(), message.size(), &core_message_length,
signature.data(), &signature_length);
// TODO: remove OEMCrypto_ERROR_INVALID_RSA_KEY once OEMCrypto v16 is not
// supported anymore. This error code has been deprecated since v17.
ASSERT_TRUE(result == OEMCrypto_ERROR_INVALID_KEY ||
result == OEMCrypto_ERROR_INVALID_RSA_KEY);
ASSERT_EQ(OEMCrypto_ERROR_INVALID_KEY, result);
const vector<uint8_t> zero(signature.size(), 0);
ASSERT_EQ(signature, zero); // Signature should not have been computed.
}
@@ -1010,6 +1008,9 @@ TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_20) {
}
TEST_P(OEMCryptoSessionTestLoadCasKeysWithHDCP, CasOnlyLoadCasKeysAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
@@ -1018,4 +1019,5 @@ TEST_P(OEMCryptoSessionTestLoadCasKeysWithHDCP, CasOnlyLoadCasKeysAPI17) {
}
INSTANTIATE_TEST_SUITE_P(TestHDCP, OEMCryptoSessionTestLoadCasKeysWithHDCP,
Range(1, 6));
/// @}
} // namespace wvoec

View File

@@ -19,9 +19,6 @@ namespace wvoec {
const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value);
std::string MaybeHex(const uint8_t* data, size_t length);
std::string MaybeHex(const std::vector<uint8_t>& data);
// This test attempts to use alternate algorithms for loaded device certs.
class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate {
protected:
@@ -54,26 +51,6 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate {
licenseRequest, signature.data(), signature_length, scheme));
}
void DisallowDeriveKeys() {
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_));
s.GenerateNonce();
vector<uint8_t> session_key;
vector<uint8_t> enc_session_key;
ASSERT_NO_FATAL_FAILURE(s.SetRsaPublicKeyFromPrivateKeyInfo(
encoded_rsa_key_.data(), encoded_rsa_key_.size()));
ASSERT_TRUE(s.GenerateRsaSessionKey(&session_key, &enc_session_key));
vector<uint8_t> mac_context;
vector<uint8_t> enc_context;
s.FillDefaultContext(&mac_context, &enc_context);
ASSERT_NE(OEMCrypto_SUCCESS,
OEMCrypto_DeriveKeysFromSessionKey(
s.session_id(), enc_session_key.data(),
enc_session_key.size(), mac_context.data(),
mac_context.size(), enc_context.data(), enc_context.size()));
}
// If force is true, we assert that the key loads successfully.
void LoadCastCertificateKey(bool force) {
if (!wvoec::global_features.cast_receiver) {

View File

@@ -242,12 +242,10 @@ TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) {
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
uint32_t frame_number = 1;
uint32_t hash = 42;
const uint32_t crc32 = 42;
// It is OK to set the hash before loading the keys
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_SetDecryptHash(session_.session_id(), frame_number,
reinterpret_cast<const uint8_t*>(&hash),
sizeof(hash)));
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_SetDecryptHash(session_.session_id(),
frame_number, crc32));
// It is OK to select the key and decrypt.
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR());
// But the error code should be bad.
@@ -257,11 +255,10 @@ TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) {
// This test verifies OEMCrypto_SetDecryptHash for out of range frame number.
TEST_P(OEMCryptoLicenseTest, DecryptHashForOutOfRangeFrameNumber) {
uint32_t frame_number = kHugeRandomNumber;
uint32_t hash = 42;
ASSERT_NO_FATAL_FAILURE(OEMCrypto_SetDecryptHash(
session_.session_id(), frame_number,
reinterpret_cast<const uint8_t*>(&hash), sizeof(hash)));
const uint32_t frame_number = kHugeRandomNumber;
const uint32_t crc32 = 42;
ASSERT_NO_FATAL_FAILURE(
OEMCrypto_SetDecryptHash(session_.session_id(), frame_number, crc32));
}
//
@@ -463,7 +460,7 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSampleAPI16) {
subsample_sizes.push_back({clear_size, encrypted_size});
bytes_remaining -= this_subsample_size;
}
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes(subsample_sizes));
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes(std::move(subsample_sizes)));
ASSERT_NO_FATAL_FAILURE(LoadLicense());
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
ASSERT_NO_FATAL_FAILURE(EncryptData());
@@ -477,7 +474,7 @@ TEST_P(OEMCryptoSessionTestsDecryptTests,
while (number_of_subsamples-- > 0) {
subsample_sizes.push_back({100, 100});
}
SetSubsampleSizes(subsample_sizes);
SetSubsampleSizes(std::move(subsample_sizes));
LoadLicense();
MakeBuffers();
EncryptData();
@@ -501,7 +498,7 @@ TEST_P(OEMCryptoSessionTestsDecryptTests,
OEMCryptoMemoryCheckDecryptCENCStatusForHugeSubSample) {
std::vector<SubsampleSize> subsample_sizes;
subsample_sizes.push_back({100000, 100000});
SetSubsampleSizes(subsample_sizes);
SetSubsampleSizes(std::move(subsample_sizes));
LoadLicense();
MakeBuffers();
EncryptData();

View File

@@ -120,7 +120,7 @@ class OEMCryptoSessionTestsDecryptTests
void SetSubsampleSizes(std::vector<SubsampleSize> subsample_sizes) {
// This is just sugar for having one sample with the given subsamples in it.
SetSampleSizes({subsample_sizes});
SetSampleSizes({std::move(subsample_sizes)});
}
void SetSampleSizes(std::vector<std::vector<SubsampleSize>> sample_sizes) {
@@ -386,11 +386,9 @@ class OEMCryptoSessionTestsDecryptTests
if (verify_crc_) {
const TestSample& sample = samples_[0];
uint32_t hash =
uint32_t crc32 =
util::wvcrc32(sample.truth_buffer.data(), sample.truth_buffer.size());
OEMCrypto_SetDecryptHash(session_.session_id(), 1,
reinterpret_cast<const uint8_t*>(&hash),
sizeof(hash));
OEMCrypto_SetDecryptHash(session_.session_id(), 1, crc32);
}
// Build an array of just the sample descriptions.

View File

@@ -11,9 +11,12 @@ using ::testing::Range;
namespace wvoec {
/// @addtogroup generic
/// @{
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyLoad) { EncryptAndLoadKeys(); }
// Test that the Generic_Encrypt function works correctly.
/** Test that the Generic_Encrypt function works correctly. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncrypt) {
EncryptAndLoadKeys();
unsigned int key_index = 0;
@@ -34,7 +37,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncrypt) {
ASSERT_EQ(expected_encrypted, encrypted);
}
// Test that the Generic_Encrypt function fails when not allowed.
/** Test that the Generic_Encrypt function fails when not allowed. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadEncrypt) {
EncryptAndLoadKeys();
BadEncrypt(0, OEMCrypto_HMAC_SHA256, buffer_size_);
@@ -45,8 +48,8 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadEncrypt) {
BadEncrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_);
}
// Test that the Generic_Encrypt works if the input and output buffers are the
// same.
/** Test that the Generic_Encrypt works if the input and output buffers are the
* same. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptSameBufferAPI12) {
EncryptAndLoadKeys();
unsigned int key_index = 0;
@@ -87,7 +90,7 @@ TEST_P(
OEMCrypto_AES_CBC_128_NO_PADDING, buffer.data()));
}
// Test Generic_Decrypt works correctly.
/** Test Generic_Decrypt works correctly. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecrypt) {
EncryptAndLoadKeys();
unsigned int key_index = 1;
@@ -108,8 +111,8 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecrypt) {
ASSERT_EQ(clear_buffer_, resultant);
}
// Test that Generic_Decrypt works correctly when the input and output buffers
// are the same.
/** Test that Generic_Decrypt works correctly when the input and output buffers
* are the same. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptSameBufferAPI12) {
EncryptAndLoadKeys();
unsigned int key_index = 1;
@@ -122,16 +125,16 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptSameBufferAPI12) {
session_.license().keys[key_index].key_id,
session_.license().keys[key_index].key_id_length,
OEMCrypto_CipherMode_CENC, key_handle));
vector<uint8_t> buffer = encrypted;
vector<uint8_t> resultant(encrypted.size());
ASSERT_EQ(OEMCrypto_SUCCESS,
GenericDecrypt(key_handle.data(), key_handle.size(), buffer.data(),
buffer.size(), iv_, OEMCrypto_AES_CBC_128_NO_PADDING,
buffer.data()));
ASSERT_EQ(clear_buffer_, buffer);
GenericDecrypt(key_handle.data(), key_handle.size(),
encrypted.data(), encrypted.size(), iv_,
OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()));
ASSERT_EQ(clear_buffer_, resultant);
}
// Test that Generic_Decrypt fails to decrypt to an insecure buffer if the key
// requires a secure data path.
/** Test that Generic_Decrypt fails to decrypt to an insecure buffer if the key
* requires a secure data path. */
TEST_P(OEMCryptoGenericCryptoTest, GenericSecureToClear) {
license_messages_.set_control(wvoec::kControlObserveDataPath |
wvoec::kControlDataPathSecure);
@@ -155,7 +158,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericSecureToClear) {
ASSERT_NE(clear_buffer_, resultant);
}
// Test that the Generic_Decrypt function fails when not allowed.
/** Test that the Generic_Decrypt function fails when not allowed. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadDecrypt) {
EncryptAndLoadKeys();
BadDecrypt(1, OEMCrypto_HMAC_SHA256, buffer_size_);
@@ -194,7 +197,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeySign) {
ASSERT_EQ(expected_signature, signature);
}
// Test that the Generic_Sign function fails when not allowed.
/** Test that the Generic_Sign function fails when not allowed. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadSign) {
EncryptAndLoadKeys();
BadSign(0, OEMCrypto_HMAC_SHA256); // Can't sign with encrypt key.
@@ -223,7 +226,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerify) {
signature.data(), signature.size()));
}
// Test that the Generic_Verify function fails when not allowed.
/** Test that the Generic_Verify function fails when not allowed. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadVerify) {
EncryptAndLoadKeys();
BadVerify(0, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, false);
@@ -235,7 +238,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadVerify) {
BadVerify(3, OEMCrypto_AES_CBC_128_NO_PADDING, SHA256_DIGEST_LENGTH, false);
}
// Test Generic_Encrypt with the maximum buffer size.
/** Test Generic_Encrypt with the maximum buffer size. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptLargeBuffer) {
ResizeBuffer(GetResourceValue(kMaxGenericBuffer));
EncryptAndLoadKeys();
@@ -257,7 +260,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptLargeBuffer) {
ASSERT_EQ(expected_encrypted, encrypted);
}
// Test Generic_Decrypt with the maximum buffer size.
/** Test Generic_Decrypt with the maximum buffer size. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptLargeBuffer) {
// Some applications are known to pass in a block that is almost 400k.
ResizeBuffer(GetResourceValue(kMaxGenericBuffer));
@@ -280,7 +283,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptLargeBuffer) {
ASSERT_EQ(clear_buffer_, resultant);
}
// Test Generic_Sign with the maximum buffer size.
/** Test Generic_Sign with the maximum buffer size. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeySignLargeBuffer) {
ResizeBuffer(GetResourceValue(kMaxGenericBuffer));
EncryptAndLoadKeys();
@@ -310,7 +313,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeySignLargeBuffer) {
ASSERT_EQ(expected_signature, signature);
}
// Test Generic_Verify with the maximum buffer size.
/** Test Generic_Verify with the maximum buffer size. */
TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerifyLargeBuffer) {
ResizeBuffer(GetResourceValue(kMaxGenericBuffer));
EncryptAndLoadKeys();
@@ -332,7 +335,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerifyLargeBuffer) {
signature.data(), signature.size()));
}
// Test Generic_Encrypt when the key duration has expired.
/** Test Generic_Encrypt when the key duration has expired. */
TEST_P(OEMCryptoGenericCryptoTest, KeyDurationEncrypt) {
license_messages_.core_response()
.timer_limits.total_playback_duration_seconds = kDuration;
@@ -368,7 +371,7 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationEncrypt) {
ASSERT_NO_FATAL_FAILURE(session_.TestGetKeyHandleExpired(key_index));
}
// Test Generic_Decrypt when the key duration has expired.
/** Test Generic_Decrypt when the key duration has expired. */
TEST_P(OEMCryptoGenericCryptoTest, KeyDurationDecrypt) {
license_messages_.core_response()
.timer_limits.total_playback_duration_seconds = kDuration;
@@ -403,7 +406,7 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationDecrypt) {
ASSERT_NO_FATAL_FAILURE(session_.TestGetKeyHandleExpired(key_index));
}
// Test Generic_Sign when the key duration has expired.
/** Test Generic_Sign when the key duration has expired. */
TEST_P(OEMCryptoGenericCryptoTest, KeyDurationSign) {
license_messages_.core_response()
.timer_limits.total_playback_duration_seconds = kDuration;
@@ -442,7 +445,7 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationSign) {
ASSERT_NO_FATAL_FAILURE(session_.TestGetKeyHandleExpired(key_index));
}
// Test Generic_Verify when the key duration has expired.
/** Test Generic_Verify when the key duration has expired. */
TEST_P(OEMCryptoGenericCryptoTest, KeyDurationVerify) {
license_messages_.core_response()
.timer_limits.total_playback_duration_seconds = kDuration;
@@ -574,4 +577,4 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoGenericCryptoKeyIdLengthTest,
Range<uint32_t>(kCoreMessagesAPI, kCurrentAPI + 1));
/// @}
} // namespace wvoec
} // namespace wvoec

View File

@@ -12,6 +12,9 @@ using ::testing::Range;
namespace wvoec {
/// @addtogroup license
/// @{
// Function to test APIs that expect a buffer length as input
// by passing huge buffer lengths up to end_buffer_length and test that the API
// doesn't crash.
@@ -457,6 +460,8 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadVerification) {
// This test verifies that LoadKeys still works when the message is not aligned
// in memory on a word (2 or 4 byte) boundary.
TEST_P(OEMCryptoLicenseTest, LoadKeyUnalignedMessageAPI16) {
license_messages_.skip_request_hash();
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
@@ -472,9 +477,12 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyUnalignedMessageAPI16) {
license_messages_.encrypted_response_buffer().end());
// Thus, buffer[offset] is NOT word aligned.
const uint8_t* unaligned_message = &buffer[offset];
const std::vector<uint8_t> context = session_.GetDefaultContext();
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_LoadLicense(
session_.session_id(), unaligned_message,
session_.session_id(), context.data(), context.size(),
session_.enc_session_key().data(),
session_.enc_session_key().size(), unaligned_message,
license_messages_.encrypted_response_buffer().size(),
license_messages_.serialized_core_message().size(),
license_messages_.response_signature().data(),
@@ -674,6 +682,9 @@ TEST_P(OEMCryptoLicenseTest, QueryKeyControl) {
// implementation should be able to handle the clear KCB in the 16.4.x response
// and load the license correctly.
TEST_F(OEMCryptoSessionTests, ClearKcbAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));

View File

@@ -329,7 +329,7 @@ class LicenseWithUsageEntry {
ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst()));
Test_PST_Report expected(pst(), status);
ASSERT_NO_FATAL_FAILURE(
session_.VerifyReport(expected, time_license_received_,
session_.VerifyReport(std::move(expected), time_license_received_,
time_first_decrypt_, time_last_decrypt_));
// The PST report was signed above. Below we verify that the entire message
// that is sent to the server will be signed by the right mac keys.
@@ -400,6 +400,17 @@ class OEMCryptoRefreshTest : public OEMCryptoLicenseTest {
ASSERT_EQ(expected_result, renewal_messages->LoadResponse());
}
void MakeReleaseRequest(ReleaseRoundTrip* release_messages) {
ASSERT_NO_FATAL_FAILURE(release_messages->SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(release_messages->CreateDefaultResponse());
}
void LoadRelease(ReleaseRoundTrip* release_messages,
OEMCryptoResult expected_result) {
ASSERT_NO_FATAL_FAILURE(release_messages->EncryptAndSignResponse());
ASSERT_EQ(expected_result, release_messages->LoadResponse());
}
ODK_TimerLimits timer_limits_;
};

View File

@@ -5,8 +5,11 @@
#include "oemcrypto_provisioning_test.h"
#include "bcc_validator.h"
#include "device_info_validator.h"
#include "log.h"
#include "platform.h"
#include "signed_csr_payload_validator.h"
#include "test_sleep.h"
namespace wvoec {
@@ -67,24 +70,6 @@ TEST_F(OEMCryptoKeyboxTest, ProductionKeyboxValid) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid());
}
// This tests GenerateDerivedKeys with an 8k context.
TEST_F(OEMCryptoKeyboxTest, GenerateDerivedKeysFromKeyboxLargeBuffer) {
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
const size_t max_size = GetResourceValue(kLargeMessageSize);
vector<uint8_t> mac_context(max_size);
vector<uint8_t> enc_context(max_size);
// Stripe the data so the two vectors are not identical, and not all zeroes.
for (size_t i = 0; i < max_size; i++) {
mac_context[i] = i % 0x100;
enc_context[i] = (3 * i) % 0x100;
}
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_GenerateDerivedKeys(
s.session_id(), mac_context.data(), mac_context.size(),
enc_context.data(), enc_context.size()));
}
// This verifies that the device really does claim to have a certificate.
// It should be filtered out for devices that have a keybox.
TEST_F(OEMCryptoProv30Test, DeviceClaimsOEMCertificate) {
@@ -164,7 +149,6 @@ TEST_F(OEMCryptoProv30Test, GetCertOnlyAPI16) {
// Derive keys from the session key -- this should use the DRM Cert's key.
// It should NOT use the OEM Private key because that key should not have
// been loaded.
ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromSessionKey());
// Now fill a message and try to load it.
LicenseRoundTrip license_messages(&s);
license_messages.set_control(0);
@@ -251,6 +235,9 @@ TEST_F(OEMCryptoProv40Test, GetBootCertificateChainSuccess) {
additional_signature.data(),
&additional_signature_size),
OEMCrypto_SUCCESS);
util::BccValidator validator;
EXPECT_EQ(util::CborMessageStatus::kCborParseOk, validator.Parse(bcc));
EXPECT_EQ(util::CborMessageStatus::kCborValidateOk, validator.Validate());
}
// Verifies that short buffer error returns when the buffer is short.
@@ -363,6 +350,9 @@ TEST_F(OEMCryptoProv40Test, GenerateCertificateKeyPairsAreDifferent) {
}
TEST_F(OEMCryptoProv40Test, GetDeviceInformationAPI18) {
if (wvoec::global_features.api_version < 18) {
GTEST_SKIP() << "Test for versions 18 and up only.";
}
std::vector<uint8_t> device_info;
size_t device_info_length = 0;
OEMCryptoResult sts =
@@ -374,24 +364,26 @@ TEST_F(OEMCryptoProv40Test, GetDeviceInformationAPI18) {
OEMCrypto_GetDeviceInformation(device_info.data(), &device_info_length),
OEMCrypto_SUCCESS);
EXPECT_NE(device_info_length, 0uL);
device_info.resize(device_info_length);
constexpr int kDeviceVersion = 3;
util::DeviceInfoValidator validator(kDeviceVersion);
EXPECT_EQ(util::CborMessageStatus::kCborParseOk,
validator.Parse(device_info));
validator.Validate();
EXPECT_EQ(util::CborMessageStatus::kCborValidateOk, validator.Validate());
}
TEST_F(OEMCryptoProv40Test, GetDeviceSignedCsrPayloadAPI18) {
std::vector<uint8_t> challenge(64, 0xaa);
// TODO: add cppbor support for oemcrypto tests for all targets. Before that,
// use hex values which are equivalent of the commented cppbor statement.
// std::vector<uint8_t> device_info = cppbor::Map()
// .add("manufacturer", "google")
// .add("fused", 0)
// .add("other", "ignored")
// .canonicalize()
// .encode();
//
std::vector<uint8_t> device_info = {
0xa3, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x0, 0x65, 0x6f, 0x74,
0x68, 0x65, 0x72, 0x67, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64,
0x6c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72,
0x65, 0x72, 0x66, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65};
if (wvoec::global_features.api_version < 18) {
GTEST_SKIP() << "Test for versions 18 and up only.";
}
const std::vector<uint8_t> challenge(64, 0xaa);
const std::vector<uint8_t> device_info = cppbor::Map()
.add("manufacturer", "google")
.add("fused", 0)
.add("other", "ignored")
.canonicalize()
.encode();
std::vector<uint8_t> signed_csr_payload;
size_t signed_csr_payload_length = 0;
OEMCryptoResult sts = OEMCrypto_GetDeviceSignedCsrPayload(
@@ -407,17 +399,22 @@ TEST_F(OEMCryptoProv40Test, GetDeviceSignedCsrPayloadAPI18) {
&signed_csr_payload_length),
OEMCrypto_SUCCESS);
EXPECT_NE(signed_csr_payload_length, 0uL);
util::SignedCsrPayloadValidator validator;
EXPECT_EQ(util::CborMessageStatus::kCborParseOk,
validator.Parse(signed_csr_payload));
EXPECT_EQ(util::CborMessageStatus::kCborValidateOk, validator.Validate());
}
TEST_F(OEMCryptoProv40Test, GetDeviceSignedCsrPayloadInvalid) {
std::vector<uint8_t> signed_csr_payload;
size_t signed_csr_payload_length = 0;
std::vector<uint8_t> challenge(64, 0xaa);
std::vector<uint8_t> device_info = {
0xa3, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x0, 0x65, 0x6f, 0x74,
0x68, 0x65, 0x72, 0x67, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64,
0x6c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72,
0x65, 0x72, 0x66, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65};
const std::vector<uint8_t> challenge(64, 0xaa);
const std::vector<uint8_t> device_info = cppbor::Map()
.add("manufacturer", "google")
.add("fused", 0)
.add("other", "ignored")
.canonicalize()
.encode();
std::vector<uint8_t> challenge_empty;
OEMCryptoResult sts = OEMCrypto_GetDeviceSignedCsrPayload(
challenge_empty.data(), challenge_empty.size(), device_info.data(),
@@ -427,7 +424,7 @@ TEST_F(OEMCryptoProv40Test, GetDeviceSignedCsrPayloadInvalid) {
ASSERT_EQ(sts, OEMCrypto_ERROR_INVALID_CONTEXT);
// Oversized challenge
std::vector<uint8_t> challenge_long(65, 0xaa);
const std::vector<uint8_t> challenge_long(65, 0xaa);
sts = OEMCrypto_GetDeviceSignedCsrPayload(
challenge_long.data(), challenge_long.size(), device_info.data(),
device_info.size(), signed_csr_payload.data(),
@@ -693,6 +690,9 @@ TEST_F(OEMCryptoLoadsCertificate, ForbidRSASignatureForDRMKey2) {
}
TEST_F(OEMCryptoLoadsCertificate, PrepAndSignLicenseRequestCounterAPI18) {
if (wvoec::global_features.api_version < 18) {
GTEST_SKIP() << "Test for versions 18 and up only.";
}
// TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for
// provisioning 4. Disabled here temporarily.
if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) {
@@ -736,14 +736,8 @@ TEST_F(OEMCryptoLoadsCertificate, SignProvisioningRequest) {
GTEST_SKIP() << "Test for non Prov 4.0 devices only.";
}
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
if (global_features.provisioning_method == OEMCrypto_OEMCertificate) {
s.LoadOEMCert(true);
} else {
EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox);
s.GenerateDerivedKeysFromKeybox(keybox_);
}
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
ASSERT_NO_FATAL_FAILURE(provisioning_messages.PrepareSession(keybox_));
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
}
@@ -755,16 +749,10 @@ TEST_F(OEMCryptoLoadsCertificate, SignLargeProvisioningRequestAPI16) {
GTEST_SKIP() << "Test for non Prov 4.0 devices only.";
}
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
if (global_features.provisioning_method == OEMCrypto_OEMCertificate) {
s.LoadOEMCert(true);
} else {
EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox);
s.GenerateDerivedKeysFromKeybox(keybox_);
}
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
const size_t max_size = GetResourceValue(kLargeMessageSize);
provisioning_messages.set_message_size(max_size);
ASSERT_NO_FATAL_FAILURE(provisioning_messages.PrepareSession(keybox_));
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
}
@@ -779,7 +767,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) {
}
Session s;
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
provisioning_messages.PrepareSession(keybox_);
ASSERT_NO_FATAL_FAILURE(provisioning_messages.PrepareSession(keybox_));
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
@@ -913,6 +901,9 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30_API16) {
// TODO(b/144186970): This test should also run on Prov 3.0 devices.
TEST_F(OEMCryptoLoadsCertificate,
CertificateProvisionBadSignatureKeyboxTestAPI16) {
if (global_features.provisioning_method != OEMCrypto_Keybox) {
GTEST_SKIP() << "Test for Prov 2.0 devices only.";
}
// TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for
// provisioning 4. Disabled here temporarily.
if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) {
@@ -977,6 +968,9 @@ TEST_F(OEMCryptoLoadsCertificate,
if (global_features.provisioning_method != OEMCrypto_Keybox) {
GTEST_SKIP() << "Test for Prov 2.0 devices only.";
}
if (global_features.provisioning_method != OEMCrypto_Keybox) {
GTEST_SKIP() << "Test for Prov 2.0 devices only.";
}
// TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for
// provisioning 4. Disabled here temporarily.
if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) {
@@ -1243,141 +1237,4 @@ TEST_F(OEMCryptoLoadsCertificate, SupportsCertificatesAPI13) {
<< "Supported certificates is only " << OEMCrypto_SupportedCertificates();
}
// This test is not run by default, because it takes a long time and
// is used to measure RSA performance, not test functionality.
TEST_F(OEMCryptoLoadsCertificate, RSAPerformance) {
// TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for
// provisioning 4. Disabled here temporarily.
if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) {
GTEST_SKIP() << "Test for non Prov 4.0 devices only.";
}
const std::chrono::milliseconds kTestDuration(5000);
OEMCryptoResult sts;
std::chrono::steady_clock clock;
wvutil::TestSleep::Sleep(kShortSleep); // Make sure we are not nonce limited.
auto start_time = clock.now();
int count = 15;
for (int i = 0; i < count; i++) { // Only 20 nonce available.
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
}
auto delta_time = clock.now() - start_time;
const double provision_time =
delta_time / std::chrono::milliseconds(1) / count;
Session session;
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
start_time = clock.now();
count = 0;
while (clock.now() - start_time < kTestDuration) {
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_));
const size_t size = 50;
vector<uint8_t> licenseRequest(size);
GetRandBytes(licenseRequest.data(), licenseRequest.size());
size_t signature_length = 0;
sts = OEMCrypto_GenerateRSASignature(s.session_id(), licenseRequest.data(),
licenseRequest.size(), nullptr,
&signature_length, kSign_RSASSA_PSS);
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
ASSERT_NE(static_cast<size_t>(0), signature_length);
if (ShouldGenerateCorpus()) {
const std::string file_name =
GetFileName("oemcrypto_generate_rsa_signature_fuzz_seed_corpus");
OEMCrypto_Generate_RSA_Signature_Fuzz fuzzed_structure;
fuzzed_structure.padding_scheme = kSign_RSASSA_PSS;
fuzzed_structure.signature_length = signature_length;
// Cipher mode and algorithm.
AppendToFile(file_name, reinterpret_cast<const char*>(&fuzzed_structure),
sizeof(fuzzed_structure));
AppendToFile(file_name,
reinterpret_cast<const char*>(licenseRequest.data()),
licenseRequest.size());
}
std::vector<uint8_t> signature(signature_length, 0);
sts = OEMCrypto_GenerateRSASignature(
s.session_id(), licenseRequest.data(), licenseRequest.size(),
signature.data(), &signature_length, kSign_RSASSA_PSS);
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
count++;
}
delta_time = clock.now() - start_time;
const double license_request_time =
delta_time / std::chrono::milliseconds(1) / count;
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_));
vector<uint8_t> session_key;
vector<uint8_t> enc_session_key;
ASSERT_NO_FATAL_FAILURE(s.SetRsaPublicKeyFromPrivateKeyInfo(
encoded_rsa_key_.data(), encoded_rsa_key_.size()));
ASSERT_TRUE(s.GenerateRsaSessionKey(&session_key, &enc_session_key));
vector<uint8_t> mac_context;
vector<uint8_t> enc_context;
s.FillDefaultContext(&mac_context, &enc_context);
enc_session_key = wvutil::a2b_hex(
"7789c619aa3b9fa3c0a53f57a4abc6"
"02157c8aa57e3c6fb450b0bea22667fb"
"0c3200f9d9d618e397837c720dc2dadf"
"486f33590744b2a4e54ca134ae7dbf74"
"434c2fcf6b525f3e132262f05ea3b3c1"
"198595c0e52b573335b2e8a3debd0d0d"
"d0306f8fcdde4e76476be71342957251"
"e1688c9ca6c1c34ed056d3b989394160"
"cf6937e5ce4d39cc73d11a2e93da21a2"
"fa019d246c852fe960095b32f120c3c2"
"7085f7b64aac344a68d607c0768676ce"
"d4c5b2d057f7601921b453a451e1dea0"
"843ebfef628d9af2784d68e86b730476"
"e136dfe19989de4be30a4e7878efcde5"
"ad2b1254f80c0c5dd3cf111b56572217"
"b9f58fc1dacbf74b59d354a1e62cfa0e"
"bf");
start_time = clock.now();
while (clock.now() - start_time < kTestDuration) {
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_DeriveKeysFromSessionKey(
s.session_id(), enc_session_key.data(),
enc_session_key.size(), mac_context.data(),
mac_context.size(), enc_context.data(), enc_context.size()));
count++;
}
delta_time = clock.now() - start_time;
const double derive_keys_time =
delta_time / std::chrono::milliseconds(1) / count;
OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel();
printf(
"PERF:head, security, provision (ms), lic req(ms), derive "
"keys(ms)\n");
printf("PERF:stat, %u, %8.3f, %8.3f, %8.3f\n",
static_cast<unsigned int>(level), provision_time, license_request_time,
derive_keys_time);
}
// Test DeriveKeysFromSessionKey using the maximum size for the HMAC context.
TEST_F(OEMCryptoUsesCertificate, GenerateDerivedKeysLargeBuffer) {
vector<uint8_t> session_key;
vector<uint8_t> enc_session_key;
ASSERT_TRUE(session_.GenerateSessionKey(&session_key, &enc_session_key));
const size_t max_size = GetResourceValue(kLargeMessageSize);
vector<uint8_t> mac_context(max_size);
vector<uint8_t> enc_context(max_size);
// Stripe the data so the two vectors are not identical, and not all zeroes.
for (size_t i = 0; i < max_size; i++) {
mac_context[i] = i % 0x100;
enc_context[i] = (3 * i) % 0x100;
}
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_DeriveKeysFromSessionKey(
session_.session_id(), enc_session_key.data(),
enc_session_key.size(), mac_context.data(), mac_context.size(),
enc_context.data(), enc_context.size()));
}
} // namespace wvoec

View File

@@ -599,11 +599,10 @@ TEST_F(OEMCryptoSessionTests,
TEST_F(OEMCryptoMemoryLicenseTest,
OEMCryptoMemoryDecryptHashForHugeHashBuffer) {
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());
auto f = [session_id]() {
const uint32_t frame_number = 1;
const uint32_t crc32 = 0;
return OEMCrypto_SetDecryptHash(session_id, frame_number, crc32);
};
TestHugeLengthDoesNotCrashAPI(f, kCheckStatus);
}

View File

@@ -281,6 +281,9 @@ class OEMCryptoEntitlementLicenseTest : public OEMCryptoLicenseTest {
/** This verifies that entitlement keys and entitled content keys can be loaded.
*/
TEST_P(OEMCryptoEntitlementLicenseTest, LoadEntitlementKeysAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
@@ -295,40 +298,15 @@ TEST_P(OEMCryptoEntitlementLicenseTest, LoadEntitlementKeysAPI17) {
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(true));
}
TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
entitled_message_1.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/true, OEMCrypto_SUCCESS));
EntitledMessage entitled_message_2(&license_messages_);
entitled_message_2.FillKeyArray();
entitled_message_2.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/false, OEMCrypto_SUCCESS));
EntitledMessage entitled_message_3(&license_messages_);
entitled_message_3.FillKeyArray();
entitled_message_3.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_3.LoadCasKeys(
/*load_even=*/false, /*load_odd=*/true, OEMCrypto_SUCCESS));
ASSERT_NO_FATAL_FAILURE(entitled_message_3.LoadCasKeys(
/*load_even=*/false, /*load_odd=*/false, OEMCrypto_SUCCESS));
}
/**
* This verifies that entitled content keys cannot be loaded if we have not yet
* loaded the entitlement keys.
*/
TEST_P(OEMCryptoEntitlementLicenseTest,
LoadEntitlementKeysNoEntitlementKeysAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
@@ -343,53 +321,14 @@ TEST_P(OEMCryptoEntitlementLicenseTest,
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false));
}
/**
* This verifies that entitled content keys cannot be loaded if we have loaded
* the wrong entitlement keys.
*/
TEST_P(OEMCryptoEntitlementLicenseTest,
CasOnlyLoadCasKeysNoEntitlementKeysAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
entitled_message_1.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/true, OEMCrypto_ERROR_INVALID_CONTEXT));
}
/**
* This verifies that entitled content keys cannot be loaded if we have loaded
* the wrong entitlement keys.
*/
TEST_P(OEMCryptoEntitlementLicenseTest,
LoadEntitlementKeysWrongEntitlementKeysAPI17) {
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
const std::string key_id = "no_key";
entitled_message_1.SetEntitlementKeyId(0, key_id);
entitled_message_1.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false));
}
TEST_P(OEMCryptoEntitlementLicenseTest,
CasOnlyLoadCasKeysWrongEntitlementKeysAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
@@ -401,8 +340,7 @@ TEST_P(OEMCryptoEntitlementLicenseTest,
const std::string key_id = "no_key";
entitled_message_1.SetEntitlementKeyId(0, key_id);
entitled_message_1.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/true, OEMCrypto_KEY_NOT_ENTITLED));
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false));
}
/**
@@ -411,22 +349,8 @@ TEST_P(OEMCryptoEntitlementLicenseTest,
*/
TEST_P(OEMCryptoEntitlementLicenseTest,
LoadEntitlementKeysWrongEntitledKeySessionAPI17) {
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
const uint32_t wrong_key_session_id = key_session_id == 0 ? 1 : 0;
entitled_message_1.SetEntitledKeySession(wrong_key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false));
}
TEST_P(OEMCryptoEntitlementLicenseTest,
CasOnlyLoadCasKeysWrongEntitledKeySessionAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
@@ -437,54 +361,9 @@ TEST_P(OEMCryptoEntitlementLicenseTest,
entitled_message_1.FillKeyArray();
const uint32_t wrong_key_session_id = key_session_id == 0 ? 1 : 0;
entitled_message_1.SetEntitledKeySession(wrong_key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/true,
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION));
}
/**
* This verifies that entitled content keys cannot be loaded if we specify an
* entitled key session that is actually an oemcrypto session.
*/
TEST_P(OEMCryptoEntitlementLicenseTest,
LoadEntitlementKeysOemcryptoSessionAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
if (session_.session_id() == key_session_id) {
GTEST_SKIP()
<< "Skipping test because entitled and entitlement sessions are both "
<< key_session_id << ".";
}
entitled_message_1.SetEntitledKeySession(session_.session_id());
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false));
}
TEST_P(OEMCryptoEntitlementLicenseTest,
CasOnlyLoadCasKeysOemcryptoSessionAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
entitled_message_1.SetEntitledKeySession(session_.session_id());
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/true,
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION));
}
INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoEntitlementLicenseTest,
Range<uint32_t>(kCoreMessagesAPI, kCurrentAPI + 1));
@@ -697,6 +576,9 @@ TEST_F(OEMCryptoMemoryLicenseTest,
/// @{
TEST_P(OEMCryptoLicenseTest, GetKeyHandleEntitledKeyAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
@@ -724,6 +606,9 @@ TEST_P(OEMCryptoLicenseTest, GetKeyHandleEntitledKeyAPI17) {
// SelectEntitledKey should fail if we attempt to select a key that has not been
// loaded. Also, the error should be NO_CONTENT_KEY.
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyNotThereAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
@@ -745,41 +630,11 @@ TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyNotThereAPI17) {
strlen(content_key_id)));
}
/**
* Select key with entitlement license fails if the key id is entitlement key
* id.
*/
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitlementKeyAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
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());
uint32_t key_session_id;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
entitled_message_1.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
if (session_.session_id() == key_session_id) {
GTEST_SKIP()
<< "Skipping test because entitled and entitlement sessions are both "
<< key_session_id << ".";
}
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
OEMCrypto_ERROR_INVALID_CONTEXT, session_.session_id(),
session_.license().keys[0].key_id,
session_.license().keys[0].key_id_length));
}
// This verifies that entitled key sessions can be created and removed.
TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
@@ -806,6 +661,9 @@ TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI17) {
TEST_P(OEMCryptoLicenseTest,
EntitledKeySessionsCloseWithOEMCryptoSessionAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
@@ -825,76 +683,13 @@ TEST_P(OEMCryptoLicenseTest,
session_.open();
}
// This verifies that multiple entitled key sessions can be created. They can
// load and select keys independently.
TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
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());
uint32_t key_session_id_1;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id_1));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
entitled_message_1.SetEntitledKeySession(key_session_id_1);
const char* content_key_id_1 = "content_key_id_1";
entitled_message_1.SetContentKeyId(0, content_key_id_1);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
// We can select content key 1 in entitled key session 1.
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
OEMCrypto_SUCCESS, key_session_id_1,
reinterpret_cast<const uint8_t*>(content_key_id_1),
strlen(content_key_id_1)));
// Create another entitled key session.
uint32_t key_session_id_2;
OEMCryptoResult status = OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id_2);
// For DRM, but not for CAS, we allow there to be only a single entitled
// session.
if (status == OEMCrypto_ERROR_TOO_MANY_SESSIONS) {
GTEST_SKIP()
<< "Skipping test because multiple entitled sessions not supported.";
}
ASSERT_EQ(OEMCrypto_SUCCESS, status);
// Entitled key sessions should have unique ids.
ASSERT_NE(key_session_id_1, key_session_id_2);
EntitledMessage entitled_message_2(&license_messages_);
entitled_message_2.FillKeyArray();
entitled_message_2.SetEntitledKeySession(key_session_id_2);
const char* content_key_id_2 = "content_key_id_2";
entitled_message_2.SetContentKeyId(0, content_key_id_2);
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(true));
// We can select content key 2 in entitled key session 2.
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
OEMCrypto_SUCCESS, key_session_id_2,
reinterpret_cast<const uint8_t*>(content_key_id_2),
strlen(content_key_id_2)));
// Content key id 1 is not in entitled key session 2.
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id_2,
reinterpret_cast<const uint8_t*>(content_key_id_1),
strlen(content_key_id_1)));
// Content key id 2 is not in entitled key session 1.
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id_1,
reinterpret_cast<const uint8_t*>(content_key_id_2),
strlen(content_key_id_2)));
}
// This verifies that within an entitled key session, each entitlement key can
// corresponds to only one content key at most.
TEST_P(OEMCryptoLicenseTest,
EntitledKeySessionOneContentKeyPerEntitlementAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
@@ -940,6 +735,9 @@ TEST_P(OEMCryptoLicenseTest,
// instead).
TEST_P(OEMCryptoLicenseTest,
RejectOecSessionDecryptWithEntitlementLicenseAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
@@ -979,6 +777,9 @@ TEST_P(OEMCryptoLicenseTest,
// This verifies that an entitled key session can be reassociated to an
// OEMCrypto session.
TEST_P(OEMCryptoEntitlementLicenseTest, ReassociateEntitledKeySessionAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
@@ -1480,7 +1281,260 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoLicenseOverflowTest,
/// @addtogroup cas
/// @{
TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
entitled_message_1.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/true, OEMCrypto_SUCCESS));
EntitledMessage entitled_message_2(&license_messages_);
entitled_message_2.FillKeyArray();
entitled_message_2.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/false, OEMCrypto_SUCCESS));
EntitledMessage entitled_message_3(&license_messages_);
entitled_message_3.FillKeyArray();
entitled_message_3.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_3.LoadCasKeys(
/*load_even=*/false, /*load_odd=*/true, OEMCrypto_SUCCESS));
ASSERT_NO_FATAL_FAILURE(entitled_message_3.LoadCasKeys(
/*load_even=*/false, /*load_odd=*/false, OEMCrypto_SUCCESS));
}
/**
* This verifies that entitled content keys cannot be loaded if we have loaded
* the wrong entitlement keys.
*/
TEST_P(OEMCryptoEntitlementLicenseTest,
CasOnlyLoadCasKeysNoEntitlementKeysAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
entitled_message_1.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/true, OEMCrypto_ERROR_INVALID_CONTEXT));
}
TEST_P(OEMCryptoEntitlementLicenseTest,
CasOnlyLoadCasKeysWrongEntitlementKeysAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
const std::string key_id = "no_key";
entitled_message_1.SetEntitlementKeyId(0, key_id);
entitled_message_1.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/true, OEMCrypto_KEY_NOT_ENTITLED));
}
TEST_P(OEMCryptoEntitlementLicenseTest,
CasOnlyLoadCasKeysWrongEntitledKeySessionAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
const uint32_t wrong_key_session_id = key_session_id == 0 ? 1 : 0;
entitled_message_1.SetEntitledKeySession(wrong_key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/true,
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION));
}
/**
* This verifies that entitled content keys cannot be loaded if we specify an
* entitled key session that is actually an oemcrypto session.
*/
TEST_P(OEMCryptoEntitlementLicenseTest,
LoadEntitlementKeysOemcryptoSessionAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
if (session_.session_id() == key_session_id) {
GTEST_SKIP()
<< "Skipping test because entitled and entitlement sessions are both "
<< key_session_id << ".";
}
entitled_message_1.SetEntitledKeySession(session_.session_id());
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false));
}
TEST_P(OEMCryptoEntitlementLicenseTest,
CasOnlyLoadCasKeysOemcryptoSessionAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
LoadEntitlementLicense();
uint32_t key_session_id = 0;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
entitled_message_1.SetEntitledKeySession(session_.session_id());
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
/*load_even=*/true, /*load_odd=*/true,
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION));
}
/**
* Select key with entitlement license fails if the key id is entitlement key
* id.
*/
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitlementKeyAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
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());
uint32_t key_session_id;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
entitled_message_1.SetEntitledKeySession(key_session_id);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
if (session_.session_id() == key_session_id) {
GTEST_SKIP()
<< "Skipping test because entitled and entitlement sessions are both "
<< key_session_id << ".";
}
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
OEMCrypto_ERROR_INVALID_CONTEXT, session_.session_id(),
session_.license().keys[0].key_id,
session_.license().keys[0].key_id_length));
}
// This verifies that multiple entitled key sessions can be created. They can
// load and select keys independently.
TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) {
if (!global_features.supports_cas) {
GTEST_SKIP() << "OEMCrypto does not support CAS";
}
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
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());
uint32_t key_session_id_1;
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id_1));
EntitledMessage entitled_message_1(&license_messages_);
entitled_message_1.FillKeyArray();
entitled_message_1.SetEntitledKeySession(key_session_id_1);
const char* content_key_id_1 = "content_key_id_1";
entitled_message_1.SetContentKeyId(0, content_key_id_1);
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true));
// We can select content key 1 in entitled key session 1.
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
OEMCrypto_SUCCESS, key_session_id_1,
reinterpret_cast<const uint8_t*>(content_key_id_1),
strlen(content_key_id_1)));
// Create another entitled key session.
uint32_t key_session_id_2;
OEMCryptoResult status = OEMCrypto_CreateEntitledKeySession(
session_.session_id(), &key_session_id_2);
// For DRM, but not for CAS, we allow there to be only a single entitled
// session.
if (!global_features.supports_cas &&
(key_session_id_2 == key_session_id_1 ||
status == OEMCrypto_ERROR_TOO_MANY_SESSIONS)) {
GTEST_SKIP()
<< "Skipping test because multiple entitled sessions not supported.";
}
ASSERT_EQ(OEMCrypto_SUCCESS, status);
// Entitled key sessions should have unique ids.
ASSERT_NE(key_session_id_1, key_session_id_2);
EntitledMessage entitled_message_2(&license_messages_);
entitled_message_2.FillKeyArray();
entitled_message_2.SetEntitledKeySession(key_session_id_2);
const char* content_key_id_2 = "content_key_id_2";
entitled_message_2.SetContentKeyId(0, content_key_id_2);
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(true));
// We can select content key 2 in entitled key session 2.
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
OEMCrypto_SUCCESS, key_session_id_2,
reinterpret_cast<const uint8_t*>(content_key_id_2),
strlen(content_key_id_2)));
// Content key id 1 is not in entitled key session 2.
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id_2,
reinterpret_cast<const uint8_t*>(content_key_id_1),
strlen(content_key_id_1)));
// Content key id 2 is not in entitled key session 1.
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled(
OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id_1,
reinterpret_cast<const uint8_t*>(content_key_id_2),
strlen(content_key_id_2)));
}
/// @}
/// @addtogroup security

View File

@@ -21,15 +21,12 @@
namespace wvoec {
// These tests are required for LollyPop Android devices.
/** These tests are required for LollyPop Android devices.*/
class OEMCryptoAndroidLMPTest : public ::testing::Test {
protected:
void SetUp() override {
OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox));
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize());
if (OEMCrypto_GetProvisioningMethod() == OEMCrypto_BootCertificateChain) {
GTEST_SKIP() << "Test for non Prov 4.0 devices only.";
}
OEMCrypto_SetMaxAPIVersion(kCurrentAPI);
OEMCrypto_EnterTestMode();
}
@@ -37,34 +34,7 @@ class OEMCryptoAndroidLMPTest : public ::testing::Test {
void TearDown() override { OEMCrypto_Terminate(); }
};
// Android devices must have a keybox, or use provisioning 3.0.
TEST_F(OEMCryptoAndroidLMPTest, GetKeyDataImplemented) {
if (global_features.provisioning_method != OEMCrypto_Keybox &&
global_features.provisioning_method != OEMCrypto_OEMCertificate) {
GTEST_SKIP() << "Test for Prov 2.0 and 3.0 devices only.";
}
uint8_t key_data[256];
size_t key_data_len = sizeof(key_data);
if (OEMCrypto_Keybox == OEMCrypto_GetProvisioningMethod()) {
ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED,
OEMCrypto_GetKeyData(key_data, &key_data_len));
} else {
ASSERT_EQ(OEMCrypto_OEMCertificate, OEMCrypto_GetProvisioningMethod());
}
}
// Android devices must have a valid keybox.
TEST_F(OEMCryptoAndroidLMPTest, ValidKeybox) {
if (OEMCrypto_GetProvisioningMethod() == OEMCrypto_Keybox) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid());
}
}
TEST_F(OEMCryptoAndroidLMPTest, MinVersionNumber9) {
uint32_t version = OEMCrypto_APIVersion();
ASSERT_LE(9u, version);
}
/** Android devices that use Provisioning 2.0 must have a valid keybox. */
TEST_F(OEMCryptoAndroidLMPTest, ValidKeyboxTest) {
if (global_features.provisioning_method != OEMCrypto_Keybox) {
GTEST_SKIP() << "Test for Prov 2.0 devices only.";
@@ -72,13 +42,15 @@ TEST_F(OEMCryptoAndroidLMPTest, ValidKeyboxTest) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid());
}
/** Android devices must support remote provisioning. Either Provisioning 2, 3
* or 4. */
TEST_F(OEMCryptoAndroidLMPTest, RewrapDeviceRSAKeyImplemented) {
ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED,
OEMCrypto_LoadProvisioning(0, nullptr, 0, 0, nullptr, 0, nullptr,
nullptr));
OEMCrypto_LoadProvisioning(0, nullptr, 0, nullptr, 0, 0, nullptr, 0,
nullptr, 0));
}
// The Generic Crypto API functions are required for Android.
/** The Generic Crypto API functions are required for Android. */
TEST_F(OEMCryptoAndroidLMPTest, GenericCryptoImplemented) {
ASSERT_NE(
OEMCrypto_ERROR_NOT_IMPLEMENTED,
@@ -96,13 +68,15 @@ TEST_F(OEMCryptoAndroidLMPTest, GenericCryptoImplemented) {
OEMCrypto_HMAC_SHA256, nullptr, 0));
}
// Android requires support of usage table. The usage table is used for Secure
// Stops and for offline licenses.
/** Android requires support of usage table. The usage table is used for
* offline licenses. */
TEST_F(OEMCryptoAndroidLMPTest, SupportsUsageTable) {
ASSERT_TRUE(OEMCrypto_SupportsUsageTable());
}
// Android devices require L1 OEMCrypto.
/** Most Android GMS devices require L1 OEMCrypto. This is not a hard
* requirement for all devices, but is a source of common errors, so we test for
* it here. */
TEST_F(OEMCryptoAndroidLMPTest, Level1Required) {
OEMCrypto_Security_Level security_level = OEMCrypto_SecurityLevel();
EXPECT_EQ(OEMCrypto_Level1, security_level)
@@ -111,32 +85,24 @@ TEST_F(OEMCryptoAndroidLMPTest, Level1Required) {
<< "repeat the tests with the flag --gtest_filter=\"*-*Level1Required\"";
}
// These tests are required for M Android devices.
/** These tests are required for M Android devices. */
class OEMCryptoAndroidMNCTest : public OEMCryptoAndroidLMPTest {};
TEST_F(OEMCryptoAndroidMNCTest, MinVersionNumber10) {
uint32_t version = OEMCrypto_APIVersion();
ASSERT_GE(version, 10u);
}
// Android devices using Provisioning 2.0 must be able to load a test keybox.
// If they are not using Provisioning 2.0, then they must use Provisioning 3.0.
/** Android devices using Provisioning 2.0 must be able to load a test keybox.
* If they are not using Provisioning 2.0, then they must use Provisioning 3 or
* 4. */
TEST_F(OEMCryptoAndroidMNCTest, LoadsTestKeyboxImplemented) {
if (global_features.provisioning_method != OEMCrypto_Keybox) {
GTEST_SKIP() << "Test for Prov 2.0 devices only.";
}
if (OEMCrypto_Keybox == OEMCrypto_GetProvisioningMethod()) {
ASSERT_EQ(
OEMCrypto_SUCCESS,
OEMCrypto_LoadTestKeybox(reinterpret_cast<const uint8_t*>(&kTestKeybox),
sizeof(kTestKeybox)));
} else {
// Android should use keybox or provisioning 3.0.
ASSERT_EQ(OEMCrypto_OEMCertificate, OEMCrypto_GetProvisioningMethod());
}
ASSERT_EQ(
OEMCrypto_SUCCESS,
OEMCrypto_LoadTestKeybox(reinterpret_cast<const uint8_t*>(&kTestKeybox),
sizeof(kTestKeybox)));
}
// Android requires implementation of these functions.
/** Android requires implementation of functions that report how many open
* sesions are available. */
TEST_F(OEMCryptoAndroidMNCTest, NumberOfSessionsImplemented) {
ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED,
OEMCrypto_GetNumberOfOpenSessions(nullptr));
@@ -144,34 +110,20 @@ TEST_F(OEMCryptoAndroidMNCTest, NumberOfSessionsImplemented) {
OEMCrypto_GetMaxNumberOfSessions(nullptr));
}
// Android requires implementation of these functions.
/** Android requires implementation of `OEMCrypto_QueryKeyControl`. */
TEST_F(OEMCryptoAndroidMNCTest, QueryKeyControlImplemented) {
ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED,
OEMCrypto_QueryKeyControl(0, nullptr, 0, nullptr, nullptr));
}
// These tests are required for N Android devices.
class OEMCryptoAndroidNYCTest : public OEMCryptoAndroidMNCTest {};
/** These tests are required for R Android devices. */
class OEMCryptoAndroidRVCTest : public OEMCryptoAndroidMNCTest {};
TEST_F(OEMCryptoAndroidNYCTest, MinVersionNumber11) {
/** Minimum OEMCrypto version 16 is required for all Android R and later
* releases. */
TEST_F(OEMCryptoAndroidRVCTest, MinVersionNumber16) {
uint32_t version = OEMCrypto_APIVersion();
ASSERT_GE(version, 11u);
}
// These tests are required for O MR1 Android devices.
class OEMCryptoAndroidOCTest : public OEMCryptoAndroidNYCTest {};
TEST_F(OEMCryptoAndroidOCTest, MinVersionNumber13) {
uint32_t version = OEMCrypto_APIVersion();
ASSERT_GE(version, 13u);
}
// These tests are required for Q Android devices.
class OEMCryptoAndroidQTest : public OEMCryptoAndroidOCTest {};
TEST_F(OEMCryptoAndroidQTest, MinVersionNumber14) {
uint32_t version = OEMCrypto_APIVersion();
ASSERT_GE(version, 15u);
ASSERT_GE(version, 16u);
}
} // namespace wvoec

View File

@@ -19,7 +19,6 @@ static void acknowledge_cast() {
// Also, the test filter is updated based on the feature list.
int main(int argc, char** argv) {
bool is_cast_receiver = false;
bool filter_tests = true;
int verbosity = 0;
// Skip the first element, which is the program name.
const std::vector<std::string> args(argv + 1, argv + argc);
@@ -37,9 +36,6 @@ int main(int argc, char** argv) {
std::cerr << "The argument --force_load_test_keybox is obsolete.\n";
return 1;
}
if (arg == "--no_filter") {
filter_tests = false;
}
if (arg == "--fake_sleep") {
wvutil::TestSleep::set_real_sleep(false);
}
@@ -55,11 +51,5 @@ int main(int argc, char** argv) {
}
// Init GTest after device properties has been initialized.
::testing::InitGoogleTest(&argc, argv);
// If the user requests --no_filter, we don't change the filter, otherwise, we
// filter out features that are not supported.
if (filter_tests) {
::testing::GTEST_FLAG(filter) =
wvoec::global_features.RestrictFilter(::testing::GTEST_FLAG(filter));
}
return RUN_ALL_TESTS();
}

View File

@@ -13,6 +13,9 @@ namespace wvoec {
// Test that successive calls to PrepAndSignProvisioningRequest only increase
// the provisioning count in the ODK message
TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) {
if (wvoec::global_features.api_version < 18) {
GTEST_SKIP() << "Test for versions 18 and up only.";
}
// local struct to hold count values from core message
typedef struct counts {
uint32_t prov;
@@ -90,6 +93,9 @@ TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) {
// Test that successive calls to PrepAndSignLicenseRequest only increase
// the license count in the ODK message
TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) {
if (wvoec::global_features.api_version < 18) {
GTEST_SKIP() << "Test for versions 18 and up only.";
}
Session s;
s.open();
LicenseRoundTrip license_messages(&s);
@@ -128,6 +134,9 @@ TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) {
// it is incremented correctly after usage table modification (save offline
// license) and decrypt. Also test that decrypt count increments.
TEST_F(OEMCryptoSessionTests, MasterGeneration_IncrementCounterAPI18) {
if (wvoec::global_features.api_version < 18) {
GTEST_SKIP() << "Test for versions 18 and up only.";
}
if (!OEMCrypto_SupportsUsageTable()) {
GTEST_SKIP() << "Usage table not supported, so master generation number "
"does not need to be checked.";
@@ -642,6 +651,28 @@ TEST_P(OEMCryptoUsageTableTest, OfflineLicenseRefresh) {
ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive));
}
// Test that an offline license can be loaded and that the license can be
// released
TEST_P(OEMCryptoUsageTableTest, OfflineLicenseReleaseAPI19) {
// License release is new in OEMCrypto v19.
if (wvoec::global_features.api_version < 19 || license_api_version_ < 19) {
GTEST_SKIP() << "Test for versions 19 and up only.";
}
LicenseWithUsageEntry entry;
entry.license_messages().set_api_version(license_api_version_);
entry.MakeAndLoad(this, wvoec::kControlNonceOrEntry);
Session& s = entry.session();
ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR());
// License release message is signed by client and verified by the server.
ReleaseRoundTrip release_messages(&entry.license_messages());
MakeReleaseRequest(&release_messages);
LoadRelease(&release_messages, OEMCrypto_SUCCESS);
ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_));
ASSERT_NO_FATAL_FAILURE(
entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE));
}
// Test that an offline license can be reloaded in a new session.
TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicense) {
LicenseWithUsageEntry entry;
@@ -1235,6 +1266,9 @@ TEST_P(OEMCryptoUsageTableDefragTest, ManyUsageEntries) {
// Verify that usage entries can be created in the position of existing entry
// indexes.
TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
LicenseWithUsageEntry entry0;
entry0.set_pst("pst 0");
LicenseWithUsageEntry entry1;
@@ -1252,6 +1286,9 @@ TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryAPI17) {
// Verify that usage entries cannot replace an entry that is currently in
// use by a session.
TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryIndexInUseAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
LicenseWithUsageEntry entry0;
entry0.set_pst("pst 0");
LicenseWithUsageEntry entry1;
@@ -1268,6 +1305,9 @@ TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryIndexInUseAPI17) {
// Verify that usage entries cannot be created if the usage entry index is
// too large.
TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryWithInvalidIndexAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
LicenseWithUsageEntry entry0;
entry0.set_pst("pst 0");
LicenseWithUsageEntry entry1;
@@ -1287,6 +1327,9 @@ TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryWithInvalidIndexAPI17) {
// entry.
TEST_P(OEMCryptoUsageTableDefragTest,
ReuseUsageEntrySessionAlreadyHasEntryAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
LicenseWithUsageEntry entry;
entry.set_pst("pst 0");
@@ -1620,6 +1663,13 @@ class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest {
// clang-format on
TEST_P(OEMCryptoUsageTableTestWallClock, TimeRollbackPrevention) {
// This test may require root access. If user is not root, filter this test
// out.
if (!wvutil::TestSleep::CanChangeSystemTime()) {
GTEST_SKIP() << "Filtering out TimeRollbackPrevention.";
} else {
printf("Can change time. I will run TimeRollbackPrevention.\n");
}
cout << "This test temporarily rolls back the system time in order to "
"verify "
<< "that the usage report accounts for the change. After the test, it "
@@ -1729,6 +1779,9 @@ TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSession) {
// Verify that a usage entry with an invalid session cannot be used.
TEST_P(OEMCryptoUsageTableTest, ReuseUsageEntryWithInvalidSessionAPI17) {
if (wvoec::global_features.api_version < 17) {
GTEST_SKIP() << "Test for versions 17 and up only.";
}
std::string pst("pst");
LicenseWithUsageEntry entry;
entry.license_messages().set_pst(pst);

View File

@@ -173,15 +173,11 @@ class OTAKeyboxProvisioningTest : public ::testing::Test, public SessionUtil {
TEST_F(OTAKeyboxProvisioningTest, BasicTest) {
OEMCryptoResult result = OEMCrypto_IsKeyboxValid();
if (result == OEMCrypto_SUCCESS) {
cout << " "
<< "Keybox valid after initialization. Skipping rest of test." << endl;
return;
GTEST_SKIP() << "Keybox valid after initialization. Skipping rest of test.";
}
if (result != OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING) {
cout << " "
<< "OTA Keybox functions not supported. Skipping rest of test."
<< endl;
return;
GTEST_SKIP()
<< "OTA Keybox functions not supported. Skipping rest of test.";
}
cout << " "
<< "OTA Keybox functions supported. Device needs provisioning." << endl;
@@ -235,28 +231,11 @@ TEST_F(OTAKeyboxProvisioningTest, BasicTest) {
const std::vector<uint8_t> model_key = GetModelKey(device_id);
#endif
// The server should derive the same set of keys as the client.
const std::string mac_label = "WV_SIGN";
std::vector<uint8_t> mac_context(mac_label.begin(), mac_label.end());
mac_context.push_back(0);
std::copy(cert.begin(), cert.end(), std::back_inserter(mac_context));
std::copy(device_id.begin(), device_id.end(),
std::back_inserter(mac_context));
uint32_t bit_size = MAC_KEY_SIZE * 8 * 2;
std::string bit_size_string = wvutil::EncodeUint32(bit_size);
std::copy(bit_size_string.begin(), bit_size_string.end(),
std::back_inserter(mac_context));
std::string enc_label = "WV_ENCRYPT";
std::vector<uint8_t> enc_context(enc_label.begin(), enc_label.end());
enc_context.push_back(0);
std::copy(cert.begin(), cert.end(), std::back_inserter(enc_context));
std::copy(device_id.begin(), device_id.end(),
std::back_inserter(enc_context));
bit_size = KEY_SIZE * 8;
bit_size_string = wvutil::EncodeUint32(bit_size);
std::copy(bit_size_string.begin(), bit_size_string.end(),
std::back_inserter(enc_context));
KeyDeriver keys;
keys.DeriveKeys(model_key.data(), model_key.size(), mac_context, enc_context);
std::vector<uint8_t> context = cert;
context.insert(context.end(), device_id.begin(), device_id.end());
keys.DeriveKeys(model_key.data(), model_key.size(), context, "WV_SIGN",
"WV_ENCRYPT");
const std::vector<uint8_t> message(
request.data(),
request.data() + request.size() - HMAC_SHA256_SIGNATURE_SIZE);