Merge changes I3002f705,If33f0616,Ifb971bf0,If8fc484f,I65a879fb, ... into udc-dev
* changes: Filter RSA 3072 tests Add MemorySanitizer support for opk_ta tests Filter CAS tests Refactor missed provisioning and renewal tests Use GTEST_SKIP to skip prov40 tests Update test updates and known issues to ChangeLog Update oemcrypto unit tests version number Update CHANGELOG for late-breaking OPK v17.1.1 changes Filter Cast Reciver tests Document RSA keypair issue on OP-TEE 64-bit Use GTEST_SKIP to skip prov 3.0 tests Remove V17 backwards-compatibility decrypt functions Small changes to refactored unit tests Add CHANGELOG entry for OPK v17.1.1 Refactor usage table tests Refactor decrypt unit tests Update OPK v18 documentation Fix null passed to memcpy in generic verify fuzz Update documentation for Cast Document lacking signature of Prov 3.0 message
This commit is contained in:
committed by
Android (Google) Code Review
commit
68e1eac8ec
@@ -308,7 +308,7 @@ class CdmSession {
|
||||
// Only assign |usage_table_| if capable of supporting usage
|
||||
// information.
|
||||
CdmUsageTable* usage_table_ = nullptr;
|
||||
UsageEntryIndex usage_entry_index_;
|
||||
UsageEntryIndex usage_entry_index_ = 0;
|
||||
UsageEntry usage_entry_;
|
||||
std::string usage_provider_session_token_;
|
||||
|
||||
|
||||
@@ -677,7 +677,13 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[],
|
||||
// Figure out which tests are appropriate for OEMCrypto, based on features
|
||||
// supported.
|
||||
wvoec::global_features.Initialize();
|
||||
wvoec::global_features.set_cast_receiver(is_cast_receiver);
|
||||
if (is_cast_receiver) {
|
||||
// Turn it on if passed in on the command line. Do not turn these tests off
|
||||
// automtically -- instead, we'll let the caller filter them out if they
|
||||
// need to. These tests will normally only run if the device claims to
|
||||
// support being a cast receiver.
|
||||
wvoec::global_features.set_cast_receiver(is_cast_receiver);
|
||||
}
|
||||
// If the user requests --no_filter, we don't change the filter, otherwise, we
|
||||
// filter out features that are not supported.
|
||||
if (filter_tests) {
|
||||
|
||||
@@ -2,6 +2,207 @@
|
||||
|
||||
[TOC]
|
||||
|
||||
## [Version 18.1][v18.1]
|
||||
|
||||
OEMCrypto V18.0 consisted of header files only. This release includes tests and
|
||||
OPK. There have been minor API changes since v18.0, so the version number has
|
||||
been bumped to 18.1
|
||||
|
||||
In general, new v18 OEMCrypto features have been implemented in the OPK; since
|
||||
those have been covered already in the published v18 headers, they will not be
|
||||
discussed in detail here.
|
||||
|
||||
This OPK release includes OEMCrypto v18 changes outlined in the document "WV
|
||||
Modular DRM Version 18.1 Delta". In addition, quite a few OPK-specific changes
|
||||
have been added since the last release. Major changes are described in more
|
||||
detail below in individual sections, followed by a consolidated list of minor
|
||||
changes.
|
||||
|
||||
### OEMCrypto Unit Tests
|
||||
|
||||
The unit tests have been updated to test all v18.1 functionality.
|
||||
|
||||
We have also refactored the unit tests into several files grouped by category.
|
||||
|
||||
Previously, the unit tests modifed the `GTEST_FILTER` in the file
|
||||
`oec_device_features.cpp` in order to skip tests of functionality. For example
|
||||
keybox tests are skipped for devices that use Provisioning 4.0. We have begun
|
||||
replacing the modification of the `GTEST_FILTER` with the GTest macro
|
||||
`GTEST_SKIP`. Previously, skipped tests would not show up on the list of running
|
||||
tests in stdout. Now, any skipped test will start to run, and then a message
|
||||
will explain why it is being skipped. A list of skipped tests will be listed to
|
||||
stdout at the end of the test run.
|
||||
|
||||
The seed corpus for the oemcrypto fuzz tests has been updated using the updated
|
||||
unit tests.
|
||||
|
||||
### REE-side hooks
|
||||
|
||||
In oemcrypto/opk/serialization/ree/GEN_oemcrypto_api.c, new ifdef checks have
|
||||
been added in OEMCrypto_Initialize, OEMCrypto_Terminate, OEMCrypto_DecryptCENC,
|
||||
and OEMCrypto_LoadLicense. Depending on the macros, liboemcrypto can be compiled
|
||||
with hooks that execute before, after, or instead of the listed OEMCrypto
|
||||
functions (or some combination of the three). If a hook macro is defined, the
|
||||
default behavior is to call that hook function instead of calling into the TEE
|
||||
-- to do both, at least one more macro must be defined. For more detail, see
|
||||
GEN_oemcrypto_api.c directly.
|
||||
|
||||
For example, defining the macro `OPK_PRE_HOOK_OEMCRYPTO_DECRYPTCENC` will
|
||||
execute `OPK_PreHook_OEMCrypto_DecryptCENC()` instead of calling into the TEE.
|
||||
The same arguments will be passed to this function, which needs a custom
|
||||
implementation. One use case for this is custom decryption hardware that is
|
||||
accessible from the REE and can accept opaque decryption key handles intended
|
||||
for the TEE. The above macro can be used to execute the hook function instead of
|
||||
calling the TEE, and the hook function can pass the key handle and encrypted
|
||||
content to the decrypt hardware directly, bypassing the TEE.
|
||||
|
||||
Another example is in `OEMCrypto_Initialize()`. Defining the macro
|
||||
`OPK_PRE_HOOK_OEMCRYPTO_INITIALIZE` will execute a call to
|
||||
`OPK_Prehook_OEMCrypto_Initialize()` instead of calling into the TEE. Defining
|
||||
another macro `OPK_HOOK_AND_USE_TA_OEMCRYPTO_INITIALIZE` will include the call
|
||||
to the TEE after the pre-hook. The implementer is free to define custom REE-side
|
||||
initialization logic, and continue to let the TA finish its initialization as
|
||||
well.
|
||||
|
||||
One more use case: `OEMCrypto_LoadLicense()`. Some implementers have requested
|
||||
the ability to call a custom function after LoadLicense() returns in order to
|
||||
parse the license response and control output restrictions accordingly from the
|
||||
REE (HDCP, watermarking, etc). To do this, define
|
||||
`OPK_POST_HOOK_OEMCRYPTO_LOAD_LICENSE` and
|
||||
`OPK_HOOK_AND_USE_TA_OEMCRYPTO_LOAD_LICENSE` at build time and provide
|
||||
a function implementation for `OPK_Posthook_OEMCrypto_LoadLicense()`. The post
|
||||
hook function will execute after `OEMCrypto_LoadLicense()` comes back from the
|
||||
TEE and will use the same function arguments.
|
||||
|
||||
If no macros are defined, there is no change to GEN_oemcrypto_api.c and all
|
||||
OEMCrypto calls will be forwarded to the TEE without hooks.
|
||||
|
||||
### Configuration macros
|
||||
|
||||
Previously, a platform-specific port needed to define hard coded values in the
|
||||
implementation of `wtpi_config_interface.h`, as well as macro definitions in
|
||||
`wtpi_config_macros.h`. These values defined which features were available in
|
||||
the build, but they were difficult to modify for a new build, and it was often
|
||||
unclear what configuration values were enabled for a particular build.
|
||||
|
||||
These have been replaced with a single header file consisting of only macro
|
||||
definitions. For a full list of these configuration macros with default values,
|
||||
see oemcrypto/opk/oemcrypto_ta/wtpi_reference/config/default.h.
|
||||
|
||||
Each platform-specific port needs to provide configuration values in a file
|
||||
called `opk_config.h`. The OPK code looks for this file at compilation time. To
|
||||
provide flexibility at build time, these macro definitions should be `#ifndef`
|
||||
checked. This way, macros can be defined in the cflags to override the default
|
||||
values if desired (eg build a version that only changes the provisioning method
|
||||
by setting the `OPK_CONFIG_PROVISIONING_METHOD` macro in the cflags). The OP-TEE,
|
||||
Trusty, and Linux reference ports all have `opk_config.h` files that can be used
|
||||
as examples.
|
||||
|
||||
As part of the OEMCrypto v18 changes, OEMCrypto_BuildInformation() now outputs
|
||||
JSON-formatted text. If the OPK is built in debug mode, the build information
|
||||
will also include all of the configuration macro values as a new JSON field.
|
||||
The intent is to improve the debugging experience by providing as much
|
||||
configuration information as possible. With the changes to
|
||||
OEMCrypto_BuildInformation(), the `WTPI_BUILD_INFO` macro is no longer required.
|
||||
|
||||
### OP-TEE port changes
|
||||
|
||||
- Add implementations for provisioning 4.0 WTPI functions. This requires the
|
||||
third party library open-dice.
|
||||
- Pre-allocate crypto handles for DecryptCENC. Since this is
|
||||
a performance-sensitive path, allocate once up front instead of per
|
||||
DecryptCENC call.
|
||||
- Reduce compiler warnings.
|
||||
- Add support for RSA CAST receiver signing.
|
||||
- Add QEMUv8 target.
|
||||
- Move der_parse and related files into the wtpi_impl directory.
|
||||
- Bugfix: Randomly generated ECC key in v17.1 sometimes was smaller than the
|
||||
expected keysize. Fixed to include leading zeroes if needed.
|
||||
- Bugfix: WPTI_GenerateRandomCertificateKeyPair() was implemented incorrectly.
|
||||
It did not return the correct minimum size, used the wrong mbedtls key type,
|
||||
and did not free allocated resources. Fixed all three issues. Please note
|
||||
that on 64-bit targets, WPTI_GenerateRandomCertificateKeyPair() will exhaust
|
||||
the default memory pool that OP-TEE uses for mbedtls. We suggest increasing
|
||||
MPI_MEMPOOL_SIZE from 12k to 14k in optee_os/lib/libutee/tee_api_arith_mpi.c
|
||||
to avoid this.
|
||||
|
||||
### Trusty port changes
|
||||
|
||||
In v17.1, the Trusty port did not compile against the OPK. This has been fixed
|
||||
in v18, with the code moved one directory deeper to a folder named `reference`.
|
||||
|
||||
Implementers looking to create a port based on this reference code are
|
||||
encouraged to copy the `reference` folder and modify it, instead of modifying
|
||||
the existing code directly.
|
||||
|
||||
The Trusty port still requires a full download of AOSP Trusty, and must be built
|
||||
into the Trusty kernel as a user module. In the future we plan to support
|
||||
standalone TA builds that can be compiled or sideloaded into Trusty OS.
|
||||
|
||||
At this time, there is still no easy way to test builds of the Trusty port
|
||||
without hardware. QEMU support is planned for a future release.
|
||||
|
||||
### Linux testing port
|
||||
|
||||
A new folder has been created under `oemcrypto/opk/ports` for an implementation
|
||||
that can run on Linux. Please note that this is a testing-only insecure
|
||||
implementation, as all code executes in the REE.
|
||||
|
||||
The oemcrypto_unittests and wtpi_unittests applications are the same. The "TA"
|
||||
is a Linux application that spins a while loop until it receives a message, then
|
||||
executes that call. The transport interface between liboemcrypto and this fake
|
||||
TA uses Linux shmem APIs to pass messages back and forth.
|
||||
|
||||
Again, this is not to be used in any kind of production release. The fake TA is
|
||||
only intended as an easier way to test the REE-TEE code path without a trusted
|
||||
OS.
|
||||
|
||||
### Other changes
|
||||
|
||||
- Provisioning 4 WTPI functions moved to wtpi_provisioning_4_interface.h. Some
|
||||
new functions added such as WTPI_GetSignedCsrPayload()
|
||||
- Provisioning 4 WTPI tests improved to test correctness of BCC and CoseSign1
|
||||
payloads, but requires new third party library COSE-C to do so.
|
||||
- WTPI unit tests can be skipped based on configuration values. Currently
|
||||
provisioning 4 WTPI functions are skipped if the configured provisioning
|
||||
method is different.
|
||||
- Calling an OEMCrypto function with shared buffer arguments could fail if the
|
||||
underlying buffer was larger than the available shared memory. This would
|
||||
occur before reaching the TEE in the serialization code, and would return the
|
||||
default OEMCrypto_ERROR_UNKNOWN_FAILURE error. Now it returns
|
||||
OEMCrypto_ERROR_BUFFER_TOO_LARGE.
|
||||
- Fixed a memory leak in the asymmetric key table management code, where key
|
||||
handles were not freed at session termination.
|
||||
- Fixed a bug in the serialization code, where `uint8_t iv[16]` parameters were
|
||||
not passed correctly through to the TEE, eg in AES operations. A NULL input
|
||||
would always show up as a valid pointer to the TEE. This is now fixed and NULL
|
||||
inputs show up as NULL in the TEE.
|
||||
|
||||
### Known bugs
|
||||
|
||||
- In the OP-TEE port, WTPI unit tests that use randomly generated ECC keys
|
||||
occasionally (1/100) fail. The exact cause is unknown, but it appears to be
|
||||
due to an edge case in the implementation of
|
||||
WPTI_GenerateRandomCertificateKeyPair().
|
||||
- The OPK does not support Cast Receiver functionality when using Provisioning
|
||||
4.0.
|
||||
- The OPK does not yet support MediaCAS functionality.
|
||||
|
||||
## [OPK Version 17.1.1][v17.1+opk-v17.1.1]
|
||||
|
||||
This release fixes a flaw in the OPK code that could allow content that requires
|
||||
HDCP 2 to output over a display connection that only supports HDCP 1. This bug
|
||||
would only be triggered if the WTPI implementation reports the minor version
|
||||
number of HDCP 1 connections. If your implementation of
|
||||
`WTPI_CurrentHDCPCapability()` ever returns `HDCP_V1_0`, `HDCP_V1_1`,
|
||||
`HDCP_V1_2`, `HDCP_V1_3`, or `HDCP_V1_4`, your device is vulnerable and you
|
||||
should take this patch urgently. If your implementation of
|
||||
`WTPI_CurrentHDCPCapability()` only ever returns `HDCP_V1` for HDCP 1
|
||||
connections or does not support HDCP 1, then your device is not affected. You
|
||||
will not need to change your WTPI implementation to apply this patch.
|
||||
|
||||
This release also fixes the value of `maximum_minor_version` in ODK.
|
||||
|
||||
## [Version 17.1][v17.1]
|
||||
|
||||
This release contains a major change to the build process for the OP-TEE port,
|
||||
@@ -163,3 +364,5 @@ Public release for OEMCrypto API and ODK library version 16.4.
|
||||
[v17+test-updates+opk]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v17+test-updates+opk
|
||||
[v17+test-updates+opk+mk]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v17+test-updates+opk+mk
|
||||
[v17.1]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v17.1
|
||||
[v17.1+opk-v17.1.1]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v17.1+opk-v17.1.1
|
||||
[v18.1]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v18.1
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// License Agreement.
|
||||
|
||||
/**
|
||||
* @mainpage OEMCrypto API v18
|
||||
* @mainpage OEMCrypto API v18.1
|
||||
*
|
||||
* OEMCrypto is the low level library implemented by the OEM to provide key and
|
||||
* content protection, usually in a separate secure memory or process space. The
|
||||
@@ -1037,6 +1037,10 @@ OEMCryptoResult OEMCrypto_GenerateDerivedKeys(
|
||||
* OEMCrypto_LoadLicense() proceed in the same manner for license requests using
|
||||
* RSA or using a Widevine keybox token.
|
||||
*
|
||||
* This function is also used to derive keys before processing a Cast
|
||||
* Certificate provisioning response in OEMCrypto_LoadProvisioning().
|
||||
* See [Cast Receiver](../../cast) for more details.
|
||||
*
|
||||
* @verification
|
||||
* If the RSA key's allowed_schemes is not kSign_RSASSA_PSS, then no keys are
|
||||
* derived and the error OEMCrypto_ERROR_INVALID_KEY is returned. An RSA
|
||||
@@ -3862,11 +3866,14 @@ OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm(
|
||||
* key and signing key generated using an algorithm at least as strong as
|
||||
* that in GenerateDerivedKeys.
|
||||
*
|
||||
* First, OEMCrypto shall verify the signature of the message using
|
||||
* HMAC-SHA256 with the derived mac_key[server]. The signature verification
|
||||
* shall use a constant-time algorithm (a signature mismatch will always take
|
||||
* the same time as a successful comparison). The signature is over the
|
||||
* entire message buffer starting at message with length message_length. If
|
||||
* First, OEMCrypto shall verify the signature of the message using the correct
|
||||
* algorithm depending on if the device supports Provisioning 2.0, 3.0 or 4.0.
|
||||
*
|
||||
* For Provisioning 2.0, OEMCrypto shall verify the signature of the message
|
||||
* using HMAC-SHA256 with the derived mac_key[server]. The signature
|
||||
* verification shall use a constant-time algorithm (a signature mismatch will
|
||||
* always take the same time as a successful comparison). The signature is over
|
||||
* the entire message buffer starting at message with length message_length. If
|
||||
* the signature verification fails, ignore all other arguments and return
|
||||
* OEMCrypto_ERROR_SIGNATURE_FAILURE.
|
||||
*
|
||||
@@ -3874,7 +3881,10 @@ OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm(
|
||||
* and encrypt_key with a call to OEMCrypto_DeriveKeysFromSessionKey() or
|
||||
* OEMCrypto_GenerateDerivedKeys().
|
||||
*
|
||||
* The function ODK_ParseProvisioning is called to parse the message. If it
|
||||
* For Provisioning 3.0 and 4.0, the signature is not verified.
|
||||
*
|
||||
* After the signature is verified,
|
||||
* the function ODK_ParseProvisioning is called to parse the message. If it
|
||||
* returns an error, OEMCrypto shall return that error to the CDM layer. The
|
||||
* function ODK_ParseProvisioning is described in the document "Widevine Core
|
||||
* Message Serialization".
|
||||
@@ -4151,22 +4161,31 @@ OEMCryptoResult OEMCrypto_GenerateRSASignature(
|
||||
RSA_Padding_Scheme padding_scheme);
|
||||
|
||||
/**
|
||||
* OEMCrypto will use OEMCrypto_PrepAndSignProvisioningRequest(), as described
|
||||
* in the document "Widevine Core Message Serialization", to prepare the core
|
||||
* message. If it returns an error, the error should be returned by OEMCrypto
|
||||
* to the CDM layer. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall
|
||||
* compute the signature of the entire message. The entire message is the
|
||||
* buffer starting at message with length message_length.
|
||||
* OEMCrypto will use ODK_PrepareCoreProvisioningRequest() or
|
||||
* ODK_PrepareCoreProvisioning40Request(), as described in the document
|
||||
* "Widevine Core Message Serialization", to prepare the core message.
|
||||
* ODK_PrepareCoreProvisioningRequest() for Provisioning 2 or 3, and
|
||||
* ODK_PrepareCoreProvisioning40Request() for Provisioning 4. If the ODK
|
||||
* function returns an error, the error should be returned by OEMCrypto to the
|
||||
* CDM layer. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall compute the
|
||||
* signature of the entire message. The entire message is the buffer starting at
|
||||
* message with length message_length.
|
||||
*
|
||||
* For a device that has a keybox, i.e. Provisioning 2.0, OEMCrypto will sign
|
||||
* the request with the session's derived client mac key from the previous
|
||||
* call to OEMCrypto_GenerateDerivedKeys().
|
||||
*
|
||||
* For a device that has an OEM Certificate, i.e. Provisioning 3.0, OEMCrypto
|
||||
* will sign the request with the private key associated with the OEM
|
||||
* For Provisioning 3.0, i.e. a device that has a baked in OEM Certificate,
|
||||
* OEMCrypto will sign the request with the private key associated with the OEM
|
||||
* Certificate. The key shall have been loaded by a previous call to
|
||||
* OEMCrypto_LoadDRMPrivateKey().
|
||||
*
|
||||
* For Provisioning 4.0, i.e. a device that uses a Boot Chain Certificate to
|
||||
* request and OEM cert, a request for an OEM cert is signed by the OEM private
|
||||
* key. A request for a DRM cert is signed by the DRM private key. The DRM cert
|
||||
* that was generated on the device in OEMCrypto_GenerateCertificateKeyPair() is
|
||||
* signed by the OEM cert private key.
|
||||
*
|
||||
* Refer to the Signing Messages Sent to a Server section above for more
|
||||
* details.
|
||||
*
|
||||
@@ -5575,17 +5594,9 @@ OEMCryptoResult OEMCrypto_GetRandom(uint8_t* random_data,
|
||||
|
||||
/**
|
||||
* OEMCrypto_SelectKey
|
||||
* @deprecated
|
||||
* Not required for the current version of OEMCrypto. Declared here to
|
||||
* help with backward compatibility.
|
||||
*
|
||||
* TODO(b/240995221): Deprecate and remove params.
|
||||
* @param[in] session: handle for the crypto or entitled key session to be used.
|
||||
* @param[in] content_key_id: pointer to the content Key ID.
|
||||
* @param[in] content_key_id_length: length of the content Key ID, in bytes.
|
||||
* From 1 to 16, inclusive.
|
||||
* @param[in] cipher_mode: whether the key should be prepared for CTR mode or
|
||||
* CBC mode when used in later calls to DecryptCENC. This should be ignored
|
||||
* when the key is used for Generic Crypto calls.
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session,
|
||||
const uint8_t* content_key_id,
|
||||
@@ -5594,18 +5605,9 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session,
|
||||
|
||||
/**
|
||||
* OEMCrypto_DecryptCENC_V17
|
||||
* @deprecated
|
||||
* Not required for the current version of OEMCrypto. Declared here to
|
||||
* help with backward compatibility.
|
||||
*
|
||||
* TODO(b/240995221): Deprecate and remove params.
|
||||
* @param[in] session: Crypto or entitled session identifier. The crypto session
|
||||
* in which decrypt is to be performed.
|
||||
* @param[in] samples: A caller-owned array of OEMCrypto_SampleDescription
|
||||
* structures. Each entry in this array contains one sample of the content.
|
||||
* @param[in] samples_length: The length of the array pointed to by the samples
|
||||
* parameter.
|
||||
* @param[in] pattern: A caller-owned structure indicating the encrypt/skip
|
||||
* pattern as specified in the ISO-CENC standard.
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_DecryptCENC_V17(
|
||||
OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples,
|
||||
@@ -5613,19 +5615,9 @@ OEMCryptoResult OEMCrypto_DecryptCENC_V17(
|
||||
|
||||
/**
|
||||
* OEMCrypto_Generic_Encrypt_V17
|
||||
* @deprecated
|
||||
* Not required for the current version of OEMCrypto. Declared here to
|
||||
* help with backward compatibility.
|
||||
*
|
||||
* TODO(b/240995221): Deprecate and remove params.
|
||||
* @param[in] session: crypto or entitled key session identifier.
|
||||
* @param[in] in_buffer: pointer to memory containing data to be encrypted.
|
||||
* @param[in] in_buffer_length: length of the buffer, in bytes. The algorithm
|
||||
* may restrict in_buffer_length to be a multiple of block size.
|
||||
* @param[in] iv: IV for encrypting data. Size is 128 bits.
|
||||
* @param[in] algorithm: Specifies which encryption algorithm to use.
|
||||
* Currently, only CBC 128 mode is allowed for encryption.
|
||||
* @param[out] out_buffer: pointer to buffer in which encrypted data should be
|
||||
* stored.
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_Generic_Encrypt_V17(
|
||||
OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* in_buffer,
|
||||
@@ -5634,19 +5626,9 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt_V17(
|
||||
|
||||
/**
|
||||
* OEMCrypto_Generic_Decrypt_V17
|
||||
* @deprecated
|
||||
* Not required for the current version of OEMCrypto. Declared here to
|
||||
* help with backward compatibility.
|
||||
*
|
||||
* TODO(b/240995221): Deprecate and remove params.
|
||||
* @param[in] session: crypto or entitled key session identifier.
|
||||
* @param[in] in_buffer: pointer to memory containing data to be encrypted.
|
||||
* @param[in] in_buffer_length: length of the buffer, in bytes. The algorithm
|
||||
* may restrict in_buffer_length to be a multiple of block size.
|
||||
* @param[in] iv: IV for encrypting data. Size is 128 bits.
|
||||
* @param[in] algorithm: Specifies which encryption algorithm to use.
|
||||
* Currently, only CBC 128 mode is allowed for decryption.
|
||||
* @param[out] out_buffer: pointer to buffer in which decrypted data should be
|
||||
* stored.
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_Generic_Decrypt_V17(
|
||||
OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* in_buffer,
|
||||
@@ -5655,19 +5637,9 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt_V17(
|
||||
|
||||
/**
|
||||
* OEMCrypto_Generic_Sign_V17
|
||||
* @deprecated
|
||||
* Not required for the current version of OEMCrypto. Declared here to
|
||||
* help with backward compatibility.
|
||||
*
|
||||
* TODO(b/240995221): Deprecate and remove params.
|
||||
* @param[in] session: crypto or entitled key session identifier.
|
||||
* @param[in] buffer: pointer to memory containing data to be encrypted.
|
||||
* @param[in] buffer_length: length of the buffer, in bytes.
|
||||
* @param[in] algorithm: Specifies which algorithm to use.
|
||||
* @param[out] signature: pointer to buffer in which signature should be
|
||||
* stored. May be null on the first call in order to find required buffer
|
||||
* size.
|
||||
* @param[in,out] signature_length: (in) length of the signature buffer, in
|
||||
* bytes. (out) actual length of the signature
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_Generic_Sign_V17(OEMCrypto_SESSION session,
|
||||
const OEMCrypto_SharedMemory* buffer,
|
||||
@@ -5678,16 +5650,9 @@ OEMCryptoResult OEMCrypto_Generic_Sign_V17(OEMCrypto_SESSION session,
|
||||
|
||||
/**
|
||||
* OEMCrypto_Generic_Verify_V17
|
||||
* @deprecated
|
||||
* Not required for the current version of OEMCrypto. Declared here to
|
||||
* help with backward compatibility.
|
||||
*
|
||||
* TODO(b/240995221): Deprecate and remove params.
|
||||
* @param[in] session: crypto or entitled key session identifier.
|
||||
* @param[in] buffer: pointer to memory containing data to be encrypted.
|
||||
* @param[in] buffer_length: length of the buffer, in bytes.
|
||||
* @param[in] algorithm: Specifies which algorithm to use.
|
||||
* @param[in] signature: pointer to buffer in which signature resides.
|
||||
* @param[in] signature_length: length of the signature buffer, in bytes.
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_Generic_Verify_V17(
|
||||
OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* buffer,
|
||||
|
||||
@@ -304,3 +304,65 @@ OEMCryptoResult _oecc120(OEMCrypto_SESSION session, const uint8_t* message,
|
||||
// OEMCrypto_GetOEMKeyToken defined in v17.2
|
||||
OEMCryptoResult _oecc130(OEMCrypto_SESSION key_session, uint8_t* key_token,
|
||||
size_t* key_token_length);
|
||||
|
||||
// OEMCrypto_SetMaxAPIVersion defined in v18.1
|
||||
OEMCryptoResult _oecc132(uint32_t max_version);
|
||||
|
||||
// OEMCrypto_GetKeyHandle defined in v18.1
|
||||
OEMCryptoResult _oecc133(OEMCrypto_SESSION session,
|
||||
const uint8_t* content_key_id,
|
||||
size_t content_key_id_length,
|
||||
OEMCryptoCipherMode cipher_mode, uint8_t* key_handle,
|
||||
size_t* key_handle_length);
|
||||
|
||||
// OEMCrypto_DecryptCENC defined in v18.1
|
||||
OEMCryptoResult _oecc134(
|
||||
const uint8_t* key_handle, size_t key_handle_length,
|
||||
const OEMCrypto_SampleDescription* samples, // an array of samples.
|
||||
size_t samples_length, // the number of samples.
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern);
|
||||
|
||||
// OEMCrypto_Generic_Encrypt defined in v18.1
|
||||
OEMCryptoResult _oecc135(const uint8_t* key_handle, size_t key_handle_length,
|
||||
const OEMCrypto_SharedMemory* in_buffer,
|
||||
size_t in_buffer_length, const uint8_t* iv,
|
||||
OEMCrypto_Algorithm algorithm,
|
||||
OEMCrypto_SharedMemory* out_buffer);
|
||||
|
||||
// OEMCrypto_Generic_Decrypt defined in v18.1
|
||||
OEMCryptoResult _oecc136(const uint8_t* key_handle, size_t key_handle_length,
|
||||
const OEMCrypto_SharedMemory* in_buffer,
|
||||
size_t in_buffer_length, const uint8_t* iv,
|
||||
OEMCrypto_Algorithm algorithm,
|
||||
OEMCrypto_SharedMemory* out_buffer);
|
||||
|
||||
// OEMCrypto_Generic_Sign defined in v18.1
|
||||
OEMCryptoResult _oecc137(const uint8_t* key_handle, size_t key_handle_length,
|
||||
const OEMCrypto_SharedMemory* buffer,
|
||||
size_t buffer_length, OEMCrypto_Algorithm algorithm,
|
||||
OEMCrypto_SharedMemory* signature,
|
||||
size_t* signature_length);
|
||||
|
||||
// OEMCrypto_Generic_Verify defined in v18.1
|
||||
OEMCryptoResult _oecc138(const uint8_t* key_handle, size_t key_handle_length,
|
||||
const OEMCrypto_SharedMemory* buffer,
|
||||
size_t buffer_length, OEMCrypto_Algorithm algorithm,
|
||||
const OEMCrypto_SharedMemory* signature,
|
||||
size_t signature_length);
|
||||
|
||||
// OEMCrypto_GetSignatureHashAlgorithm defined in v18.1
|
||||
OEMCryptoResult _oecc139(OEMCrypto_SESSION session,
|
||||
OEMCrypto_SignatureHashAlgorithm* algorithm);
|
||||
|
||||
// OEMCrypto_GetDeviceInformation defined in v18.1
|
||||
OEMCryptoResult _oecc131(uint8_t* device_info, size_t* device_info_length);
|
||||
|
||||
// OEMCrypto_GetDeviceSignedCsrPayload defined in v18.1
|
||||
OEMCryptoResult _oecc141(const uint8_t* challenge, size_t challenge_length,
|
||||
const uint8_t* encoded_device_info,
|
||||
size_t encoded_device_info_length,
|
||||
uint8_t* signed_csr_payload,
|
||||
size_t* signed_csr_payload_length);
|
||||
|
||||
// OEMCrypto_EnterTestMode defined in v18.1
|
||||
OEMCryptoResult _oecc140(void);
|
||||
|
||||
@@ -17,8 +17,10 @@ LOCAL_SRC_FILES:= \
|
||||
oemcrypto_corpus_generator_helper.cpp \
|
||||
oemcrypto_session_tests_helper.cpp \
|
||||
oemcrypto_basic_test.cpp \
|
||||
oemcrypto_decrypt_test.cpp \
|
||||
oemcrypto_license_test.cpp \
|
||||
oemcrypto_provisioning_test.cpp \
|
||||
oemcrypto_usage_table_test.cpp \
|
||||
oemcrypto_test.cpp \
|
||||
oemcrypto_test_android.cpp \
|
||||
oemcrypto_test_main.cpp \
|
||||
|
||||
@@ -64,44 +64,48 @@ extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Select key and perform verification.
|
||||
// Get key handle for signing and verifying.
|
||||
Session* const session = license_api_fuzz.session();
|
||||
vector<uint8_t> key_handle;
|
||||
GetKeyHandleIntoVector(
|
||||
OEMCryptoResult result = GetKeyHandleIntoVector(
|
||||
session->session_id(), session->license().keys[0].key_id,
|
||||
session->license().keys[0].key_id_length,
|
||||
fuzzed_properties.value.structure.cipher_mode, key_handle);
|
||||
if (OEMCrypto_Generic_Verify(key_handle.data(), key_handle.size(),
|
||||
fuzzed_properties.value.buffer.data(),
|
||||
fuzzed_properties.value.buffer.size(),
|
||||
fuzzed_properties.value.structure.algorithm,
|
||||
fuzzed_properties.value.signature.data(),
|
||||
fuzzed_properties.value.signature.size()) !=
|
||||
OEMCrypto_SUCCESS) {
|
||||
// Generate a new signature.
|
||||
size_t signature_length = 0;
|
||||
OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(),
|
||||
fuzzed_properties.value.buffer.data(),
|
||||
fuzzed_properties.value.buffer.size(),
|
||||
fuzzed_properties.value.structure.algorithm, nullptr,
|
||||
&signature_length);
|
||||
fuzzed_properties.value.signature.resize(signature_length);
|
||||
OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(),
|
||||
fuzzed_properties.value.buffer.data(),
|
||||
fuzzed_properties.value.buffer.size(),
|
||||
fuzzed_properties.value.structure.algorithm,
|
||||
fuzzed_properties.value.signature.data(),
|
||||
&signature_length);
|
||||
const size_t signature_offset = sizeof(fuzzed_properties.value.structure) +
|
||||
fuzzed_properties.value.buffer.size() +
|
||||
sizeof(kFuzzDataSeparator);
|
||||
size = signature_offset + signature_length;
|
||||
if (size > max_size) {
|
||||
return 0;
|
||||
if (result == OEMCrypto_SUCCESS) {
|
||||
// Generate a new signature if verification fails.
|
||||
result =
|
||||
OEMCrypto_Generic_Verify(key_handle.data(), key_handle.size(),
|
||||
fuzzed_properties.value.buffer.data(),
|
||||
fuzzed_properties.value.buffer.size(),
|
||||
fuzzed_properties.value.structure.algorithm,
|
||||
fuzzed_properties.value.signature.data(),
|
||||
fuzzed_properties.value.signature.size());
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
size_t signature_length = 0;
|
||||
OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(),
|
||||
fuzzed_properties.value.buffer.data(),
|
||||
fuzzed_properties.value.buffer.size(),
|
||||
fuzzed_properties.value.structure.algorithm,
|
||||
nullptr, &signature_length);
|
||||
fuzzed_properties.value.signature.resize(signature_length);
|
||||
OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(),
|
||||
fuzzed_properties.value.buffer.data(),
|
||||
fuzzed_properties.value.buffer.size(),
|
||||
fuzzed_properties.value.structure.algorithm,
|
||||
fuzzed_properties.value.signature.data(),
|
||||
&signature_length);
|
||||
const size_t signature_offset =
|
||||
sizeof(fuzzed_properties.value.structure) +
|
||||
fuzzed_properties.value.buffer.size() + sizeof(kFuzzDataSeparator);
|
||||
size = signature_offset + signature_length;
|
||||
if (size > max_size) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(data + signature_offset, fuzzed_properties.value.signature.data(),
|
||||
signature_length);
|
||||
}
|
||||
memcpy(data + signature_offset, fuzzed_properties.value.signature.data(),
|
||||
signature_length);
|
||||
}
|
||||
|
||||
return LLVMFuzzerMutate(data, size, max_size);
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,6 @@ void DeviceFeatures::Initialize() {
|
||||
switch (derive_key_method) {
|
||||
case NO_METHOD:
|
||||
printf("NO_METHOD: Cannot derive known session keys.\n");
|
||||
// Note: cast_receiver left unchanged because set by user on command line.
|
||||
uses_keybox = false;
|
||||
loads_certificate = false;
|
||||
generic_crypto = false;
|
||||
@@ -161,13 +160,7 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) {
|
||||
provisioning_method == OEMCrypto_BootCertificateChain)
|
||||
FilterOut(&filter, "OEMCryptoLoadsCert*");
|
||||
if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*");
|
||||
if (!cast_receiver) FilterOut(&filter, "*CastReceiver*");
|
||||
if (!supports_cas) FilterOut(&filter, "*CasOnly*");
|
||||
if (derive_key_method == NO_METHOD) FilterOut(&filter, "*SessionTest*");
|
||||
if (provisioning_method
|
||||
!= OEMCrypto_OEMCertificate) FilterOut(&filter, "*Prov30*");
|
||||
if (provisioning_method != OEMCrypto_BootCertificateChain)
|
||||
FilterOut(&filter, "*Prov40*");
|
||||
if (!supports_rsa_3072) FilterOut(&filter, "*RSAKey3072*");
|
||||
if (api_version < 17) FilterOut(&filter, "*API17*");
|
||||
if (api_version < 18) FilterOut(&filter, "*API18*");
|
||||
|
||||
@@ -147,20 +147,26 @@ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) {
|
||||
* Verifies initialization and logs version information.
|
||||
* This test is first, because it might give an idea why other
|
||||
* tests are failing when the device has the wrong keybox installed.
|
||||
*
|
||||
* The log message should be updated by Widevine with every release so that it
|
||||
* is easier to verify which version of the tests a partner is running. Widevine
|
||||
* should change the API version number when the API changes, but the unit tests
|
||||
* might be updated more frequently, and are only tracked by the date of the
|
||||
* last change.
|
||||
*/
|
||||
TEST_F(OEMCryptoClientTest, VersionNumber) {
|
||||
const std::string log_message =
|
||||
"OEMCrypto unit tests for API 18.0. Tests last updated 2022-08-08";
|
||||
"OEMCrypto unit tests for API 18.1. Tests last updated 2023-03-08";
|
||||
cout << " " << log_message << "\n";
|
||||
cout << " "
|
||||
<< "These tests are part of Android T."
|
||||
<< "These tests are part of Android U."
|
||||
<< "\n";
|
||||
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, 1);
|
||||
EXPECT_EQ(kCurrentAPI, 18u);
|
||||
EXPECT_EQ(kCurrentAPI, static_cast<unsigned>(ODK_MAJOR_VERSION));
|
||||
OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel();
|
||||
EXPECT_GT(level, OEMCrypto_Level_Unknown);
|
||||
EXPECT_LE(level, OEMCrypto_Level3);
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "oec_session_util.h"
|
||||
#include "oemcrypto_session_tests_helper.h"
|
||||
|
||||
namespace wvoec {
|
||||
|
||||
const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value);
|
||||
|
||||
686
libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp
Normal file
686
libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp
Normal file
@@ -0,0 +1,686 @@
|
||||
// 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 "oemcrypto_decrypt_test.h"
|
||||
|
||||
#include "test_sleep.h"
|
||||
|
||||
using ::testing::Combine;
|
||||
using ::testing::Range;
|
||||
using ::testing::Values;
|
||||
|
||||
namespace wvoec {
|
||||
|
||||
// Cannot decrypt without first getting a key handle.
|
||||
TEST_P(OEMCryptoLicenseTest, FailDecryptWithoutGettingAHandle) {
|
||||
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());
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
||||
}
|
||||
|
||||
// Cannot decrypt with an old key handle.
|
||||
TEST_P(OEMCryptoLicenseTest, FailDecryptWithOldKeyHandle) {
|
||||
Session donor_session;
|
||||
LicenseRoundTrip license_messages2(&donor_session);
|
||||
ASSERT_NO_FATAL_FAILURE(donor_session.open());
|
||||
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&donor_session));
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages2.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages2.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages2.LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(donor_session.TestDecryptCTR());
|
||||
|
||||
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());
|
||||
|
||||
// Inject the donor session's key handle into |session_| and then close the
|
||||
// donor, which should render the handle invalid.
|
||||
session_.key_handle() = donor_session.key_handle();
|
||||
donor_session.close();
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
||||
}
|
||||
|
||||
// SelectKey should fail if we attempt to select a key that has not been loaded.
|
||||
// Also, the error should be NO_CONTENT_KEY.
|
||||
// This test should pass for v15 devices, except that the exact error code was
|
||||
// not specified until v16.
|
||||
TEST_P(OEMCryptoLicenseTest, SelectKeyNotThereAPI16) {
|
||||
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());
|
||||
|
||||
const char* key_id = "no_key";
|
||||
vector<uint8_t> key_handle;
|
||||
OEMCryptoResult sts = GetKeyHandleIntoVector(
|
||||
session_.session_id(), reinterpret_cast<const uint8_t*>(key_id),
|
||||
strlen(key_id), OEMCrypto_CipherMode_CENC, key_handle);
|
||||
if (sts != OEMCrypto_SUCCESS) {
|
||||
EXPECT_EQ(OEMCrypto_ERROR_NO_CONTENT_KEY, sts);
|
||||
} else {
|
||||
// Delayed error code. If select key was a success, then we should
|
||||
// eventually see the error when we decrypt.
|
||||
vector<uint8_t> in_buffer(256);
|
||||
vector<uint8_t> out_buffer(in_buffer.size());
|
||||
OEMCrypto_SampleDescription sample_description;
|
||||
OEMCrypto_SubSampleDescription subsample_description;
|
||||
|
||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
||||
&subsample_description);
|
||||
|
||||
// Generate test data
|
||||
for (size_t i = 0; i < in_buffer.size(); i++) in_buffer[i] = i % 256;
|
||||
|
||||
// Create the pattern description (always 0,0 for CTR)
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0};
|
||||
|
||||
// Try to decrypt the data
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_EQ(sts, OEMCrypto_ERROR_NO_CONTENT_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
// 'cens' mode is no longer supported in v16
|
||||
TEST_P(OEMCryptoLicenseTest, RejectCensAPI16) {
|
||||
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());
|
||||
|
||||
vector<uint8_t> key_handle;
|
||||
OEMCryptoResult sts;
|
||||
sts = GetKeyHandleIntoVector(session_.session_id(),
|
||||
session_.license().keys[0].key_id,
|
||||
session_.license().keys[0].key_id_length,
|
||||
OEMCrypto_CipherMode_CENC, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
vector<uint8_t> in_buffer(256);
|
||||
vector<uint8_t> out_buffer(in_buffer.size());
|
||||
OEMCrypto_SampleDescription sample_description;
|
||||
OEMCrypto_SubSampleDescription subsample_description;
|
||||
|
||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
||||
&subsample_description);
|
||||
|
||||
// Create a non-zero pattern to indicate this is 'cens'
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern = {1, 9};
|
||||
|
||||
// Try to decrypt the data
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
||||
}
|
||||
|
||||
// 'cbc1' mode is no longer supported in v16
|
||||
TEST_P(OEMCryptoLicenseTest, RejectCbc1API16) {
|
||||
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());
|
||||
|
||||
vector<uint8_t> key_handle;
|
||||
OEMCryptoResult sts;
|
||||
sts = GetKeyHandleIntoVector(session_.session_id(),
|
||||
session_.license().keys[0].key_id,
|
||||
session_.license().keys[0].key_id_length,
|
||||
OEMCrypto_CipherMode_CBCS, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
vector<uint8_t> in_buffer(256);
|
||||
vector<uint8_t> out_buffer(in_buffer.size());
|
||||
OEMCrypto_SampleDescription sample_description;
|
||||
OEMCrypto_SubSampleDescription subsample_description;
|
||||
|
||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
||||
&subsample_description);
|
||||
|
||||
// Create a zero pattern to indicate this is 'cbc1'
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0};
|
||||
|
||||
// Try to decrypt the data
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoLicenseTest, RejectCbcsWithBlockOffset) {
|
||||
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());
|
||||
|
||||
vector<uint8_t> key_handle;
|
||||
OEMCryptoResult sts;
|
||||
sts = GetKeyHandleIntoVector(session_.session_id(),
|
||||
session_.license().keys[0].key_id,
|
||||
session_.license().keys[0].key_id_length,
|
||||
OEMCrypto_CipherMode_CBCS, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
vector<uint8_t> in_buffer(256);
|
||||
vector<uint8_t> out_buffer(in_buffer.size());
|
||||
OEMCrypto_SampleDescription sample_description;
|
||||
OEMCrypto_SubSampleDescription subsample_description;
|
||||
|
||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
||||
&subsample_description);
|
||||
subsample_description.block_offset = 5; // Any value 1-15 will do.
|
||||
|
||||
// Create a non-zero pattern to indicate this is 'cbcs'.
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern = {1, 9};
|
||||
|
||||
// Try to decrypt the data
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoLicenseTest, RejectOversizedBlockOffset) {
|
||||
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());
|
||||
|
||||
vector<uint8_t> key_handle;
|
||||
OEMCryptoResult sts;
|
||||
sts = GetKeyHandleIntoVector(session_.session_id(),
|
||||
session_.license().keys[0].key_id,
|
||||
session_.license().keys[0].key_id_length,
|
||||
OEMCrypto_CipherMode_CENC, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
vector<uint8_t> in_buffer(256);
|
||||
vector<uint8_t> out_buffer(in_buffer.size());
|
||||
OEMCrypto_SampleDescription sample_description;
|
||||
OEMCrypto_SubSampleDescription subsample_description;
|
||||
|
||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
||||
&subsample_description);
|
||||
subsample_description.block_offset = 0xFF; // Anything 16+
|
||||
|
||||
// Create a zero pattern to indicate this is 'cenc'.
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0};
|
||||
|
||||
// Try to decrypt the data
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_NE(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
// Try again with the minimum invalid value
|
||||
subsample_description.block_offset = 16;
|
||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
||||
&sample_description, 1, &pattern);
|
||||
EXPECT_NE(OEMCrypto_SUCCESS, sts);
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoSessionTestDecryptWithHDCP, DecryptAPI09) {
|
||||
// Test parameterized by HDCP version.
|
||||
DecryptWithHDCP(static_cast<OEMCrypto_HDCP_Capability>(GetParam()));
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(TestHDCP, OEMCryptoSessionTestDecryptWithHDCP,
|
||||
Range(1, 6));
|
||||
|
||||
// If the license does not allow a hash, then we should not compute one.
|
||||
TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) {
|
||||
uint32_t hash_type = OEMCrypto_SupportsDecryptHash();
|
||||
// If hash is not supported, or is vendor defined, don't try to test it.
|
||||
if (hash_type != OEMCrypto_CRC_Clear_Buffer) return;
|
||||
|
||||
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 frame_number = 1;
|
||||
uint32_t hash = 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)));
|
||||
// It is OK to select the key and decrypt.
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR());
|
||||
// But the error code should be bad.
|
||||
ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE,
|
||||
OEMCrypto_GetHashErrorCode(session_.session_id(), &frame_number));
|
||||
}
|
||||
|
||||
// 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)));
|
||||
}
|
||||
|
||||
//
|
||||
// Decrypt Tests -- these test Decrypt CTR mode only.
|
||||
//
|
||||
TEST_P(OEMCryptoLicenseTest, Decrypt) {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
license_messages_.core_response()
|
||||
.timer_limits.total_playback_duration_seconds = kDuration;
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR());
|
||||
}
|
||||
|
||||
// Verify that a zero duration means infinite license duration.
|
||||
TEST_P(OEMCryptoLicenseTest, DecryptZeroDuration) {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
license_messages_.core_response()
|
||||
.timer_limits.total_playback_duration_seconds = 0;
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR());
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, SingleLargeSubsample) {
|
||||
// This subsample size is larger than a few encrypt/skip patterns. Most
|
||||
// test cases use a pattern length of 160, so we'll run through at least two
|
||||
// full patterns if we have more than 320 -- round up to 400.
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 400},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// When the pattern length is 10 blocks, there is a discrepancy between the
|
||||
// HLS and the CENC standards for samples of size 160*N+16, for N = 1, 2, 3...
|
||||
// We require the CENC standard for OEMCrypto, and let a layer above us break
|
||||
// samples into pieces if they wish to use the HLS standard.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, PatternPlusOneBlock) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 160 + 16},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// Test that a single block can be decrypted.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, OneBlock) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 16},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests the ability to decrypt multiple subsamples with no offset.
|
||||
// There is no offset within the block, used by CTR mode.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, NoOffset) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{25, 160},
|
||||
{50, 256},
|
||||
{25, 160},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests an offset into the block for the second encrypted subsample.
|
||||
// This should only work for CTR mode, for CBC mode an error is expected in
|
||||
// the decrypt step.
|
||||
// If this test fails for CTR mode, then it is probably handling the
|
||||
// block_offset incorrectly.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, EvenOffset) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{25, 8},
|
||||
{25, 32},
|
||||
{25, 50},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
// CTR Mode is self-inverse -- i.e. We can pick the encrypted data and
|
||||
// compute the unencrypted data. By picking the encrypted data to be all 0,
|
||||
// it is easier to re-encrypt the data and debug problems. Similarly, we
|
||||
// pick an iv = 0.
|
||||
memset(initial_iv_, 0, KEY_IV_SIZE);
|
||||
TestSample& sample = samples_[0]; // There is only one sample in this test
|
||||
sample.truth_buffer.assign(sample.description.buffers.input_data_length, 0);
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
if (decrypt_inplace_) {
|
||||
const size_t total_size = sample.description.buffers.input_data_length;
|
||||
// In case of decrypt_inplace_, encrypted_buffer contains padded bytes
|
||||
// which is used for buffer overrun validation. Do not copy the padded
|
||||
// bytes to truth_buffer.
|
||||
sample.truth_buffer.assign(sample.encrypted_buffer.begin(),
|
||||
sample.encrypted_buffer.begin() + total_size);
|
||||
} else {
|
||||
sample.truth_buffer =
|
||||
sample.encrypted_buffer; // truth_buffer_ = encrypted zero buffer.
|
||||
}
|
||||
// Run EncryptData to re-encrypt this buffer. For CTR mode, we should get
|
||||
// back to zeros.
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// If the EvenOffset test passes, but this one doesn't, then DecryptCENC might
|
||||
// be using the wrong definition of block offset. Adding the block offset to
|
||||
// the block boundary should give you the beginning of the encrypted data.
|
||||
// This should only work for CTR mode, for CBC mode, the block offset must be
|
||||
// 0, so an error is expected in the decrypt step.
|
||||
// Another way to view the block offset is with the formula:
|
||||
// block_boundary + block_offset = beginning of subsample.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, OddOffset) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{10, 50},
|
||||
{10, 75},
|
||||
{10, 75},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests that the algorithm used to increment the counter for
|
||||
// AES-CTR mode is correct. There are two possible implementations:
|
||||
// 1) increment the counter as if it were a 128 bit number,
|
||||
// 2) increment the low 64 bits as a 64 bit number and leave the high bits
|
||||
// alone.
|
||||
// For CENC, the algorithm we should use is the second one. OpenSSL defaults to
|
||||
// the first. If this test is not passing, you should look at the way you
|
||||
// increment the counter. Look at the example code in ctr128_inc64 above.
|
||||
// If you start with an IV of 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, after you
|
||||
// increment twice, you should get 0xFFFFFFFFFFFFFFFF0000000000000000.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptWithNearWrap) {
|
||||
memcpy(initial_iv_,
|
||||
wvutil::a2b_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE").data(),
|
||||
KEY_IV_SIZE);
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 256},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests the case where an encrypted sample is not an even number of
|
||||
// blocks. For CTR mode, the partial block is encrypted. For CBC mode the
|
||||
// partial block should be a copy of the clear data.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, PartialBlock) {
|
||||
// Note: for more complete test coverage, we want a sample size that is in
|
||||
// the encrypted range for some tests, e.g. (3,7), and in the skip range for
|
||||
// other tests, e.g. (7, 3). 3*16 < 50 and 7*16 > 50.
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 50},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// Based on the resource rating, OEMCrypto should be able to handle the maximum
|
||||
// amount of data that can be passed to it. This is the lesser of:
|
||||
//
|
||||
// 1) The maximum total sample size
|
||||
// 2) The maximum number of subsamples multiplied by the maximum subsample size
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSampleAPI16) {
|
||||
const size_t max_sample_size = GetResourceValue(kMaxSampleSize);
|
||||
const size_t max_subsample_size = GetResourceValue(kMaxSubsampleSize);
|
||||
const size_t max_num_subsamples = GetResourceValue(kMaxNumberSubsamples);
|
||||
|
||||
// The +1 on this line ensures that, even in cases where max_sample_size is
|
||||
// not evenly divisible by max_num_subsamples and thus the division gets
|
||||
// truncated, (max_num_subsamples * subsample_size) will be greater than
|
||||
// max_sample_size.
|
||||
const size_t subsample_size =
|
||||
std::min(max_sample_size / max_num_subsamples + 1, max_subsample_size);
|
||||
size_t bytes_remaining = max_sample_size;
|
||||
std::vector<SubsampleSize> subsample_sizes;
|
||||
while (bytes_remaining > 0 && subsample_sizes.size() < max_num_subsamples) {
|
||||
const size_t this_subsample_size =
|
||||
std::min(subsample_size, bytes_remaining);
|
||||
const size_t clear_size = this_subsample_size / 2;
|
||||
const size_t encrypted_size = this_subsample_size - clear_size;
|
||||
|
||||
subsample_sizes.push_back({clear_size, encrypted_size});
|
||||
bytes_remaining -= this_subsample_size;
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes(subsample_sizes));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests,
|
||||
OEMCryptoMemoryCheckDecryptCENCStatusForHugeNumberOfSubSamples) {
|
||||
size_t number_of_subsamples = 10000;
|
||||
std::vector<SubsampleSize> subsample_sizes;
|
||||
while (number_of_subsamples-- > 0) {
|
||||
subsample_sizes.push_back({100, 100});
|
||||
}
|
||||
SetSubsampleSizes(subsample_sizes);
|
||||
LoadLicense();
|
||||
MakeBuffers();
|
||||
EncryptData();
|
||||
// Build an array of just the sample descriptions.
|
||||
std::vector<OEMCrypto_SampleDescription> sample_descriptions;
|
||||
sample_descriptions.reserve(samples_.size());
|
||||
for (TestSample& sample : samples_) {
|
||||
// This must be deferred until this point in case the test modifies the
|
||||
// buffer before testing decrypt.
|
||||
sample.description.buffers.input_data = sample.encrypted_buffer.data();
|
||||
// Append to the description array.
|
||||
sample_descriptions.push_back(sample.description);
|
||||
}
|
||||
OEMCryptoResult result =
|
||||
OEMCrypto_DecryptCENC(key_handle_.data(), key_handle_.size(),
|
||||
sample_descriptions.data(), 1, &pattern_);
|
||||
LOGD("Large number of subsamples test has return code %d", result);
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests,
|
||||
OEMCryptoMemoryCheckDecryptCENCStatusForHugeSubSample) {
|
||||
std::vector<SubsampleSize> subsample_sizes;
|
||||
subsample_sizes.push_back({100000, 100000});
|
||||
SetSubsampleSizes(subsample_sizes);
|
||||
LoadLicense();
|
||||
MakeBuffers();
|
||||
EncryptData();
|
||||
// Build an array of just the sample descriptions.
|
||||
std::vector<OEMCrypto_SampleDescription> sample_descriptions;
|
||||
sample_descriptions.reserve(samples_.size());
|
||||
for (TestSample& sample : samples_) {
|
||||
// This must be deferred until this point in case the test modifies the
|
||||
// buffer before testing decrypt.
|
||||
sample.description.buffers.input_data = sample.encrypted_buffer.data();
|
||||
// Append to the description array.
|
||||
sample_descriptions.push_back(sample.description);
|
||||
}
|
||||
OEMCryptoResult result =
|
||||
OEMCrypto_DecryptCENC(key_handle_.data(), key_handle_.size(),
|
||||
sample_descriptions.data(), 1, &pattern_);
|
||||
LOGD("Large subsample test has return code %d", result);
|
||||
}
|
||||
|
||||
// Based on the resource rating, OEMCrypto should be able to handle the maximum
|
||||
// subsample size.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSubsample) {
|
||||
const size_t max = GetResourceValue(kMaxSubsampleSize);
|
||||
const size_t half_max = max / 2;
|
||||
// This test assumes that the maximum sample size is always more than three
|
||||
// times the maximum subsample size.
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{max, 0},
|
||||
{0, max},
|
||||
{half_max, max - half_max},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// There are probably no frames this small, but we should handle them anyway.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptSmallBuffer) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{5, 5},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// Test the case where there is only a clear subsample and no encrypted
|
||||
// subsample.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptUnencrypted) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{256, 0},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests the ability to decrypt multiple samples at once.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, MultipleSamples) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetSampleSizes({
|
||||
{
|
||||
{52, 160},
|
||||
{25, 256},
|
||||
{25, 320},
|
||||
},
|
||||
{
|
||||
{300, 64},
|
||||
{50, 160},
|
||||
{2, 160},
|
||||
{24, 160},
|
||||
{128, 256},
|
||||
},
|
||||
{
|
||||
{70, 320},
|
||||
{160, 160},
|
||||
},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// This tests that calling OEMCrypto_Idle and OEMCrypto_Wake once or multiple
|
||||
// times doesn't break anything.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, IdleAndWake) {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
OEMCrypto_Idle(OEMCrypto_IdleState::OEMCrypto_CpuSuspend, 0));
|
||||
ASSERT_NO_FATAL_FAILURE(OEMCrypto_Wake());
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
OEMCrypto_Idle(OEMCrypto_IdleState::OEMCrypto_CpuSuspend, 0));
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
OEMCrypto_Idle(OEMCrypto_IdleState::OEMCrypto_CpuSuspend, 0));
|
||||
ASSERT_NO_FATAL_FAILURE(OEMCrypto_Wake());
|
||||
ASSERT_NO_FATAL_FAILURE(OEMCrypto_Wake());
|
||||
}
|
||||
|
||||
// This tests that after an idle and a wake, decryption can continue in an
|
||||
// open session.
|
||||
TEST_P(OEMCryptoSessionTestsDecryptTests, ContinueDecryptionAfterIdleAndWake) {
|
||||
// This subsample size is larger than a few encrypt/skip patterns. Most
|
||||
// test cases use a pattern length of 160, so we'll run through at least two
|
||||
// full patterns if we have more than 320 -- round up to 400.
|
||||
ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({
|
||||
{0, 400},
|
||||
}));
|
||||
ASSERT_NO_FATAL_FAILURE(LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
FreeSecureBuffers();
|
||||
// Set state to idle then wake again and try to reencrypt/decrypt
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
OEMCrypto_Idle(OEMCrypto_IdleState::OEMCrypto_CpuSuspend, 0));
|
||||
ASSERT_NO_FATAL_FAILURE(OEMCrypto_Wake());
|
||||
ASSERT_NO_FATAL_FAILURE(MakeBuffers());
|
||||
ASSERT_NO_FATAL_FAILURE(EncryptData());
|
||||
ASSERT_NO_FATAL_FAILURE(TestDecryptCENC());
|
||||
}
|
||||
|
||||
// Used to construct a specific pattern.
|
||||
constexpr OEMCrypto_CENCEncryptPatternDesc MakePattern(size_t encrypt,
|
||||
size_t skip) {
|
||||
return {encrypt, skip};
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
CTRTests, OEMCryptoSessionTestsDecryptTests,
|
||||
Combine(Values(MakePattern(0, 0)), Values(OEMCrypto_CipherMode_CENC),
|
||||
::testing::ValuesIn(global_features.GetOutputTypes())));
|
||||
|
||||
// Decrypt in place for CBC tests was only required in v13.
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
CBCTestsAPI14, OEMCryptoSessionTestsDecryptTests,
|
||||
Combine(
|
||||
Values(MakePattern(3, 7), MakePattern(9, 1),
|
||||
// HLS edge cases. We should follow the CENC spec, not HLS spec.
|
||||
MakePattern(1, 9), MakePattern(1, 0),
|
||||
// AV1 patterns not already covered above.
|
||||
MakePattern(5, 5), MakePattern(10, 0)),
|
||||
Values(OEMCrypto_CipherMode_CBCS),
|
||||
::testing::ValuesIn(global_features.GetOutputTypes())));
|
||||
|
||||
// A request to decrypt data to a clear buffer when the key control block
|
||||
// requires a secure data path.
|
||||
TEST_P(OEMCryptoLicenseTest, DecryptSecureToClear) {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
license_messages_.set_control(wvoec::kControlObserveDataPath |
|
||||
wvoec::kControlDataPathSecure);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
||||
}
|
||||
|
||||
// Test that key duration is honored.
|
||||
TEST_P(OEMCryptoLicenseTest, KeyDuration) {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
license_messages_.core_response()
|
||||
.timer_limits.total_playback_duration_seconds = kDuration;
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(true, OEMCrypto_SUCCESS));
|
||||
wvutil::TestSleep::Sleep(kShortSleep); // Should still be valid key.
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false, OEMCrypto_SUCCESS));
|
||||
wvutil::TestSleep::Sleep(kLongSleep); // Should be expired key.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED));
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestGetKeyHandleExpired(0));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoLicenseTest,
|
||||
Range<uint32_t>(kCurrentAPI - 2, kCurrentAPI + 1));
|
||||
|
||||
} // namespace wvoec
|
||||
427
libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h
Normal file
427
libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h
Normal file
@@ -0,0 +1,427 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine
|
||||
// License Agreement.
|
||||
//
|
||||
// Test data for OEMCrypto unit tests.
|
||||
//
|
||||
#ifndef CDM_OEMCRYPTO_DECRYPT_TEST_
|
||||
#define CDM_OEMCRYPTO_DECRYPT_TEST_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "log.h"
|
||||
#include "oec_decrypt_fallback_chain.h"
|
||||
#include "oemcrypto_basic_test.h"
|
||||
#include "oemcrypto_license_test.h"
|
||||
|
||||
namespace wvoec {
|
||||
|
||||
// Used to test the different HDCP versions. This test is parameterized by the
|
||||
// required HDCP version in the key control block.
|
||||
class OEMCryptoSessionTestDecryptWithHDCP : public OEMCryptoSessionTests,
|
||||
public WithParamInterface<int> {
|
||||
protected:
|
||||
void DecryptWithHDCP(OEMCrypto_HDCP_Capability version) {
|
||||
OEMCryptoResult sts;
|
||||
OEMCrypto_HDCP_Capability current, maximum;
|
||||
sts = OEMCrypto_GetHDCPCapability(¤t, &maximum);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
Session s;
|
||||
ASSERT_NO_FATAL_FAILURE(s.open());
|
||||
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s));
|
||||
LicenseRoundTrip license_messages(&s);
|
||||
license_messages.set_control((version << wvoec::kControlHDCPVersionShift) |
|
||||
wvoec::kControlObserveHDCP |
|
||||
wvoec::kControlHDCPRequired);
|
||||
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());
|
||||
|
||||
if (((version <= HDCP_V2_3 || current >= HDCP_V1_0) && version > current) ||
|
||||
(current == HDCP_V1 && version >= HDCP_V1_0)) {
|
||||
if (global_features.api_version >= 16) {
|
||||
// Can provide either OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION or
|
||||
// OEMCrypto_ERROR_INSUFFICIENT_HDCP. TestDecryptCTR allows either to be
|
||||
// reported if OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION is expected.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
s.TestDecryptCTR(true, OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION))
|
||||
<< "Failed when current HDCP = " << HDCPCapabilityAsString(current)
|
||||
<< ", maximum HDCP = " << HDCPCapabilityAsString(maximum)
|
||||
<< ", license HDCP = " << HDCPCapabilityAsString(version);
|
||||
} else {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
s.TestDecryptCTR(true, OEMCrypto_ERROR_INSUFFICIENT_HDCP))
|
||||
<< "Failed when current HDCP = " << HDCPCapabilityAsString(current)
|
||||
<< ", maximum HDCP = " << HDCPCapabilityAsString(maximum)
|
||||
<< ", license HDCP = " << HDCPCapabilityAsString(version);
|
||||
}
|
||||
} else {
|
||||
ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(true, OEMCrypto_SUCCESS))
|
||||
<< "Failed when current HDCP = " << HDCPCapabilityAsString(current)
|
||||
<< ", maximum HDCP = " << HDCPCapabilityAsString(maximum)
|
||||
<< ", license HDCP = " << HDCPCapabilityAsString(version);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct SubsampleSize {
|
||||
size_t clear_size;
|
||||
size_t encrypted_size;
|
||||
SubsampleSize(size_t clear, size_t encrypted)
|
||||
: clear_size(clear), encrypted_size(encrypted) {}
|
||||
};
|
||||
|
||||
// Struct for holding the data for one test sample in the decrypt tests.
|
||||
struct TestSample {
|
||||
// Encrypted data -- this is input to OEMCrypto, and output from EncryptData.
|
||||
std::vector<uint8_t> encrypted_buffer;
|
||||
std::vector<uint8_t> clear_buffer; // OEMCrypto store clear output here.
|
||||
std::vector<uint8_t> truth_buffer; // Truth data for clear text.
|
||||
OEMCrypto_SampleDescription description;
|
||||
std::vector<OEMCrypto_SubSampleDescription> subsamples;
|
||||
int secure_buffer_fid;
|
||||
};
|
||||
|
||||
// A class of tests that test decryption for a variety of patterns and modes.
|
||||
// This test is parameterized by three parameters:
|
||||
// 1. The pattern used for pattern decryption.
|
||||
// 2. The cipher mode for decryption: either CTR or CBC.
|
||||
// 3. A boolean that determines if decrypt in place should be done. When the
|
||||
// output buffer is clear, it should be possible for the input and output
|
||||
// buffers to be the same.
|
||||
class OEMCryptoSessionTestsDecryptTests
|
||||
: public OEMCryptoLicenseTestAPI16,
|
||||
public WithParamInterface<tuple<OEMCrypto_CENCEncryptPatternDesc,
|
||||
OEMCryptoCipherMode, OutputType>> {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
OEMCryptoLicenseTestAPI16::SetUp();
|
||||
pattern_ = ::testing::get<0>(GetParam());
|
||||
cipher_mode_ = ::testing::get<1>(GetParam());
|
||||
decrypt_inplace_ = ::testing::get<2>(GetParam()).decrypt_inplace;
|
||||
output_buffer_type_ = ::testing::get<2>(GetParam()).type;
|
||||
verify_crc_ = global_features.supports_crc;
|
||||
// Pick a random key.
|
||||
EXPECT_EQ(GetRandBytes(key_, sizeof(key_)), 1);
|
||||
// Pick a random starting iv. Some tests override this before using it.
|
||||
EXPECT_EQ(GetRandBytes(initial_iv_, sizeof(initial_iv_)), 1);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
FreeSecureBuffers();
|
||||
OEMCryptoLicenseTestAPI16::TearDown();
|
||||
}
|
||||
|
||||
void SetSubsampleSizes(std::vector<SubsampleSize> subsample_sizes) {
|
||||
// This is just sugar for having one sample with the given subsamples in it.
|
||||
SetSampleSizes({subsample_sizes});
|
||||
}
|
||||
|
||||
void SetSampleSizes(std::vector<std::vector<SubsampleSize>> sample_sizes) {
|
||||
ASSERT_GT(sample_sizes.size(), 0u);
|
||||
samples_.clear();
|
||||
samples_.reserve(sample_sizes.size());
|
||||
|
||||
// Convert all the size arrays to TestSample structs
|
||||
for (const std::vector<SubsampleSize>& subsample_sizes : sample_sizes) {
|
||||
// This could be one line if we had C++17
|
||||
samples_.emplace_back();
|
||||
TestSample& sample = samples_.back();
|
||||
|
||||
ASSERT_GT(subsample_sizes.size(), 0u);
|
||||
sample.subsamples.reserve(subsample_sizes.size());
|
||||
|
||||
// Convert all the sizes to subsample descriptions and tally the total
|
||||
// sample size
|
||||
size_t sample_size = 0;
|
||||
size_t current_block_offset = 0;
|
||||
for (const SubsampleSize& size : subsample_sizes) {
|
||||
sample.subsamples.push_back(OEMCrypto_SubSampleDescription{
|
||||
size.clear_size, size.encrypted_size,
|
||||
0, // Subsample Flags, to be filled in after the loop
|
||||
current_block_offset});
|
||||
|
||||
// Update the rolling variables
|
||||
sample_size += size.clear_size + size.encrypted_size;
|
||||
if (cipher_mode_ == OEMCrypto_CipherMode_CENC) {
|
||||
current_block_offset =
|
||||
(current_block_offset + size.encrypted_size) % AES_BLOCK_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the subsample flags now that all the subsamples are processed
|
||||
sample.subsamples.front().subsample_flags |= OEMCrypto_FirstSubsample;
|
||||
sample.subsamples.back().subsample_flags |= OEMCrypto_LastSubsample;
|
||||
|
||||
// Set related information on the sample description
|
||||
sample.description.subsamples = sample.subsamples.data();
|
||||
sample.description.subsamples_length = sample.subsamples.size();
|
||||
sample.description.buffers.input_data_length = sample_size;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the input buffer and either a clear or secure output buffer for each
|
||||
// test sample. This should be called after SetSubsampleSizes().
|
||||
void MakeBuffers() {
|
||||
for (TestSample& sample : samples_) {
|
||||
const size_t total_size = sample.description.buffers.input_data_length;
|
||||
ASSERT_GT(total_size, 0u);
|
||||
sample.encrypted_buffer.clear();
|
||||
sample.truth_buffer.clear();
|
||||
sample.clear_buffer.clear();
|
||||
sample.encrypted_buffer.resize(total_size);
|
||||
sample.truth_buffer.resize(total_size);
|
||||
for (size_t i = 0; i < total_size; i++) sample.truth_buffer[i] = i % 256;
|
||||
|
||||
OEMCrypto_DestBufferDesc& output_descriptor =
|
||||
sample.description.buffers.output_descriptor;
|
||||
output_descriptor.type = output_buffer_type_;
|
||||
switch (output_descriptor.type) {
|
||||
case OEMCrypto_BufferType_Clear:
|
||||
if (decrypt_inplace_) {
|
||||
// Add some padding to verify there is no overrun.
|
||||
sample.encrypted_buffer.resize(total_size + kBufferOverrunPadding,
|
||||
0xaa);
|
||||
output_descriptor.buffer.clear.clear_buffer =
|
||||
sample.encrypted_buffer.data();
|
||||
} else {
|
||||
// Add some padding to verify there is no overrun.
|
||||
sample.clear_buffer.resize(total_size + kBufferOverrunPadding,
|
||||
0xaa);
|
||||
output_descriptor.buffer.clear.clear_buffer =
|
||||
sample.clear_buffer.data();
|
||||
}
|
||||
output_descriptor.buffer.clear.clear_buffer_length = total_size;
|
||||
break;
|
||||
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
output_descriptor.buffer.secure.secure_buffer_length = total_size;
|
||||
ASSERT_EQ(OEMCrypto_AllocateSecureBuffer(
|
||||
session_.session_id(), total_size, &output_descriptor,
|
||||
&sample.secure_buffer_fid),
|
||||
OEMCrypto_SUCCESS);
|
||||
ASSERT_NE(output_descriptor.buffer.secure.secure_buffer, nullptr);
|
||||
// It is OK if OEMCrypto changes the maximum size, but there must
|
||||
// still be enough room for our data.
|
||||
ASSERT_GE(output_descriptor.buffer.secure.secure_buffer_length,
|
||||
total_size);
|
||||
output_descriptor.buffer.secure.offset = 0;
|
||||
break;
|
||||
|
||||
case OEMCrypto_BufferType_Direct:
|
||||
output_descriptor.buffer.direct.is_video = false;
|
||||
break;
|
||||
} // switch (output_descriptor.type)
|
||||
} // sample loop
|
||||
}
|
||||
|
||||
void FreeSecureBuffers() {
|
||||
for (TestSample& sample : samples_) {
|
||||
OEMCrypto_DestBufferDesc& output_descriptor =
|
||||
sample.description.buffers.output_descriptor;
|
||||
if (output_descriptor.type == OEMCrypto_BufferType_Secure) {
|
||||
ASSERT_EQ(OEMCrypto_FreeSecureBuffer(session_.session_id(),
|
||||
&output_descriptor,
|
||||
sample.secure_buffer_fid),
|
||||
OEMCrypto_SUCCESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EncryptData() {
|
||||
AES_KEY aes_key;
|
||||
AES_set_encrypt_key(key_, AES_BLOCK_SIZE * 8, &aes_key);
|
||||
|
||||
for (TestSample& sample : samples_) {
|
||||
uint8_t iv[KEY_IV_SIZE]; // Current IV
|
||||
memcpy(iv, initial_iv_, KEY_IV_SIZE);
|
||||
memcpy(sample.description.iv, initial_iv_, KEY_IV_SIZE);
|
||||
|
||||
size_t buffer_index = 0; // byte index into in and out.
|
||||
size_t block_offset = 0; // byte index into current block.
|
||||
for (const OEMCrypto_SubSampleDescription& subsample :
|
||||
sample.subsamples) {
|
||||
// Copy clear content.
|
||||
if (subsample.num_bytes_clear > 0) {
|
||||
memcpy(&sample.encrypted_buffer[buffer_index],
|
||||
&sample.truth_buffer[buffer_index], subsample.num_bytes_clear);
|
||||
buffer_index += subsample.num_bytes_clear;
|
||||
}
|
||||
|
||||
// The IV resets at the start of each subsample in the 'cbcs' schema.
|
||||
if (cipher_mode_ == OEMCrypto_CipherMode_CBCS) {
|
||||
memcpy(iv, initial_iv_, KEY_IV_SIZE);
|
||||
}
|
||||
|
||||
size_t pattern_offset = 0;
|
||||
const size_t subsample_end =
|
||||
buffer_index + subsample.num_bytes_encrypted;
|
||||
while (buffer_index < subsample_end) {
|
||||
const size_t size =
|
||||
min(subsample_end - buffer_index, AES_BLOCK_SIZE - block_offset);
|
||||
const size_t pattern_length = pattern_.encrypt + pattern_.skip;
|
||||
const bool skip_block =
|
||||
(pattern_offset >= pattern_.encrypt) && (pattern_length > 0);
|
||||
if (pattern_length > 0) {
|
||||
pattern_offset = (pattern_offset + 1) % pattern_length;
|
||||
}
|
||||
// CBC mode should just copy a partial block at the end. If there
|
||||
// is a partial block at the beginning, an error is returned, so we
|
||||
// can put whatever we want in the output buffer.
|
||||
if (skip_block || ((cipher_mode_ == OEMCrypto_CipherMode_CBCS) &&
|
||||
(size < AES_BLOCK_SIZE))) {
|
||||
memcpy(&sample.encrypted_buffer[buffer_index],
|
||||
&sample.truth_buffer[buffer_index], size);
|
||||
block_offset = 0; // Next block should be complete.
|
||||
} else {
|
||||
if (cipher_mode_ == OEMCrypto_CipherMode_CENC) {
|
||||
uint8_t aes_output[AES_BLOCK_SIZE];
|
||||
AES_encrypt(iv, aes_output, &aes_key);
|
||||
for (size_t n = 0; n < size; n++) {
|
||||
sample.encrypted_buffer[buffer_index + n] =
|
||||
aes_output[n + block_offset] ^
|
||||
sample.truth_buffer[buffer_index + n];
|
||||
}
|
||||
if (size + block_offset < AES_BLOCK_SIZE) {
|
||||
// Partial block. Don't increment iv. Compute next block
|
||||
// offset.
|
||||
block_offset = block_offset + size;
|
||||
} else {
|
||||
EXPECT_EQ(block_offset + size,
|
||||
static_cast<size_t>(AES_BLOCK_SIZE));
|
||||
// Full block. Increment iv, and set offset to 0 for next
|
||||
// block.
|
||||
ctr128_inc64(1, iv);
|
||||
block_offset = 0;
|
||||
}
|
||||
} else {
|
||||
uint8_t aes_input[AES_BLOCK_SIZE];
|
||||
for (size_t n = 0; n < size; n++) {
|
||||
aes_input[n] = sample.truth_buffer[buffer_index + n] ^ iv[n];
|
||||
}
|
||||
AES_encrypt(aes_input, &sample.encrypted_buffer[buffer_index],
|
||||
&aes_key);
|
||||
memcpy(iv, &sample.encrypted_buffer[buffer_index],
|
||||
AES_BLOCK_SIZE);
|
||||
// CBC mode should always start on block boundary.
|
||||
block_offset = 0;
|
||||
}
|
||||
}
|
||||
buffer_index += size;
|
||||
} // encryption loop
|
||||
} // per-subsample loop
|
||||
} // per-sample loop
|
||||
}
|
||||
|
||||
void LoadLicense() {
|
||||
uint32_t control = wvoec::kControlNonceEnabled;
|
||||
if (verify_crc_) control |= kControlAllowHashVerification;
|
||||
if (output_buffer_type_ == OEMCrypto_BufferType_Secure)
|
||||
control |= kControlObserveDataPath | kControlDataPathSecure;
|
||||
license_messages_.set_control(control);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
license_messages_.core_response()
|
||||
.timer_limits.initial_renewal_duration_seconds = kDuration;
|
||||
memcpy(license_messages_.response_data().keys[0].key_data, key_,
|
||||
sizeof(key_));
|
||||
license_messages_.response_data().keys[0].cipher_mode = cipher_mode_;
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
ASSERT_EQ(GetKeyHandleIntoVector(session_.session_id(),
|
||||
session_.license().keys[0].key_id,
|
||||
session_.license().keys[0].key_id_length,
|
||||
cipher_mode_, key_handle_),
|
||||
OEMCrypto_SUCCESS);
|
||||
}
|
||||
|
||||
void TestDecryptCENC() { ASSERT_EQ(DecryptCENC(), OEMCrypto_SUCCESS); }
|
||||
|
||||
void ValidateDecryptedData() {
|
||||
for (TestSample& sample : samples_) {
|
||||
if (sample.description.buffers.output_descriptor.type ==
|
||||
OEMCrypto_BufferType_Clear) {
|
||||
const size_t total_size = sample.description.buffers.input_data_length;
|
||||
// To verify there is no buffer overrun after decrypting, look at the
|
||||
// padded bytes just after the data buffer that was written. It
|
||||
// should not have changed from the original 0xaa that we set in
|
||||
// MakeBuffer function.
|
||||
if (decrypt_inplace_) {
|
||||
EXPECT_EQ(std::count(sample.encrypted_buffer.begin() + total_size,
|
||||
sample.encrypted_buffer.end(), 0xaa),
|
||||
static_cast<int32_t>(kBufferOverrunPadding))
|
||||
<< "Buffer overrun.";
|
||||
sample.encrypted_buffer.resize(total_size); // Remove padding.
|
||||
// We expect encrypted buffer to have been changed by OEMCrypto.
|
||||
EXPECT_EQ(sample.encrypted_buffer, sample.truth_buffer);
|
||||
} else {
|
||||
EXPECT_EQ(std::count(sample.clear_buffer.begin() + total_size,
|
||||
sample.clear_buffer.end(), 0xaa),
|
||||
static_cast<int32_t>(kBufferOverrunPadding))
|
||||
<< "Buffer overrun.";
|
||||
sample.clear_buffer.resize(total_size); // Remove padding.
|
||||
EXPECT_EQ(sample.clear_buffer, sample.truth_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (verify_crc_) {
|
||||
uint32_t frame;
|
||||
ASSERT_EQ(OEMCrypto_GetHashErrorCode(session_.session_id(), &frame),
|
||||
OEMCrypto_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
OEMCryptoResult DecryptCENC() {
|
||||
// OEMCrypto only supports providing a decrypt hash for one sample.
|
||||
if (samples_.size() > 1) verify_crc_ = false;
|
||||
|
||||
// If supported, check the decrypt hashes.
|
||||
if (verify_crc_) {
|
||||
const TestSample& sample = samples_[0];
|
||||
|
||||
uint32_t hash =
|
||||
util::wvcrc32(sample.truth_buffer.data(), sample.truth_buffer.size());
|
||||
OEMCrypto_SetDecryptHash(session_.session_id(), 1,
|
||||
reinterpret_cast<const uint8_t*>(&hash),
|
||||
sizeof(hash));
|
||||
}
|
||||
|
||||
// Build an array of just the sample descriptions.
|
||||
std::vector<OEMCrypto_SampleDescription> sample_descriptions;
|
||||
sample_descriptions.reserve(samples_.size());
|
||||
for (TestSample& sample : samples_) {
|
||||
// This must be deferred until this point in case the test modifies the
|
||||
// buffer before testing decrypt.
|
||||
sample.description.buffers.input_data = sample.encrypted_buffer.data();
|
||||
// Append to the description array.
|
||||
sample_descriptions.push_back(sample.description);
|
||||
}
|
||||
|
||||
// Perform decryption using the test data that was previously set up.
|
||||
OEMCryptoResult result = DecryptFallbackChain::Decrypt(
|
||||
key_handle_.data(), key_handle_.size(), sample_descriptions.data(),
|
||||
sample_descriptions.size(), cipher_mode_, &pattern_);
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
ValidateDecryptedData();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parameters of test case
|
||||
OEMCrypto_CENCEncryptPatternDesc pattern_;
|
||||
OEMCryptoCipherMode cipher_mode_;
|
||||
bool decrypt_inplace_; // If true, input and output buffers are the same.
|
||||
OEMCryptoBufferType output_buffer_type_;
|
||||
|
||||
bool verify_crc_;
|
||||
uint8_t key_[AES_BLOCK_SIZE]; // Encryption Key.
|
||||
uint8_t initial_iv_[KEY_IV_SIZE]; // Starting IV for every sample.
|
||||
std::vector<TestSample> samples_;
|
||||
std::vector<uint8_t> key_handle_;
|
||||
};
|
||||
|
||||
} // namespace wvoec
|
||||
|
||||
#endif // CDM_OEMCRYPTO_DECRYPT_TEST_
|
||||
@@ -4,11 +4,11 @@
|
||||
//
|
||||
|
||||
#include "oemcrypto_license_test.h"
|
||||
#include "log.h"
|
||||
#include "oemcrypto_basic_test.h"
|
||||
#include "oemcrypto_resource_test.h"
|
||||
#include "oemcrypto_session_tests_helper.h"
|
||||
|
||||
#include "platform.h"
|
||||
#include "test_sleep.h"
|
||||
|
||||
using ::testing::Range;
|
||||
|
||||
namespace wvoec {
|
||||
|
||||
@@ -803,5 +803,169 @@ TEST_F(OEMCryptoSessionTests, CheckMinimumPatchLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Load, Refresh Keys Test
|
||||
//
|
||||
|
||||
// Refresh keys should work if the license uses a nonce.
|
||||
TEST_P(OEMCryptoRefreshTest, RefreshWithNonce) {
|
||||
LoadLicense();
|
||||
RenewalRoundTrip renewal_messages(&license_messages_);
|
||||
MakeRenewalRequest(&renewal_messages);
|
||||
LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS);
|
||||
}
|
||||
|
||||
// Refresh keys should work if the license does not use a nonce.
|
||||
TEST_P(OEMCryptoRefreshTest, RefreshNoNonce) {
|
||||
license_messages_.set_control(0);
|
||||
LoadLicense();
|
||||
RenewalRoundTrip renewal_messages(&license_messages_);
|
||||
MakeRenewalRequest(&renewal_messages);
|
||||
LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS);
|
||||
}
|
||||
|
||||
// Refresh keys should NOT work if a license has not been loaded.
|
||||
TEST_P(OEMCryptoRefreshTestAPI16, RefreshNoLicense) {
|
||||
Session s;
|
||||
s.open();
|
||||
constexpr size_t message_size = kMaxCoreMessage + 42;
|
||||
std::vector<uint8_t> data(message_size);
|
||||
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
|
||||
size_t gen_signature_length = 0;
|
||||
size_t core_message_length = 0;
|
||||
OEMCryptoResult sts = OEMCrypto_PrepAndSignRenewalRequest(
|
||||
s.session_id(), data.data(), data.size(), &core_message_length, nullptr,
|
||||
&gen_signature_length);
|
||||
ASSERT_LT(core_message_length, message_size);
|
||||
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
|
||||
vector<uint8_t> gen_signature(gen_signature_length);
|
||||
sts = OEMCrypto_PrepAndSignRenewalRequest(
|
||||
s.session_id(), data.data(), data.size(), &core_message_length,
|
||||
gen_signature.data(), &gen_signature_length);
|
||||
}
|
||||
ASSERT_NE(OEMCrypto_SUCCESS, sts);
|
||||
}
|
||||
|
||||
// Refresh keys should fail if the nonce is not in the session.
|
||||
TEST_P(OEMCryptoRefreshTestAPI16, RefreshBadNonce) {
|
||||
LoadLicense();
|
||||
RenewalRoundTrip renewal_messages(&license_messages_);
|
||||
MakeRenewalRequest(&renewal_messages);
|
||||
renewal_messages.core_request().nonce ^= 42;
|
||||
LoadRenewal(&renewal_messages, OEMCrypto_ERROR_INVALID_NONCE);
|
||||
}
|
||||
|
||||
// Refresh keys should fail if the session_id does not match the license.
|
||||
TEST_P(OEMCryptoRefreshTestAPI16, RefreshBadSessionID) {
|
||||
LoadLicense();
|
||||
RenewalRoundTrip renewal_messages(&license_messages_);
|
||||
MakeRenewalRequest(&renewal_messages);
|
||||
renewal_messages.core_request().session_id += 1;
|
||||
LoadRenewal(&renewal_messages, OEMCrypto_ERROR_INVALID_NONCE);
|
||||
}
|
||||
|
||||
// Refresh keys should handle the maximum message size.
|
||||
TEST_P(OEMCryptoRefreshTest, RefreshLargeBuffer) {
|
||||
LoadLicense();
|
||||
RenewalRoundTrip renewal_messages(&license_messages_);
|
||||
const size_t max_size = GetResourceValue(kLargeMessageSize);
|
||||
renewal_messages.set_message_size(max_size);
|
||||
MakeRenewalRequest(&renewal_messages);
|
||||
LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS);
|
||||
}
|
||||
|
||||
// This situation would occur if an app only uses one key in the license. When
|
||||
// that happens, SelectKey would be called before the first decrypt, and then
|
||||
// would not need to be called again, even if the license is refreshed.
|
||||
TEST_P(OEMCryptoRefreshTest, RefreshWithNoSelectKey) {
|
||||
LoadLicense();
|
||||
|
||||
// Call select key before the refresh. No calls below to TestDecryptCTR with
|
||||
// select key set to true.
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(true));
|
||||
|
||||
// This should still be valid key, even if the refresh failed, because this
|
||||
// is before the original license duration.
|
||||
wvutil::TestSleep::Sleep(kShortSleep);
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false));
|
||||
|
||||
// This should be after duration of the original license, but before the
|
||||
// expiration of the refresh message. This should fail until we have loaded
|
||||
// the renewal.
|
||||
wvutil::TestSleep::Sleep(kShortSleep + kLongSleep);
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED));
|
||||
|
||||
RenewalRoundTrip renewal_messages(&license_messages_);
|
||||
MakeRenewalRequest(&renewal_messages);
|
||||
LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS);
|
||||
|
||||
// After we've loaded the renewal, decrypt should succeed again.
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false));
|
||||
}
|
||||
|
||||
// Test that playback clock is correctly started and that the license can be
|
||||
// renewed.
|
||||
TEST_P(OEMCryptoRefreshTest, RenewLicenseLoadSuccess) {
|
||||
license_messages_.core_response().renewal_delay_base = OEMCrypto_License_Load;
|
||||
timer_limits_.rental_duration_seconds = kDuration; // 2 seconds.
|
||||
timer_limits_.initial_renewal_duration_seconds = kLongDuration; // 5 seconds.
|
||||
// First version to support Renew on Load.
|
||||
constexpr uint32_t kFeatureVersion = 18;
|
||||
|
||||
// Loading the license should start the playback clock.
|
||||
LoadLicense();
|
||||
// Sleep until just after rental window is over.
|
||||
wvutil::TestSleep::Sleep(kDuration + kShortSleep);
|
||||
if (license_api_version_ < kFeatureVersion ||
|
||||
global_features.api_version < kFeatureVersion) {
|
||||
// If the feature is not supported, then we expect failure because the
|
||||
// playback clock was not started and we are outside the rental window.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(true, OEMCrypto_ERROR_KEY_EXPIRED));
|
||||
return;
|
||||
} else {
|
||||
// If the feature is supported, we expect decrypt to work because we are
|
||||
// still within the initial renewal window, and the playback clock should
|
||||
// have started.
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(true, OEMCrypto_SUCCESS));
|
||||
}
|
||||
// This is after the initial renewal duration, so we expect failure before
|
||||
// loading the renewal.
|
||||
wvutil::TestSleep::Sleep(kShortSleep + kLongSleep);
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED));
|
||||
|
||||
RenewalRoundTrip renewal_messages(&license_messages_);
|
||||
MakeRenewalRequest(&renewal_messages);
|
||||
LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS);
|
||||
|
||||
// After we've loaded the renewal, decrypt should succeed again.
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false));
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoRefreshTest, RenewLicenseLoadOutsideRentalDuration) {
|
||||
license_messages_.core_response().renewal_delay_base = OEMCrypto_License_Load;
|
||||
timer_limits_.rental_duration_seconds = kDuration; // 2 seconds.
|
||||
timer_limits_.initial_renewal_duration_seconds = kLongDuration; // 5 seconds.
|
||||
|
||||
// Sleep until just after rental window is over.
|
||||
wvutil::TestSleep::Sleep(kDuration + kShortSleep);
|
||||
// Loading the license should start the playback clock.
|
||||
LoadLicense();
|
||||
// If the license is loaded after the rental duration window, we expect
|
||||
// failure.
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE));
|
||||
return;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoRefreshTest,
|
||||
Range<uint32_t>(kCurrentAPI - 1, kCurrentAPI + 1));
|
||||
|
||||
// These tests only work when the license has a core message.
|
||||
INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoRefreshTestAPI16,
|
||||
Range<uint32_t>(kCoreMessagesAPI, kCurrentAPI + 1));
|
||||
|
||||
/// @}
|
||||
} // namespace wvoec
|
||||
@@ -10,11 +10,12 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "clock.h"
|
||||
#include "log.h"
|
||||
#include "oec_session_util.h"
|
||||
#include "oemcrypto_basic_test.h"
|
||||
#include "oemcrypto_corpus_generator_helper.h"
|
||||
#include "oemcrypto_resource_test.h"
|
||||
#include "oemcrypto_session_tests_helper.h"
|
||||
#include "wvcrc32.h"
|
||||
|
||||
using ::testing::WithParamInterface;
|
||||
|
||||
@@ -237,6 +238,173 @@ class OEMCryptoLicenseTest : public OEMCryptoLicenseTestAPI16,
|
||||
}
|
||||
};
|
||||
|
||||
// Test usage table functionality.
|
||||
class LicenseWithUsageEntry {
|
||||
public:
|
||||
LicenseWithUsageEntry(const std::string& pst = "my_pst")
|
||||
: session_(),
|
||||
license_messages_(&session_),
|
||||
generic_crypto_(false),
|
||||
time_license_received_(0),
|
||||
time_first_decrypt_(0),
|
||||
time_last_decrypt_(0),
|
||||
active_(true) {
|
||||
license_messages_.set_pst(pst);
|
||||
}
|
||||
|
||||
void MakeAndLoadOnline(OEMCryptoSessionTests* test) {
|
||||
MakeAndLoad(test,
|
||||
wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired);
|
||||
}
|
||||
|
||||
// If status in not a nullptr, then creating a new entry is allowed to fail,
|
||||
// and its error code is stored in status.
|
||||
void MakeOfflineAndClose(OEMCryptoSessionTests* test,
|
||||
OEMCryptoResult* status = nullptr) {
|
||||
MakeAndLoad(test, wvoec::kControlNonceOrEntry, status);
|
||||
if (status != nullptr && *status != OEMCrypto_SUCCESS) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.close());
|
||||
return;
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.UpdateUsageEntry(&(test->encrypted_usage_header_)));
|
||||
ASSERT_NO_FATAL_FAILURE(GenerateVerifyReport(kUnused));
|
||||
ASSERT_NO_FATAL_FAILURE(session_.close());
|
||||
}
|
||||
|
||||
// If status in not a nullptr, then creating a new entry is allowed to fail,
|
||||
// and its error code is stored in status.
|
||||
void MakeAndLoad(SessionUtil* util, uint32_t control,
|
||||
OEMCryptoResult* status = nullptr) {
|
||||
license_messages_.set_control(control);
|
||||
ASSERT_NO_FATAL_FAILURE(session_.open());
|
||||
ASSERT_NO_FATAL_FAILURE(util->InstallTestDrmKey(&session_));
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
if (generic_crypto_) {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
license_messages_.CreateResponseWithGenericCryptoKeys());
|
||||
} else {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry(status));
|
||||
if (status != nullptr && *status != OEMCrypto_SUCCESS) return;
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
time_license_received_ = wvutil::Clock().GetCurrentTime();
|
||||
}
|
||||
|
||||
void OpenAndReload(SessionUtil* util) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.open());
|
||||
ASSERT_NO_FATAL_FAILURE(session_.ReloadUsageEntry());
|
||||
ASSERT_NO_FATAL_FAILURE(util->InstallTestDrmKey(&session_));
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateDerivedKeysFromSessionKey());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
}
|
||||
|
||||
// Test decrypt, and update the decrypt times for the pst report.
|
||||
void TestDecryptCTR(bool select_key_first = true,
|
||||
OEMCryptoResult expected_result = OEMCrypto_SUCCESS) {
|
||||
session_.TestDecryptCTR(select_key_first, expected_result);
|
||||
time_last_decrypt_ = wvutil::Clock().GetCurrentTime();
|
||||
if (time_first_decrypt_ == 0) time_first_decrypt_ = time_last_decrypt_;
|
||||
}
|
||||
|
||||
void DeactivateUsageEntry() {
|
||||
active_ = false;
|
||||
if (ShouldGenerateCorpus()) {
|
||||
const std::string file_name =
|
||||
GetFileName("oemcrypto_deactivate_usage_entry_fuzz_seed_corpus");
|
||||
AppendToFile(file_name, pst().c_str(), pst().length());
|
||||
}
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
OEMCrypto_DeactivateUsageEntry(
|
||||
session_.session_id(),
|
||||
reinterpret_cast<const uint8_t*>(pst().c_str()), pst().length()));
|
||||
}
|
||||
|
||||
void GenerateVerifyReport(OEMCrypto_Usage_Entry_Status status) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst()));
|
||||
Test_PST_Report expected(pst(), status);
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
session_.VerifyReport(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.
|
||||
RenewalRoundTrip renewal_messages(&license_messages_);
|
||||
renewal_messages.set_is_release(!active_);
|
||||
ASSERT_NO_FATAL_FAILURE(renewal_messages.SignAndVerifyRequest());
|
||||
}
|
||||
|
||||
void ReloadUsageEntry() {
|
||||
session_.ReloadUsageEntry();
|
||||
session_.set_mac_keys(license_messages_.response_data().mac_keys);
|
||||
}
|
||||
|
||||
const std::string& pst() const { return license_messages_.pst(); }
|
||||
void set_pst(const std::string& pst) { license_messages_.set_pst(pst); }
|
||||
LicenseRoundTrip& license_messages() { return license_messages_; }
|
||||
Session& session() { return session_; }
|
||||
void set_generic_crypto(bool generic_crypto) {
|
||||
generic_crypto_ = generic_crypto;
|
||||
}
|
||||
|
||||
private:
|
||||
Session session_;
|
||||
LicenseRoundTrip license_messages_;
|
||||
bool generic_crypto_;
|
||||
int64_t time_license_received_;
|
||||
int64_t time_first_decrypt_;
|
||||
int64_t time_last_decrypt_;
|
||||
bool active_;
|
||||
};
|
||||
|
||||
class OEMCryptoRefreshTest : public OEMCryptoLicenseTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
OEMCryptoLicenseTest::SetUp();
|
||||
// These values allow us to run a few simple duration tests or just start
|
||||
// playback right away. All times are in seconds since the license was
|
||||
// signed.
|
||||
// Soft expiry false means timers are strictly enforce.
|
||||
timer_limits_.soft_enforce_rental_duration = true;
|
||||
timer_limits_.soft_enforce_playback_duration = false;
|
||||
// Playback may begin immediately.
|
||||
timer_limits_.earliest_playback_start_seconds = 0;
|
||||
// First playback may be within the first two seconds.
|
||||
timer_limits_.rental_duration_seconds = kDuration;
|
||||
// Once started, playback may last two seconds without a renewal.
|
||||
timer_limits_.initial_renewal_duration_seconds = kDuration;
|
||||
// Total playback is not limited.
|
||||
timer_limits_.total_playback_duration_seconds = 0;
|
||||
}
|
||||
|
||||
void LoadLicense() {
|
||||
license_messages_.core_response().timer_limits = timer_limits_;
|
||||
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());
|
||||
}
|
||||
|
||||
void MakeRenewalRequest(RenewalRoundTrip* renewal_messages) {
|
||||
ASSERT_NO_FATAL_FAILURE(renewal_messages->SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(renewal_messages->CreateDefaultResponse());
|
||||
}
|
||||
|
||||
void LoadRenewal(RenewalRoundTrip* renewal_messages,
|
||||
OEMCryptoResult expected_result) {
|
||||
ASSERT_NO_FATAL_FAILURE(renewal_messages->EncryptAndSignResponse());
|
||||
ASSERT_EQ(expected_result, renewal_messages->LoadResponse());
|
||||
}
|
||||
|
||||
ODK_TimerLimits timer_limits_;
|
||||
};
|
||||
|
||||
// This class is for the refresh tests that should only be run on licenses with
|
||||
// a core message.
|
||||
class OEMCryptoRefreshTestAPI16 : public OEMCryptoRefreshTest {};
|
||||
|
||||
} // namespace wvoec
|
||||
|
||||
#endif // CDM_OEMCRYPTO_LICENSE_TEST_
|
||||
@@ -6,9 +6,6 @@
|
||||
#include "oemcrypto_provisioning_test.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "oemcrypto_basic_test.h"
|
||||
#include "oemcrypto_resource_test.h"
|
||||
#include "oemcrypto_session_tests_helper.h"
|
||||
#include "platform.h"
|
||||
|
||||
namespace wvoec {
|
||||
@@ -623,5 +620,446 @@ TEST_F(OEMCryptoProv40Test, ProvisionDrmCert) {
|
||||
ASSERT_EQ(s.IsPublicKeySet(), true);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertificate, PrepAndSignLicenseRequestCounterAPI18) {
|
||||
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
|
||||
Session s;
|
||||
ASSERT_NO_FATAL_FAILURE(s.open());
|
||||
ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_));
|
||||
s.GenerateNonce();
|
||||
|
||||
size_t core_message_length = 100;
|
||||
std::vector<uint8_t> message(128, 0);
|
||||
std::vector<uint8_t> signature(256, 0);
|
||||
size_t signature_length = signature.size();
|
||||
|
||||
OEMCryptoResult result = OEMCrypto_PrepAndSignLicenseRequest(
|
||||
s.session_id(), message.data(), message.size(), &core_message_length,
|
||||
signature.data(), &signature_length);
|
||||
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, result);
|
||||
}
|
||||
|
||||
// This test verifies that we can create a wrapped RSA key, and then reload it.
|
||||
TEST_F(OEMCryptoLoadsCertificate, LoadRSASessionKey) {
|
||||
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
|
||||
Session s;
|
||||
ASSERT_NO_FATAL_FAILURE(s.open());
|
||||
ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_));
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertificate, SignProvisioningRequest) {
|
||||
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.SignAndVerifyRequest());
|
||||
}
|
||||
|
||||
// This tests a large message size. The size is larger than we required in v15.
|
||||
TEST_F(OEMCryptoLoadsCertificate, SignLargeProvisioningRequestAPI16) {
|
||||
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.SignAndVerifyRequest());
|
||||
}
|
||||
|
||||
// This creates a wrapped RSA key, and then does the sanity check that the
|
||||
// unencrypted key is not found in the wrapped key. The wrapped key should be
|
||||
// encrypted.
|
||||
TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
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());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse());
|
||||
// We should not be able to find the rsa key in the wrapped key. It should
|
||||
// be encrypted.
|
||||
EXPECT_EQ(nullptr, find(provisioning_messages.wrapped_rsa_key(),
|
||||
provisioning_messages.encoded_rsa_key()));
|
||||
}
|
||||
|
||||
// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning
|
||||
// message.
|
||||
TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1_API16) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
|
||||
// Encrypt and sign once, so that we can use the size of the response.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
provisioning_messages.core_response().enc_private_key.offset =
|
||||
provisioning_messages.encrypted_response_buffer().size() + 1;
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse());
|
||||
provisioning_messages.VerifyLoadFailed();
|
||||
}
|
||||
|
||||
// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning
|
||||
// message.
|
||||
TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2_API16) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
|
||||
// Encrypt and sign once, so that we can use the size of the response.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
provisioning_messages.core_response().enc_private_key_iv.offset =
|
||||
provisioning_messages.encrypted_response_buffer().size() + 1;
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse());
|
||||
provisioning_messages.VerifyLoadFailed();
|
||||
}
|
||||
|
||||
// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning
|
||||
// message.
|
||||
TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3_API16) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
|
||||
// Encrypt and sign once, so that we can use the size of the response.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
// If the offset is before the end, but the offset+length is bigger, then
|
||||
// the message should be rejected.
|
||||
provisioning_messages.core_response().enc_private_key.offset =
|
||||
provisioning_messages.encrypted_response_buffer().size() - 5;
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse());
|
||||
provisioning_messages.VerifyLoadFailed();
|
||||
}
|
||||
|
||||
// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning
|
||||
// message.
|
||||
TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4_API16) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
|
||||
// Encrypt and sign once, so that we can use the size of the response.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
// If the offset is before the end, but the offset+length is bigger, then
|
||||
// the message should be rejected.
|
||||
provisioning_messages.core_response().enc_private_key_iv.offset =
|
||||
provisioning_messages.encrypted_response_buffer().size() - 5;
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse());
|
||||
provisioning_messages.VerifyLoadFailed();
|
||||
}
|
||||
|
||||
// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning
|
||||
// message.
|
||||
TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30_API16) {
|
||||
if (global_features.provisioning_method != OEMCrypto_OEMCertificate) {
|
||||
GTEST_SKIP() << "Test for Prov 3.0 devices only.";
|
||||
}
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
|
||||
// Encrypt and sign once, so that we can use the size of the response.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
// If the offset is before the end, but the offset+length is bigger, then
|
||||
// the message should be rejected.
|
||||
provisioning_messages.core_response().encrypted_message_key.offset =
|
||||
provisioning_messages.encrypted_response_buffer().size() + 1;
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse());
|
||||
provisioning_messages.VerifyLoadFailed();
|
||||
}
|
||||
|
||||
// Test that RewrapDeviceRSAKey verifies the message signature.
|
||||
// TODO(b/144186970): This test should also run on Prov 3.0 devices.
|
||||
TEST_F(OEMCryptoLoadsCertificate,
|
||||
CertificateProvisionBadSignatureKeyboxTestAPI16) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
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());
|
||||
provisioning_messages.response_signature()[4] ^= 42; // bad signature.
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE,
|
||||
provisioning_messages.LoadResponse());
|
||||
provisioning_messages.VerifyLoadFailed();
|
||||
}
|
||||
|
||||
// Test that RewrapDeviceRSAKey verifies the nonce is current.
|
||||
TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce_API16) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
|
||||
provisioning_messages.core_request().nonce ^= 42; // bad nonce.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE,
|
||||
provisioning_messages.LoadResponse());
|
||||
provisioning_messages.VerifyLoadFailed();
|
||||
}
|
||||
|
||||
// Test that RewrapDeviceRSAKey verifies the RSA key is valid.
|
||||
TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRSAKey) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse());
|
||||
provisioning_messages.response_data().rsa_key[4] ^= 42; // bad key.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse());
|
||||
ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse());
|
||||
provisioning_messages.VerifyLoadFailed();
|
||||
}
|
||||
|
||||
// Test that RewrapDeviceRSAKey verifies the RSA key is valid.
|
||||
// TODO(b/144186970): This test should also run on Prov 3.0 devices.
|
||||
TEST_F(OEMCryptoLoadsCertificate,
|
||||
CertificateProvisionBadRSAKeyKeyboxTestAPI16) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
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());
|
||||
size_t rsa_offset =
|
||||
provisioning_messages.core_response().enc_private_key.offset;
|
||||
// Offsets are relative to the message body, after the core message.
|
||||
rsa_offset += provisioning_messages.serialized_core_message().size();
|
||||
rsa_offset += 4; // Change the middle of the key.
|
||||
provisioning_messages.encrypted_response_buffer()[rsa_offset] ^= 42;
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE,
|
||||
provisioning_messages.LoadResponse());
|
||||
provisioning_messages.VerifyLoadFailed();
|
||||
}
|
||||
|
||||
// Test that RewrapDeviceRSAKey accepts the maximum message size.
|
||||
TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionLargeBuffer) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
const size_t max_size = GetResourceValue(kLargeMessageSize);
|
||||
provisioning_messages.set_message_size(max_size);
|
||||
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());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse());
|
||||
// We should not be able to find the rsa key in the wrapped key. It should
|
||||
// be encrypted.
|
||||
EXPECT_EQ(nullptr, find(provisioning_messages.wrapped_rsa_key(),
|
||||
provisioning_messages.encoded_rsa_key()));
|
||||
}
|
||||
|
||||
// Test that a wrapped RSA key can be loaded.
|
||||
TEST_F(OEMCryptoLoadsCertificate, LoadWrappedRSAKey) {
|
||||
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
|
||||
Session s;
|
||||
ASSERT_NO_FATAL_FAILURE(s.open());
|
||||
ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_));
|
||||
}
|
||||
|
||||
class OEMCryptoLoadsCertVariousKeys : public OEMCryptoLoadsCertificate {
|
||||
public:
|
||||
void TestKey(const uint8_t* key, size_t key_length) {
|
||||
encoded_rsa_key_.assign(key, key + key_length);
|
||||
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
|
||||
Session s;
|
||||
ASSERT_NO_FATAL_FAILURE(s.open());
|
||||
ASSERT_NO_FATAL_FAILURE(s.SetRsaPublicKeyFromPrivateKeyInfo(
|
||||
encoded_rsa_key_.data(), encoded_rsa_key_.size()));
|
||||
ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_));
|
||||
|
||||
LicenseRoundTrip license_messages(&s);
|
||||
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());
|
||||
ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR());
|
||||
}
|
||||
};
|
||||
|
||||
// Test a 3072 bit RSA key certificate.
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestLargeRSAKey3072) {
|
||||
if (!global_features.supports_rsa_3072) {
|
||||
GTEST_SKIP() << "OEMCrypto does not support RSA 3072";
|
||||
}
|
||||
TestKey(kTestRSAPKCS8PrivateKeyInfo3_3072,
|
||||
sizeof(kTestRSAPKCS8PrivateKeyInfo3_3072));
|
||||
}
|
||||
|
||||
// Test an RSA key certificate which has a private key generated using the
|
||||
// Carmichael totient.
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestCarmichaelRSAKey) {
|
||||
TestKey(kTestKeyRSACarmichael_2048, sizeof(kTestKeyRSACarmichael_2048));
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestCarmichaelNonZeroNormalDer) {
|
||||
TestKey(kCarmichaelNonZeroNormalDer, kCarmichaelNonZeroNormalDerLen);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestCarmichaelNonZeroShortDer) {
|
||||
TestKey(kCarmichaelNonZeroShortDer, kCarmichaelNonZeroShortDerLen);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestCarmichaelZeroNormalDer) {
|
||||
TestKey(kCarmichaelZeroNormalDer, kCarmichaelZeroNormalDerLen);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestCarmichaelZeroShortDer) {
|
||||
TestKey(kCarmichaelZeroShortDer, kCarmichaelZeroShortDerLen);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestDualNonZeroNormalDer) {
|
||||
TestKey(kDualNonZeroNormalDer, kDualNonZeroNormalDerLen);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestDualNonZeroShortDer) {
|
||||
TestKey(kDualNonZeroShortDer, kDualNonZeroShortDerLen);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestDualZeroNormalDer) {
|
||||
TestKey(kDualZeroNormalDer, kDualZeroNormalDerLen);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestDualZeroShortDer) {
|
||||
TestKey(kDualZeroShortDer, kDualZeroShortDerLen);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestEulerNonZeroNormalDer) {
|
||||
TestKey(kEulerNonZeroNormalDer, kEulerNonZeroNormalDerLen);
|
||||
}
|
||||
|
||||
TEST_F(OEMCryptoLoadsCertVariousKeys, TestEulerZeroNormalDer) {
|
||||
TestKey(kEulerZeroNormalDer, kEulerZeroNormalDerLen);
|
||||
}
|
||||
|
||||
// This tests that two sessions can use different RSA keys simultaneously.
|
||||
TEST_F(OEMCryptoLoadsCertificate, TestMultipleRSAKeys) {
|
||||
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
|
||||
Session s1; // Session s1 loads the default rsa key, but doesn't use it
|
||||
// until after s2 uses its key.
|
||||
ASSERT_NO_FATAL_FAILURE(s1.open());
|
||||
ASSERT_NO_FATAL_FAILURE(s1.SetRsaPublicKeyFromPrivateKeyInfo(
|
||||
encoded_rsa_key_.data(), encoded_rsa_key_.size()));
|
||||
ASSERT_NO_FATAL_FAILURE(s1.LoadWrappedRsaDrmKey(wrapped_drm_key_));
|
||||
|
||||
Session s2; // Session s2 uses a different rsa key.
|
||||
encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeyInfo4_2048,
|
||||
kTestRSAPKCS8PrivateKeyInfo4_2048 +
|
||||
sizeof(kTestRSAPKCS8PrivateKeyInfo4_2048));
|
||||
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
|
||||
ASSERT_NO_FATAL_FAILURE(s2.open());
|
||||
ASSERT_NO_FATAL_FAILURE(s2.SetRsaPublicKeyFromPrivateKeyInfo(
|
||||
encoded_rsa_key_.data(), encoded_rsa_key_.size()));
|
||||
ASSERT_NO_FATAL_FAILURE(s2.LoadWrappedRsaDrmKey(wrapped_drm_key_));
|
||||
LicenseRoundTrip license_messages2(&s2);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages2.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages2.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages2.LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR());
|
||||
s2.close();
|
||||
|
||||
// After s2 has loaded its rsa key, we continue using s1's key.
|
||||
LicenseRoundTrip license_messages1(&s1);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages1.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages1.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages1.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages1.LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR());
|
||||
}
|
||||
|
||||
// This tests the maximum number of DRM private keys that OEMCrypto can load
|
||||
TEST_F(OEMCryptoLoadsCertificate, TestMaxDRMKeys) {
|
||||
const size_t max_total_keys = GetResourceValue(kMaxTotalDRMPrivateKeys);
|
||||
std::vector<std::unique_ptr<Session>> sessions;
|
||||
std::vector<std::unique_ptr<LicenseRoundTrip>> licenses;
|
||||
|
||||
// It should be able to load up to kMaxTotalDRMPrivateKeys keys
|
||||
for (size_t i = 0; i < max_total_keys; i++) {
|
||||
sessions.push_back(std::unique_ptr<Session>(new Session()));
|
||||
licenses.push_back(std::unique_ptr<LicenseRoundTrip>(
|
||||
new LicenseRoundTrip(sessions[i].get())));
|
||||
const size_t key_index = i % kTestRSAPKCS8PrivateKeys_2048.size();
|
||||
encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeys_2048[key_index].begin(),
|
||||
kTestRSAPKCS8PrivateKeys_2048[key_index].end());
|
||||
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
|
||||
ASSERT_NO_FATAL_FAILURE(sessions[i]->open());
|
||||
ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(sessions[i].get()));
|
||||
}
|
||||
|
||||
// Attempts to load one more key than the kMaxTotalDRMPrivateKeys
|
||||
if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) {
|
||||
Session s;
|
||||
const size_t buffer_size = 5000; // Make sure it is large enough.
|
||||
std::vector<uint8_t> public_key(buffer_size);
|
||||
size_t public_key_size = buffer_size;
|
||||
std::vector<uint8_t> public_key_signature(buffer_size);
|
||||
size_t public_key_signature_size = buffer_size;
|
||||
std::vector<uint8_t> wrapped_private_key(buffer_size);
|
||||
size_t wrapped_private_key_size = buffer_size;
|
||||
OEMCrypto_PrivateKeyType key_type;
|
||||
OEMCryptoResult result = OEMCrypto_GenerateCertificateKeyPair(
|
||||
s.session_id(), public_key.data(), &public_key_size,
|
||||
public_key_signature.data(), &public_key_signature_size,
|
||||
wrapped_private_key.data(), &wrapped_private_key_size, &key_type);
|
||||
// Key creation is allowed to fail due to resource restriction
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
ASSERT_TRUE(result == OEMCrypto_ERROR_INSUFFICIENT_RESOURCES ||
|
||||
result == OEMCrypto_ERROR_TOO_MANY_KEYS);
|
||||
}
|
||||
} else {
|
||||
Session s;
|
||||
encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeyInfo2_2048,
|
||||
kTestRSAPKCS8PrivateKeyInfo2_2048 +
|
||||
sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048));
|
||||
Session ps;
|
||||
ProvisioningRoundTrip provisioning_messages(&ps, encoded_rsa_key_);
|
||||
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());
|
||||
OEMCryptoResult result = provisioning_messages.LoadResponse();
|
||||
// Key loading is allowed to fail due to resource restriction
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
ASSERT_TRUE(result == OEMCrypto_ERROR_INSUFFICIENT_RESOURCES ||
|
||||
result == OEMCrypto_ERROR_TOO_MANY_KEYS);
|
||||
}
|
||||
}
|
||||
// Verifies that the DRM keys which are already loaded should still function
|
||||
for (size_t i = 0; i < licenses.size(); i++) {
|
||||
ASSERT_NO_FATAL_FAILURE(licenses[i]->SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(licenses[i]->CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(licenses[i]->EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, licenses[i]->LoadResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(sessions[i]->TestDecryptCTR());
|
||||
}
|
||||
}
|
||||
|
||||
// Devices that load certificates, should at least support RSA 2048 keys.
|
||||
TEST_F(OEMCryptoLoadsCertificate, SupportsCertificatesAPI13) {
|
||||
ASSERT_NE(0u,
|
||||
OEMCrypto_Supports_RSA_2048bit & OEMCrypto_SupportedCertificates())
|
||||
<< "Supported certificates is only " << OEMCrypto_SupportedCertificates();
|
||||
}
|
||||
|
||||
/// @}
|
||||
} // namespace wvoec
|
||||
@@ -8,11 +8,13 @@
|
||||
#define CDM_OEMCRYPTO_PROVISIONING_TEST_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "oemcrypto_basic_test.h"
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "oec_session_util.h"
|
||||
#include "oemcrypto_session_tests_helper.h"
|
||||
#include "oec_extra_test_keys.h"
|
||||
#include "oemcrypto_basic_test.h"
|
||||
#include "oemcrypto_license_test.h"
|
||||
#include "oemcrypto_resource_test.h"
|
||||
|
||||
namespace wvoec {
|
||||
|
||||
// Tests using this class are only used for devices with a keybox. They are not
|
||||
@@ -34,10 +36,95 @@ class OEMCryptoKeyboxTest : public OEMCryptoClientTest {
|
||||
};
|
||||
|
||||
// This class is for tests that have an OEM Certificate instead of a keybox.
|
||||
class OEMCryptoProv30Test : public OEMCryptoClientTest {};
|
||||
class OEMCryptoProv30Test : public OEMCryptoClientTest {
|
||||
void SetUp() override {
|
||||
OEMCryptoClientTest::SetUp();
|
||||
if (global_features.provisioning_method != OEMCrypto_OEMCertificate) {
|
||||
GTEST_SKIP() << "Test for Prov 3.0 devices only.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This class is for tests that have boot certificate chain instead of a keybox.
|
||||
class OEMCryptoProv40Test : public OEMCryptoClientTest {};
|
||||
class OEMCryptoProv40Test : public OEMCryptoClientTest {
|
||||
void SetUp() override {
|
||||
OEMCryptoClientTest::SetUp();
|
||||
if (global_features.provisioning_method != OEMCrypto_BootCertificateChain) {
|
||||
GTEST_SKIP() << "Test for Prov 4.0 devices only.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Certificate Root of Trust Tests
|
||||
//
|
||||
class OEMCryptoLoadsCertificate : public OEMCryptoSessionTestKeyboxTest {
|
||||
protected:
|
||||
void TestPrepareProvisioningRequestForHugeBufferLengths(
|
||||
const std::function<void(size_t, ProvisioningRoundTrip*)> f,
|
||||
bool check_status) {
|
||||
auto oemcrypto_function = [&](size_t message_length) {
|
||||
Session s;
|
||||
s.open();
|
||||
if (global_features.provisioning_method == OEMCrypto_OEMCertificate) {
|
||||
s.LoadOEMCert(true);
|
||||
} else {
|
||||
s.GenerateDerivedKeysFromKeybox(keybox_);
|
||||
}
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
f(message_length, &provisioning_messages);
|
||||
return provisioning_messages
|
||||
.SignAndCreateRequestWithCustomBufferLengths();
|
||||
};
|
||||
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, check_status);
|
||||
}
|
||||
|
||||
void TestLoadProvisioningForHugeBufferLengths(
|
||||
const std::function<void(size_t, ProvisioningRoundTrip*)> f,
|
||||
bool check_status, bool update_core_message_substring_values) {
|
||||
auto oemcrypto_function = [&](size_t message_length) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
provisioning_messages.SignAndVerifyRequest();
|
||||
provisioning_messages.CreateDefaultResponse();
|
||||
if (update_core_message_substring_values) {
|
||||
// Make provisioning message big enough so that updated core message
|
||||
// substring offset and length values from tests are still able to read
|
||||
// valid data from provisioning_message buffer rather than some garbage
|
||||
// data.
|
||||
provisioning_messages.set_message_size(
|
||||
sizeof(provisioning_messages.response_data()) + message_length);
|
||||
}
|
||||
f(message_length, &provisioning_messages);
|
||||
provisioning_messages
|
||||
.EncryptAndSignResponseWithoutUpdatingEncPrivateKeyLength();
|
||||
OEMCryptoResult result = provisioning_messages.LoadResponse();
|
||||
s.close();
|
||||
return result;
|
||||
};
|
||||
TestHugeLengthDoesNotCrashAPI(oemcrypto_function, check_status);
|
||||
}
|
||||
|
||||
void TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths(
|
||||
const std::function<void(size_t, ProvisioningRoundTrip*)> f) {
|
||||
Session s;
|
||||
ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_);
|
||||
provisioning_messages.PrepareSession(keybox_);
|
||||
provisioning_messages.SignAndVerifyRequest();
|
||||
provisioning_messages.CreateDefaultResponse();
|
||||
size_t message_length = sizeof(provisioning_messages.response_data());
|
||||
f(message_length, &provisioning_messages);
|
||||
provisioning_messages
|
||||
.EncryptAndSignResponseWithoutUpdatingEncPrivateKeyLength();
|
||||
OEMCryptoResult result = provisioning_messages.LoadResponse();
|
||||
s.close();
|
||||
// Verifying error is not due to signature failure which can be caused due
|
||||
// to test code.
|
||||
ASSERT_NE(OEMCrypto_ERROR_SIGNATURE_FAILURE, result);
|
||||
ASSERT_NE(OEMCrypto_SUCCESS, result);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wvoec
|
||||
|
||||
|
||||
@@ -772,6 +772,7 @@ TEST_F(OEMCryptoLoadsCertificateAlternates,
|
||||
ASSERT_TRUE(key_loaded_);
|
||||
}
|
||||
if (key_loaded_) {
|
||||
// If the key did load, then it should be processed correctly.
|
||||
Session s;
|
||||
ASSERT_NO_FATAL_FAILURE(s.open());
|
||||
ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_rsa_key_));
|
||||
@@ -804,6 +805,7 @@ TEST_F(OEMCryptoLoadsCertificateAlternates,
|
||||
ASSERT_TRUE(key_loaded_);
|
||||
}
|
||||
if (key_loaded_) {
|
||||
// If the key did load, then it should be processed correctly.
|
||||
Session s;
|
||||
ASSERT_NO_FATAL_FAILURE(s.open());
|
||||
ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_rsa_key_));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,13 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
wvutil::g_cutoff = static_cast<wvutil::LogPriority>(verbosity);
|
||||
wvoec::global_features.Initialize();
|
||||
wvoec::global_features.set_cast_receiver(is_cast_receiver);
|
||||
if (is_cast_receiver) {
|
||||
// Turn it on if passed in on the command line. Do not turn these tests off
|
||||
// automtically -- instead, we'll let the caller filter them out if they
|
||||
// need to. These tests will normally only run if the device claims to
|
||||
// support being a cast receiver.
|
||||
wvoec::global_features.set_cast_receiver(is_cast_receiver);
|
||||
}
|
||||
// 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
|
||||
|
||||
1707
libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp
Normal file
1707
libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
337
libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h
Normal file
337
libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h
Normal file
@@ -0,0 +1,337 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine
|
||||
// License Agreement.
|
||||
//
|
||||
// Test data for OEMCrypto unit tests.
|
||||
//
|
||||
#ifndef CDM_OEMCRYPTO_USAGE_TABLE_TEST_
|
||||
#define CDM_OEMCRYPTO_USAGE_TABLE_TEST_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "log.h"
|
||||
#include "oemcrypto_basic_test.h"
|
||||
#include "oemcrypto_license_test.h"
|
||||
#include "test_sleep.h"
|
||||
|
||||
namespace wvoec {
|
||||
|
||||
// This class is for testing the generic crypto functionality.
|
||||
class OEMCryptoGenericCryptoTest : public OEMCryptoRefreshTest {
|
||||
protected:
|
||||
// buffer_size_ must be a multiple of encryption block size, 16. We'll use a
|
||||
// reasonable number of blocks for most of the tests.
|
||||
OEMCryptoGenericCryptoTest() : buffer_size_(160) {}
|
||||
|
||||
void SetUp() override {
|
||||
OEMCryptoRefreshTest::SetUp();
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
license_messages_.CreateResponseWithGenericCryptoKeys());
|
||||
InitializeClearBuffer();
|
||||
}
|
||||
|
||||
void InitializeClearBuffer() {
|
||||
clear_buffer_.assign(buffer_size_, 0);
|
||||
for (size_t i = 0; i < clear_buffer_.size(); i++) {
|
||||
clear_buffer_[i] = 1 + i % 250;
|
||||
}
|
||||
for (size_t i = 0; i < wvoec::KEY_IV_SIZE; i++) {
|
||||
iv_[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
void ResizeBuffer(size_t new_size) {
|
||||
buffer_size_ = new_size;
|
||||
InitializeClearBuffer(); // Re-initialize the clear buffer.
|
||||
}
|
||||
|
||||
void EncryptAndLoadKeys() {
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
}
|
||||
|
||||
// Encrypt the buffer with the specified key made in
|
||||
// CreateResponseWithGenericCryptoKeys.
|
||||
void EncryptBuffer(unsigned int key_index, const vector<uint8_t>& in_buffer,
|
||||
vector<uint8_t>* out_buffer) {
|
||||
EncryptBufferWithKey(session_.license().keys[key_index].key_data, in_buffer,
|
||||
out_buffer);
|
||||
}
|
||||
// Encrypt the buffer with the specified key.
|
||||
void EncryptBufferWithKey(const uint8_t* key_data,
|
||||
const vector<uint8_t>& in_buffer,
|
||||
vector<uint8_t>* out_buffer) {
|
||||
AES_KEY aes_key;
|
||||
ASSERT_EQ(0, AES_set_encrypt_key(key_data, AES_BLOCK_SIZE * 8, &aes_key));
|
||||
uint8_t iv_buffer[wvoec::KEY_IV_SIZE];
|
||||
memcpy(iv_buffer, iv_, wvoec::KEY_IV_SIZE);
|
||||
out_buffer->resize(in_buffer.size());
|
||||
ASSERT_GT(in_buffer.size(), 0u);
|
||||
ASSERT_EQ(0u, in_buffer.size() % AES_BLOCK_SIZE);
|
||||
AES_cbc_encrypt(in_buffer.data(), out_buffer->data(), in_buffer.size(),
|
||||
&aes_key, iv_buffer, AES_ENCRYPT);
|
||||
}
|
||||
|
||||
// Sign the buffer with the specified key made in
|
||||
// CreateResponseWithGenericCryptoKeys.
|
||||
void SignBuffer(unsigned int key_index, const vector<uint8_t>& in_buffer,
|
||||
vector<uint8_t>* signature) {
|
||||
SignBufferWithKey(session_.license().keys[key_index].key_data, in_buffer,
|
||||
signature);
|
||||
}
|
||||
|
||||
// Sign the buffer with the specified key.
|
||||
void SignBufferWithKey(const uint8_t* key_data,
|
||||
const vector<uint8_t>& in_buffer,
|
||||
vector<uint8_t>* signature) {
|
||||
unsigned int md_len = SHA256_DIGEST_LENGTH;
|
||||
signature->resize(SHA256_DIGEST_LENGTH);
|
||||
HMAC(EVP_sha256(), key_data, wvoec::MAC_KEY_SIZE, in_buffer.data(),
|
||||
in_buffer.size(), signature->data(), &md_len);
|
||||
}
|
||||
|
||||
OEMCryptoResult GenericEncrypt(const uint8_t* key_handle,
|
||||
size_t key_handle_length,
|
||||
const uint8_t* clear_buffer,
|
||||
size_t clear_buffer_length, const uint8_t* iv,
|
||||
OEMCrypto_Algorithm algorithm,
|
||||
uint8_t* out_buffer) {
|
||||
if (ShouldGenerateCorpus()) {
|
||||
const std::string file_name =
|
||||
GetFileName("oemcrypto_generic_encrypt_fuzz_seed_corpus");
|
||||
OEMCrypto_Generic_Api_Fuzz fuzzed_structure;
|
||||
fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC;
|
||||
fuzzed_structure.algorithm = algorithm;
|
||||
// Cipher mode and algorithm.
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(&fuzzed_structure),
|
||||
sizeof(fuzzed_structure));
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(iv),
|
||||
wvoec::KEY_IV_SIZE);
|
||||
AppendSeparator(file_name);
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(clear_buffer),
|
||||
clear_buffer_length);
|
||||
}
|
||||
return OEMCrypto_Generic_Encrypt(key_handle, key_handle_length,
|
||||
clear_buffer, clear_buffer_length, iv,
|
||||
algorithm, out_buffer);
|
||||
}
|
||||
|
||||
OEMCryptoResult GenericDecrypt(
|
||||
const uint8_t* key_handle, size_t key_handle_length,
|
||||
const uint8_t* encrypted_buffer, size_t encrypted_buffer_length,
|
||||
const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer) {
|
||||
if (ShouldGenerateCorpus()) {
|
||||
const std::string file_name =
|
||||
GetFileName("oemcrypto_generic_decrypt_fuzz_seed_corpus");
|
||||
OEMCrypto_Generic_Api_Fuzz fuzzed_structure;
|
||||
fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC;
|
||||
fuzzed_structure.algorithm = algorithm;
|
||||
// Cipher mode and algorithm.
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(&fuzzed_structure),
|
||||
sizeof(fuzzed_structure));
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(iv),
|
||||
wvoec::KEY_IV_SIZE);
|
||||
AppendSeparator(file_name);
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(encrypted_buffer),
|
||||
encrypted_buffer_length);
|
||||
}
|
||||
return OEMCrypto_Generic_Decrypt(key_handle, key_handle_length,
|
||||
encrypted_buffer, encrypted_buffer_length,
|
||||
iv, algorithm, out_buffer);
|
||||
}
|
||||
|
||||
OEMCryptoResult GenericVerify(const uint8_t* key_handle,
|
||||
size_t key_handle_length,
|
||||
const uint8_t* clear_buffer,
|
||||
size_t clear_buffer_length,
|
||||
OEMCrypto_Algorithm algorithm,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length) {
|
||||
if (ShouldGenerateCorpus()) {
|
||||
const std::string file_name =
|
||||
GetFileName("oemcrypto_generic_verify_fuzz_seed_corpus");
|
||||
OEMCrypto_Generic_Api_Fuzz fuzzed_structure;
|
||||
fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC;
|
||||
fuzzed_structure.algorithm = algorithm;
|
||||
// Cipher mode and algorithm.
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(&fuzzed_structure),
|
||||
sizeof(fuzzed_structure));
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(clear_buffer),
|
||||
clear_buffer_length);
|
||||
AppendSeparator(file_name);
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(signature),
|
||||
signature_length);
|
||||
}
|
||||
return OEMCrypto_Generic_Verify(key_handle, key_handle_length, clear_buffer,
|
||||
clear_buffer_length, algorithm, signature,
|
||||
signature_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult GenericSign(const uint8_t* key_handle,
|
||||
size_t key_handle_length,
|
||||
const uint8_t* clear_buffer,
|
||||
size_t clear_buffer_length,
|
||||
OEMCrypto_Algorithm algorithm, uint8_t* signature,
|
||||
size_t* signature_length) {
|
||||
if (ShouldGenerateCorpus()) {
|
||||
const std::string file_name =
|
||||
GetFileName("oemcrypto_generic_sign_fuzz_seed_corpus");
|
||||
OEMCrypto_Generic_Api_Fuzz fuzzed_structure;
|
||||
fuzzed_structure.cipher_mode = OEMCrypto_CipherMode_CENC;
|
||||
fuzzed_structure.algorithm = algorithm;
|
||||
// Cipher mode and algorithm.
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(&fuzzed_structure),
|
||||
sizeof(fuzzed_structure));
|
||||
AppendToFile(file_name, reinterpret_cast<const char*>(clear_buffer),
|
||||
clear_buffer_length);
|
||||
}
|
||||
return OEMCrypto_Generic_Sign(key_handle, key_handle_length, clear_buffer,
|
||||
clear_buffer_length, algorithm, signature,
|
||||
signature_length);
|
||||
}
|
||||
|
||||
// This asks OEMCrypto to encrypt with the specified key, and expects a
|
||||
// failure.
|
||||
void BadEncrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm,
|
||||
size_t buffer_length) {
|
||||
OEMCryptoResult sts;
|
||||
vector<uint8_t> expected_encrypted;
|
||||
EncryptBuffer(key_index, clear_buffer_, &expected_encrypted);
|
||||
vector<uint8_t> key_handle;
|
||||
sts = GetKeyHandleIntoVector(
|
||||
session_.session_id(), session_.license().keys[key_index].key_id,
|
||||
session_.license().keys[key_index].key_id_length,
|
||||
OEMCrypto_CipherMode_CENC, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
vector<uint8_t> encrypted(buffer_length);
|
||||
sts = GenericEncrypt(key_handle.data(), key_handle.size(),
|
||||
clear_buffer_.data(), buffer_length, iv_, algorithm,
|
||||
encrypted.data());
|
||||
EXPECT_NE(OEMCrypto_SUCCESS, sts);
|
||||
expected_encrypted.resize(buffer_length);
|
||||
EXPECT_NE(encrypted, expected_encrypted);
|
||||
}
|
||||
|
||||
// This asks OEMCrypto to decrypt with the specified key, and expects a
|
||||
// failure.
|
||||
void BadDecrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm,
|
||||
size_t buffer_length) {
|
||||
OEMCryptoResult sts;
|
||||
vector<uint8_t> encrypted;
|
||||
EncryptBuffer(key_index, clear_buffer_, &encrypted);
|
||||
vector<uint8_t> key_handle;
|
||||
sts = GetKeyHandleIntoVector(
|
||||
session_.session_id(), session_.license().keys[key_index].key_id,
|
||||
session_.license().keys[key_index].key_id_length,
|
||||
OEMCrypto_CipherMode_CENC, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
vector<uint8_t> resultant(encrypted.size());
|
||||
sts = GenericDecrypt(key_handle.data(), key_handle.size(), encrypted.data(),
|
||||
buffer_length, iv_, algorithm, resultant.data());
|
||||
EXPECT_NE(OEMCrypto_SUCCESS, sts);
|
||||
EXPECT_NE(clear_buffer_, resultant);
|
||||
}
|
||||
|
||||
// This asks OEMCrypto to sign with the specified key, and expects a
|
||||
// failure.
|
||||
void BadSign(unsigned int key_index, OEMCrypto_Algorithm algorithm) {
|
||||
OEMCryptoResult sts;
|
||||
vector<uint8_t> expected_signature;
|
||||
SignBuffer(key_index, clear_buffer_, &expected_signature);
|
||||
|
||||
vector<uint8_t> key_handle;
|
||||
sts = GetKeyHandleIntoVector(
|
||||
session_.session_id(), session_.license().keys[key_index].key_id,
|
||||
session_.license().keys[key_index].key_id_length,
|
||||
OEMCrypto_CipherMode_CENC, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
size_t signature_length = (size_t)SHA256_DIGEST_LENGTH;
|
||||
vector<uint8_t> signature(SHA256_DIGEST_LENGTH);
|
||||
sts = GenericSign(key_handle.data(), key_handle.size(),
|
||||
clear_buffer_.data(), clear_buffer_.size(), algorithm,
|
||||
signature.data(), &signature_length);
|
||||
EXPECT_NE(OEMCrypto_SUCCESS, sts);
|
||||
EXPECT_NE(signature, expected_signature);
|
||||
}
|
||||
|
||||
// This asks OEMCrypto to verify a signature with the specified key, and
|
||||
// expects a failure.
|
||||
void BadVerify(unsigned int key_index, OEMCrypto_Algorithm algorithm,
|
||||
size_t signature_size, bool alter_data) {
|
||||
OEMCryptoResult sts;
|
||||
vector<uint8_t> signature;
|
||||
SignBuffer(key_index, clear_buffer_, &signature);
|
||||
if (alter_data) {
|
||||
signature[0] ^= 42;
|
||||
}
|
||||
if (signature.size() < signature_size) {
|
||||
signature.resize(signature_size);
|
||||
}
|
||||
|
||||
vector<uint8_t> key_handle;
|
||||
sts = GetKeyHandleIntoVector(
|
||||
session_.session_id(), session_.license().keys[key_index].key_id,
|
||||
session_.license().keys[key_index].key_id_length,
|
||||
OEMCrypto_CipherMode_CENC, key_handle);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
sts = GenericVerify(key_handle.data(), key_handle.size(),
|
||||
clear_buffer_.data(), clear_buffer_.size(), algorithm,
|
||||
signature.data(), signature_size);
|
||||
EXPECT_NE(OEMCrypto_SUCCESS, sts);
|
||||
}
|
||||
|
||||
// This must be a multiple of encryption block size.
|
||||
size_t buffer_size_;
|
||||
vector<uint8_t> clear_buffer_;
|
||||
vector<uint8_t> encrypted_buffer_;
|
||||
uint8_t iv_[wvoec::KEY_IV_SIZE];
|
||||
};
|
||||
|
||||
class OEMCryptoUsageTableTest : public OEMCryptoGenericCryptoTest {
|
||||
public:
|
||||
void SetUp() override { OEMCryptoGenericCryptoTest::SetUp(); }
|
||||
|
||||
virtual void ShutDown() {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.close());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate());
|
||||
}
|
||||
|
||||
virtual void Restart() {
|
||||
OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox));
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize());
|
||||
(void)OEMCrypto_SetMaxAPIVersion(kCurrentAPI);
|
||||
(void)OEMCrypto_EnterTestMode();
|
||||
EnsureTestROT();
|
||||
ASSERT_NO_FATAL_FAILURE(session_.open());
|
||||
}
|
||||
|
||||
void PrintDotsWhileSleep(int64_t total_seconds, int64_t interval_seconds) {
|
||||
int64_t dot_time = interval_seconds;
|
||||
int64_t elapsed_time = 0;
|
||||
const int64_t start_time = wvutil::Clock().GetCurrentTime();
|
||||
do {
|
||||
wvutil::TestSleep::Sleep(1);
|
||||
elapsed_time = wvutil::Clock().GetCurrentTime() - start_time;
|
||||
if (elapsed_time >= dot_time) {
|
||||
cout << ".";
|
||||
cout.flush();
|
||||
dot_time += interval_seconds;
|
||||
}
|
||||
} while (elapsed_time < total_seconds);
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
OEMCryptoResult LoadUsageTableHeader(
|
||||
const vector<uint8_t>& encrypted_usage_header) {
|
||||
return OEMCrypto_LoadUsageTableHeader(encrypted_usage_header.data(),
|
||||
encrypted_usage_header.size());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wvoec
|
||||
|
||||
#endif // CDM_OEMCRYPTO_USAGE_TABLE_TEST_
|
||||
Reference in New Issue
Block a user