Merge changes I3002f705,If33f0616,Ifb971bf0,If8fc484f,I65a879fb, ... into udc-dev am: 68e1eac8ec
Original change: https://googleplex-android-review.googlesource.com/c/platform/vendor/widevine/+/22305945 Change-Id: Idd423ad1ae8d952ee64c112bffca73d42b688545 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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