Source release 19.1.0
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 LLVM’s
|
||||
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
|
||||
|
||||
171
oemcrypto/test/fuzz_tests/clusterfuzz_setup.md
Normal file
171
oemcrypto/test/fuzz_tests/clusterfuzz_setup.md
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
'-D_POSIX_C_SOURCE=200809L',
|
||||
],
|
||||
'cflags_cc': [
|
||||
'-std=c++14',
|
||||
'-std=c++17',
|
||||
],
|
||||
'ldflags': [
|
||||
'-fPIC',
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
],
|
||||
'cflags_cc' : [
|
||||
'-frtti',
|
||||
'-std=c++14',
|
||||
'-std=c++17',
|
||||
],
|
||||
'ldflags': [
|
||||
'-fPIC',
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
'-D_POSIX_C_SOURCE=200809L',
|
||||
],
|
||||
'cflags_cc': [
|
||||
'-std=c++14',
|
||||
'-std=c++17',
|
||||
'-frtti',
|
||||
],
|
||||
'ldflags': [
|
||||
|
||||
233
oemcrypto/test/install_keybox_tool.cpp
Normal file
233
oemcrypto/test/install_keybox_tool.cpp
Normal 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;
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user