From 9e7877a95dbaf74742a226077af7c07378498121 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Mon, 27 Mar 2023 19:37:09 -0700 Subject: [PATCH 01/20] Document lacking signature of Prov 3.0 message Merge from Widevine repo of http://go/wvgerrit/169039 Bug: 243734378 Merged from https://widevine-internal-review.googlesource.com/166458 Change-Id: I3eae16d09cf42e554d450f746390744ef580ac03 --- .../oemcrypto/include/OEMCryptoCENC.h | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index f0f0144f..4835da1f 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -3862,11 +3862,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 +3877,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 +4157,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. * From 322355dbbf6c8c8d2f2e04e2652e38eb4766e74c Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Mon, 27 Mar 2023 19:40:19 -0700 Subject: [PATCH 02/20] Update documentation for Cast Merge from Widevine repo of http://go/wvgerrit/169044 Document changes needed for supporting cast and provisioning 4.0 at the same time. Bug: 259454830 Merged from https://widevine-internal-review.googlesource.com/166459 Change-Id: Iebf50d856c18f29db66352041b2b0429c43bd594 --- libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index 4835da1f..2ceb4315 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -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 From c579a794620207f6858bfc519aa482f422d5df1d Mon Sep 17 00:00:00 2001 From: Ian Benz Date: Mon, 27 Mar 2023 19:40:31 -0700 Subject: [PATCH 03/20] Fix null passed to memcpy in generic verify fuzz Merge from Widevine repo of http://go/wvgerrit/169048 Do not generate a new signature during mutation if a key handle cannot be retrieved by OEMCrypto_GetKeyHandle(). Bug: 275264353 Test: luci tests Change-Id: I9a804328c4b6d3e50d14c3f9c71043e71a88e3da --- .../oemcrypto_generic_verify_fuzz.cc | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_generic_verify_fuzz.cc b/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_generic_verify_fuzz.cc index b4d98104..23f598d2 100644 --- a/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_generic_verify_fuzz.cc +++ b/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_generic_verify_fuzz.cc @@ -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 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); } From dbd5bd2a4d4c45e7e33122151a0996dc846e9c89 Mon Sep 17 00:00:00 2001 From: Matt Feddersen Date: Mon, 27 Mar 2023 19:40:37 -0700 Subject: [PATCH 04/20] Update OPK v18 documentation Merge from Widevine repo of http://go/wvgerrit/169050 - Update changelog - Update copy parter files script to include linux port - Update opk_partner_test script (used to make sure everything works out of the box) with third party dependencies, refactored downloads into a public setup.sh script - Remove WTPI_BUILD_INFO from OPK makefiles and gyp files, since it is no longer needed - Remove FILES.md since it is out of date and ree-sources.mk and tee-sources.mk satisfy the same purpose - Add debug flag in comments for OP-TEE and Linux ports. As a hint for how to enable debug in OPK - Remove oemcrypto_build_info.h since it is no longer needed. Move the XSTR macro it contained to oemcrypto_api_macros.h - Add provisioning method macro to OPTEE and Linux build files to hint at how to build Prov 2 and Prov 4 using the same build files but different build-time values. Merged from https://widevine-internal-review.googlesource.com/166219 Bug: 275264353 Test: luci tests Change-Id: I220e3296f631d895a7c4504454635fe396efc0a4 --- libwvdrmengine/oemcrypto/CHANGELOG.md | 161 ++++++++++++++++++ .../oemcrypto/test/GEN_api_lock_file.c | 62 +++++++ 2 files changed, 223 insertions(+) diff --git a/libwvdrmengine/oemcrypto/CHANGELOG.md b/libwvdrmengine/oemcrypto/CHANGELOG.md index 11d38aa0..eef79c27 100644 --- a/libwvdrmengine/oemcrypto/CHANGELOG.md +++ b/libwvdrmengine/oemcrypto/CHANGELOG.md @@ -2,6 +2,167 @@ [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. + +### 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. + +### 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(). + ## [Version 17.1][v17.1] This release contains a major change to the build process for the OP-TEE port, diff --git a/libwvdrmengine/oemcrypto/test/GEN_api_lock_file.c b/libwvdrmengine/oemcrypto/test/GEN_api_lock_file.c index 37e577e6..2a29201b 100644 --- a/libwvdrmengine/oemcrypto/test/GEN_api_lock_file.c +++ b/libwvdrmengine/oemcrypto/test/GEN_api_lock_file.c @@ -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); From 6897bc1a1ce75dda56e933886bd4b8ceda3db26b Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Mon, 27 Mar 2023 19:40:43 -0700 Subject: [PATCH 05/20] Refactor decrypt unit tests Merge from Widevine repo of http://go/wvgerrit/169052 Refactor the decrypt unit tests into a separate file. Bug: 253779846 Merged from https://widevine-internal-review.googlesource.com/167180 Change-Id: I10a4a987b0d597f0c6d2953c0723bea4d790fb9c --- .../oemcrypto/test/oemcrypto_decrypt_test.cpp | 690 ++++++++++++++++++ .../oemcrypto/test/oemcrypto_decrypt_test.h | 430 +++++++++++ .../oemcrypto/test/oemcrypto_license_test.h | 124 ++++ .../oemcrypto/test/oemcrypto_test.cpp | 121 --- 4 files changed, 1244 insertions(+), 121 deletions(-) create mode 100644 libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp create mode 100644 libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp new file mode 100644 index 00000000..74945cdb --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp @@ -0,0 +1,690 @@ +// 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 "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::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 key_handle; + OEMCryptoResult sts = GetKeyHandleIntoVector( + session_.session_id(), reinterpret_cast(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 in_buffer(256); + vector 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 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 in_buffer(256); + vector 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 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 in_buffer(256); + vector 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 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 in_buffer(256); + vector 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 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 in_buffer(256); + vector 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(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(&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(&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 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 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 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 subsample_sizes; + subsample_sizes.push_back({100000, 100000}); + SetSubsampleSizes(subsample_sizes); + LoadLicense(); + MakeBuffers(); + EncryptData(); + // Build an array of just the sample descriptions. + std::vector 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(kCurrentAPI - 2, kCurrentAPI + 1)); + +} // namespace wvoec \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h new file mode 100644 index 00000000..4e49e2b9 --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h @@ -0,0 +1,430 @@ +// 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 + +#include "OEMCryptoCENC.h" +#include "log.h" +#include "oec_decrypt_fallback_chain.h" +#include "oec_session_util.h" +#include "oemcrypto_basic_test.h" +#include "oemcrypto_license_test.h" +#include "oemcrypto_resource_test.h" +#include "oemcrypto_session_tests_helper.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 { + 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 encrypted_buffer; + std::vector clear_buffer; // OEMCrypto store clear output here. + std::vector truth_buffer; // Truth data for clear text. + OEMCrypto_SampleDescription description; + std::vector 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> { + 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 subsample_sizes) { + // This is just sugar for having one sample with the given subsamples in it. + SetSampleSizes({subsample_sizes}); + } + + void SetSampleSizes(std::vector> 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& 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(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(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(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(&hash), + sizeof(hash)); + } + + // Build an array of just the sample descriptions. + std::vector 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 samples_; + std::vector key_handle_; +}; + +} // namespace wvoec + +#endif // CDM_OEMCRYPTO_DECRYPT_TEST_ \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h index 139ecad9..d07ff2b1 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h @@ -10,11 +10,14 @@ #include #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 +240,127 @@ 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(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_; +}; + } // namespace wvoec #endif // CDM_OEMCRYPTO_LICENSE_TEST_ \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index b851d97d..e23e9ae8 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -5505,127 +5505,6 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoGenericCryptoKeyIdLengthTest, /// @addtogroup usage_table /// @{ -// 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(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 OEMCryptoUsageTableTest : public OEMCryptoGenericCryptoTest { public: void SetUp() override { OEMCryptoGenericCryptoTest::SetUp(); } From 26aa378ca5f76de9f359e0a1d1c14f7f0f833848 Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Mon, 27 Mar 2023 19:41:09 -0700 Subject: [PATCH 06/20] Refactor usage table tests Merge from Widevine repo of http://go/wvgerrit/169061 Bug: 253779846 Merged from https://widevine-internal-review.googlesource.com/167477 Change-Id: I6046e59449700c8be05641f71dcbb2bba6ce493b --- libwvdrmengine/oemcrypto/test/common.mk | 1 + .../oemcrypto/test/oemcrypto_test.cpp | 2050 +---------------- .../test/oemcrypto_usage_table_test.cpp | 1707 ++++++++++++++ .../test/oemcrypto_usage_table_test.h | 379 +++ 4 files changed, 2088 insertions(+), 2049 deletions(-) create mode 100644 libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp create mode 100644 libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h diff --git a/libwvdrmengine/oemcrypto/test/common.mk b/libwvdrmengine/oemcrypto/test/common.mk index 309abca9..dba1ee3b 100644 --- a/libwvdrmengine/oemcrypto/test/common.mk +++ b/libwvdrmengine/oemcrypto/test/common.mk @@ -19,6 +19,7 @@ LOCAL_SRC_FILES:= \ oemcrypto_basic_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 \ diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index e23e9ae8..23c0040e 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -80,6 +80,7 @@ #include "oemcrypto_resource_test.h" #include "oemcrypto_session_tests_helper.h" #include "oemcrypto_types.h" +#include "oemcrypto_usage_table_test.h" #include "platform.h" #include "string_conversions.h" #include "test_sleep.h" @@ -1718,47 +1719,6 @@ INSTANTIATE_TEST_SUITE_P(TestHDCP, OEMCryptoSessionTestLoadCasKeysWithHDCP, // // Load, Refresh Keys Test // -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. @@ -4667,278 +4627,6 @@ TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_20) { /// @addtogroup generic /// @{ -// 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& in_buffer, - vector* 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& in_buffer, - vector* 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& in_buffer, - vector* 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& in_buffer, - vector* 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(&fuzzed_structure), - sizeof(fuzzed_structure)); - AppendToFile(file_name, reinterpret_cast(iv), - wvoec::KEY_IV_SIZE); - AppendSeparator(file_name); - AppendToFile(file_name, reinterpret_cast(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(&fuzzed_structure), - sizeof(fuzzed_structure)); - AppendToFile(file_name, reinterpret_cast(iv), - wvoec::KEY_IV_SIZE); - AppendSeparator(file_name); - AppendToFile(file_name, reinterpret_cast(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(&fuzzed_structure), - sizeof(fuzzed_structure)); - AppendToFile(file_name, reinterpret_cast(clear_buffer), - clear_buffer_length); - AppendSeparator(file_name); - AppendToFile(file_name, reinterpret_cast(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(&fuzzed_structure), - sizeof(fuzzed_structure)); - AppendToFile(file_name, reinterpret_cast(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 expected_encrypted; - EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); - vector 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 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 encrypted; - EncryptBuffer(key_index, clear_buffer_, &encrypted); - vector 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 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 expected_signature; - SignBuffer(key_index, clear_buffer_, &expected_signature); - - vector 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 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 signature; - SignBuffer(key_index, clear_buffer_, &signature); - if (alter_data) { - signature[0] ^= 42; - } - if (signature.size() < signature_size) { - signature.resize(signature_size); - } - - vector 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 clear_buffer_; - vector encrypted_buffer_; - uint8_t iv_[wvoec::KEY_IV_SIZE]; -}; - TEST_P(OEMCryptoGenericCryptoTest, GenericKeyLoad) { EncryptAndLoadKeys(); } // Test that the Generic_Encrypt function works correctly. @@ -5501,1740 +5189,4 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoGenericCryptoTest, INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoGenericCryptoKeyIdLengthTest, Range(kCoreMessagesAPI, kCurrentAPI + 1)); /// @} - -/// @addtogroup usage_table -/// @{ - -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& encrypted_usage_header) { - return OEMCrypto_LoadUsageTableHeader(encrypted_usage_header.data(), - encrypted_usage_header.size()); - } -}; - -// Test that successive calls to PrepAndSignProvisioningRequest only increase -// the provisioning count in the ODK message -TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) { - // local struct to hold count values from core message - typedef struct counts { - uint32_t prov; - uint32_t lic; - uint32_t decrypt; - uint64_t mgn; - } counts; - - // prep and sign provisioning2 request, then extract counter values - auto provision2 = [&](counts* c) { - Session s; - ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); - provisioning_messages.PrepareSession(keybox_); - provisioning_messages.SignAndVerifyRequest(); - c->prov = - provisioning_messages.core_request().counter_info.provisioning_count; - c->lic = provisioning_messages.core_request().counter_info.license_count; - c->decrypt = - provisioning_messages.core_request().counter_info.decrypt_count; - c->mgn = provisioning_messages.core_request() - .counter_info.master_generation_number; - }; - - // prep and sign provisioning4 request, then extract counter values - auto provision4 = [&](counts* c) { - // Same as SessionUtil::CreateProv4OEMKey, but we can't extract counter - // values using that function - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - Provisioning40RoundTrip provisioning_messages(&s); - ASSERT_NO_FATAL_FAILURE(provisioning_messages.PrepareSession(true)); - ASSERT_NO_FATAL_FAILURE(s.SetPublicKeyFromSubjectPublicKey( - provisioning_messages.oem_key_type(), - provisioning_messages.oem_public_key().data(), - provisioning_messages.oem_public_key().size())); - wrapped_oem_key_ = provisioning_messages.wrapped_oem_key(); - oem_public_key_ = provisioning_messages.oem_public_key(); - oem_key_type_ = provisioning_messages.oem_key_type(); - ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); - ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadOEMCertResponse()); - c->prov = - provisioning_messages.core_request().counter_info.provisioning_count; - c->lic = provisioning_messages.core_request().counter_info.license_count; - c->decrypt = - provisioning_messages.core_request().counter_info.decrypt_count; - c->mgn = provisioning_messages.core_request() - .counter_info.master_generation_number; - }; - - if (global_features.provisioning_method == OEMCrypto_OEMCertificate || - global_features.provisioning_method == OEMCrypto_DrmCertificate) { - GTEST_SKIP() << "Provisioning method does not increment prov counter"; - } else if (global_features.provisioning_method == OEMCrypto_Keybox) { - counts c1, c2; - provision2(&c1); - provision2(&c2); - - ASSERT_TRUE(c2.prov > c1.prov); - ASSERT_TRUE(c2.lic == c1.lic); - ASSERT_TRUE(c2.decrypt == c1.decrypt); - ASSERT_TRUE(c2.mgn == c1.mgn); - } else if (global_features.provisioning_method == - OEMCrypto_BootCertificateChain) { - counts c1, c2; - provision4(&c1); - provision4(&c2); - - ASSERT_TRUE(c2.prov > c1.prov); - ASSERT_TRUE(c2.lic == c1.lic); - ASSERT_TRUE(c2.decrypt == c1.decrypt); - ASSERT_TRUE(c2.mgn == c1.mgn); - } -} - -// Test that successive calls to PrepAndSignLicenseRequest only increase -// the license count in the ODK message -TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) { - Session s; - s.open(); - LicenseRoundTrip license_messages(&s); - InstallTestDrmKey(&s); - ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); - uint32_t prov_count1 = - license_messages.core_request().counter_info.provisioning_count; - uint32_t lic_count1 = - license_messages.core_request().counter_info.license_count; - uint32_t decrypt_count1 = - license_messages.core_request().counter_info.decrypt_count; - uint64_t master_generation_number1 = - license_messages.core_request().counter_info.master_generation_number; - - Session s2; - s2.open(); - LicenseRoundTrip license_messages2(&s2); - InstallTestDrmKey(&s2); - ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest()); - uint32_t prov_count2 = - license_messages2.core_request().counter_info.provisioning_count; - uint32_t lic_count2 = - license_messages2.core_request().counter_info.license_count; - uint32_t decrypt_count2 = - license_messages2.core_request().counter_info.decrypt_count; - uint64_t master_generation_number2 = - license_messages2.core_request().counter_info.master_generation_number; - - ASSERT_TRUE(prov_count2 == prov_count1); - ASSERT_TRUE(lic_count2 > lic_count1); - ASSERT_TRUE(decrypt_count2 == decrypt_count1); - ASSERT_TRUE(master_generation_number2 == master_generation_number1); -} - -// Test that the license request includes the master generation number, and that -// it is incremented correctly after usage table modification (save offline -// license) and decrypt. Also test that decrypt count increments. -TEST_F(OEMCryptoSessionTests, MasterGeneration_IncrementCounterAPI18) { - if (!OEMCrypto_SupportsUsageTable()) { - GTEST_SKIP() << "Usage table not supported, so master generation number " - "does not need to be checked."; - } - Session s1; - s1.open(); - LicenseRoundTrip license_messages(&s1); - InstallTestDrmKey(&s1); - ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); - uint32_t prov_count1 = - license_messages.core_request().counter_info.provisioning_count; - uint32_t lic_count1 = - license_messages.core_request().counter_info.license_count; - uint32_t decrypt_count1 = - license_messages.core_request().counter_info.decrypt_count; - uint64_t master_generation_number1 = - license_messages.core_request().counter_info.master_generation_number; - - // do the same as ReloadOfflineLicense to push the master generation number - // up - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(kCurrentAPI); - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); - - Session s2; - s2.open(); - LicenseRoundTrip license_messages2(&s2); - InstallTestDrmKey(&s2); - ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest()); - uint32_t prov_count2 = - license_messages2.core_request().counter_info.provisioning_count; - uint32_t lic_count2 = - license_messages2.core_request().counter_info.license_count; - uint32_t decrypt_count2 = - license_messages2.core_request().counter_info.decrypt_count; - uint64_t master_generation_number2 = - license_messages2.core_request().counter_info.master_generation_number; - - ASSERT_TRUE(prov_count2 == prov_count1); - ASSERT_TRUE(lic_count2 > lic_count1); - ASSERT_TRUE(decrypt_count2 > decrypt_count1); - ASSERT_TRUE(master_generation_number2 > master_generation_number1); -} -TEST_P(OEMCryptoUsageTableTest, - OEMCryptoMemoryLoadUsageEntryForHugeInvalidUsageEntryNumber) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - // Make first entry 0. - entry.MakeOfflineAndClose(this); - Session s; - s.open(); - InstallTestDrmKey(&s); - const uint32_t usage_entry_number = kHugeRandomNumber; - ASSERT_NO_FATAL_FAILURE(OEMCrypto_LoadUsageEntry( - s.session_id(), usage_entry_number, s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); -} - -// Test an online or streaming license with PST. This license requires a -// valid nonce and can only be loaded once. -TEST_P(OEMCryptoUsageTableTest, OnlineLicense) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - - // test repeated report generation - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); - // Flag the entry as inactive. - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // It should report as inactive. - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); - // Decrypt should fail. - ASSERT_NO_FATAL_FAILURE( - entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - // We could call DeactivateUsageEntry multiple times. The state should not - // change. - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // It should report as inactive. - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); -} - -// Test the usage report when the license is loaded but the keys are never -// used for decryption. -TEST_P(OEMCryptoUsageTableTest, OnlineLicenseUnused) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // No decrypt. We do not use this license. - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - // Flag the entry as inactive. - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // It should report as inactive. - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); - // Decrypt should fail. - ASSERT_NO_FATAL_FAILURE( - entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - // We could call DeactivateUsageEntry multiple times. The state should not - // change. - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // It should report as inactive. - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); -} - -// Test that the usage table has been updated and saved before a report can be -// generated. -TEST_P(OEMCryptoUsageTableTest, ForbidReportWithNoUpdate) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - // Cannot generate a report without first updating the file. - ASSERT_NO_FATAL_FAILURE( - s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // Now it's OK. - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); - // Flag the entry as inactive. - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - // Cannot generate a report without first updating the file. - ASSERT_NO_FATAL_FAILURE( - s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); - // Decrypt should fail. - ASSERT_NO_FATAL_FAILURE( - entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); -} - -// Test an online license with a license renewal. -TEST_P(OEMCryptoUsageTableTest, OnlineLicenseWithRefreshAPI16) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - - RenewalRoundTrip renewal_messages(&entry.license_messages()); - MakeRenewalRequest(&renewal_messages); - LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); -} - -// Verify that a streaming license cannot be reloaded. -TEST_P(OEMCryptoUsageTableTest, RepeatOnlineLicense) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.close()); - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s2)); - s2.LoadUsageEntry(s); // Use the same entry. - ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s2)); -} - -// An offline license should not load on the first call if the nonce is bad. -TEST_P(OEMCryptoUsageTableTest, OnlineBadNonce) { - Session s; - LicenseRoundTrip license_messages(&s); - license_messages.set_api_version(license_api_version_); - license_messages.set_control(wvoec::kControlNonceEnabled | - wvoec::kControlNonceRequired); - license_messages.set_pst("my-pst"); - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); - for (uint32_t i = 0; i < license_messages.num_keys(); i++) - license_messages.response_data().keys[i].control.nonce ^= 42; - ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse()); -} - -// A license with non-zero replay control bits needs a valid pst. -TEST_P(OEMCryptoUsageTableTest, OnlineEmptyPST) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); - LicenseRoundTrip license_messages(&s); - license_messages.set_api_version(license_api_version_); - license_messages.set_control(wvoec::kControlNonceEnabled | - wvoec::kControlNonceRequired); - // DO NOT SET PST: license_messages.set_pst(pst); - ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); - ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); -} - -// A license with non-zero replay control bits needs a valid pst. -TEST_P(OEMCryptoUsageTableTest, OnlineMissingEntry) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); - LicenseRoundTrip license_messages(&s); - license_messages.set_api_version(license_api_version_); - license_messages.set_control(wvoec::kControlNonceEnabled | - wvoec::kControlNonceRequired); - license_messages.set_pst("my-pst"); - ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); - ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); - // ENTRY NOT CREATED: ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); -} - -// Sessions should have at most one entry at a time. This tests different -// orderings of CreateNewUsageEntry and LoadUsageEntry calls. -TEST_P(OEMCryptoUsageTableTest, CreateAndLoadMultipleEntriesAPI16) { - // Entry Count: we start each test with an empty header. - uint32_t usage_entry_number; - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - Session& s = entry.session(); - // Make first entry 0. - ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); - - // Load an entry, then try to create a second. - ASSERT_NO_FATAL_FAILURE(s.open()); - // Reload entry 0. - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - // Create new entry 1 should fail. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_CreateNewUsageEntry(entry.session().session_id(), - &usage_entry_number)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // Create an entry, then try to load a second. - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - // Create entry 1. - ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry()); - // Try to reload entry 0. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageEntry(s2.session_id(), s.usage_entry_number(), - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - // Reload an entry and a license, then try to load the same entry again. - // This reloads entry 0. - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // Create an entry, then try to create a second entry. - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry()); - ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_CreateNewUsageEntry( - s2.session_id(), &usage_entry_number)); -} - -// An entry can be loaded in only one session at a time. -TEST_P(OEMCryptoUsageTableTest, LoadEntryInMultipleSessions) { - // Entry Count: we start each test with an empty header. - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - Session& s = entry.session(); - // Make first entry 0. - ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); - const uint32_t usage_entry_number = s.usage_entry_number(); - EXPECT_EQ(usage_entry_number, 0u); // Should be only entry in this test. - - // Load an entry, then try to create a second. - ASSERT_NO_FATAL_FAILURE(s.open()); - // Reload entry 0. - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - - // Create an entry, then try to load a second. - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - // Try to load entry 0 into session 2. - ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, - OEMCrypto_LoadUsageEntry(s2.session_id(), usage_entry_number, - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); -} - -// Test generic encrypt when the license uses a PST. -TEST_P(OEMCryptoUsageTableTest, GenericCryptoEncrypt) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.set_generic_crypto(true); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - OEMCryptoResult sts; - unsigned int key_index = 0; - vector expected_encrypted; - EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, - &expected_encrypted); - vector key_handle; - sts = - GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id, - s.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CENC, key_handle); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - vector encrypted(clear_buffer_.size()); - sts = OEMCrypto_Generic_Encrypt(key_handle.data(), key_handle.size(), - clear_buffer_.data(), clear_buffer_.size(), - iv_, OEMCrypto_AES_CBC_128_NO_PADDING, - encrypted.data()); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - EXPECT_EQ(expected_encrypted, encrypted); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); - encrypted.assign(clear_buffer_.size(), 0); - sts = OEMCrypto_Generic_Encrypt(key_handle.data(), key_handle.size(), - clear_buffer_.data(), clear_buffer_.size(), - iv_, OEMCrypto_AES_CBC_128_NO_PADDING, - encrypted.data()); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - EXPECT_NE(encrypted, expected_encrypted); -} - -// Test generic decrypt when the license uses a PST. -TEST_P(OEMCryptoUsageTableTest, GenericCryptoDecrypt) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.set_generic_crypto(true); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - OEMCryptoResult sts; - unsigned int key_index = 1; - vector encrypted; - EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, - &encrypted); - vector key_handle; - sts = - GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id, - s.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CENC, key_handle); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - vector resultant(encrypted.size()); - sts = OEMCrypto_Generic_Decrypt( - key_handle.data(), key_handle.size(), encrypted.data(), encrypted.size(), - iv_, OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - EXPECT_EQ(clear_buffer_, resultant); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); - resultant.assign(encrypted.size(), 0); - sts = OEMCrypto_Generic_Decrypt( - key_handle.data(), key_handle.size(), encrypted.data(), encrypted.size(), - iv_, OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - EXPECT_NE(clear_buffer_, resultant); -} - -// Test generic sign when the license uses a PST. -TEST_P(OEMCryptoUsageTableTest, GenericCryptoSign) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.set_generic_crypto(true); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - OEMCryptoResult sts; - unsigned int key_index = 2; - vector expected_signature; - SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, - &expected_signature); - - vector key_handle; - sts = - GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id, - s.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CENC, key_handle); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - size_t gen_signature_length = 0; - sts = OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(), - clear_buffer_.data(), clear_buffer_.size(), - OEMCrypto_HMAC_SHA256, nullptr, - &gen_signature_length); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); - vector signature(SHA256_DIGEST_LENGTH); - sts = OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(), - clear_buffer_.data(), clear_buffer_.size(), - OEMCrypto_HMAC_SHA256, signature.data(), - &gen_signature_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(expected_signature, signature); - - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); - signature.assign(SHA256_DIGEST_LENGTH, 0); - gen_signature_length = SHA256_DIGEST_LENGTH; - sts = OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(), - clear_buffer_.data(), clear_buffer_.size(), - OEMCrypto_HMAC_SHA256, signature.data(), - &gen_signature_length); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NE(signature, expected_signature); -} - -// Test generic verify when the license uses a PST. -TEST_P(OEMCryptoUsageTableTest, GenericCryptoVerify) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.set_generic_crypto(true); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - OEMCryptoResult sts; - unsigned int key_index = 3; - vector signature; - SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, - &signature); - - vector key_handle; - sts = - GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id, - s.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CENC, key_handle); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - sts = OEMCrypto_Generic_Verify(key_handle.data(), key_handle.size(), - clear_buffer_.data(), clear_buffer_.size(), - OEMCrypto_HMAC_SHA256, signature.data(), - signature.size()); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); - sts = OEMCrypto_Generic_Verify(key_handle.data(), key_handle.size(), - clear_buffer_.data(), clear_buffer_.size(), - OEMCrypto_HMAC_SHA256, signature.data(), - signature.size()); - ASSERT_NE(OEMCrypto_SUCCESS, sts); -} - -// Test that an offline license can be loaded. -TEST_P(OEMCryptoUsageTableTest, OfflineLicense) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); -} - -// Test that an offline license can be loaded and that the license can be -// renewed. -TEST_P(OEMCryptoUsageTableTest, OfflineLicenseRefresh) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeAndLoad(this, wvoec::kControlNonceOrEntry); - Session& s = entry.session(); - - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - // License renewal message is signed by client and verified by the server. - RenewalRoundTrip renewal_messages(&entry.license_messages()); - MakeRenewalRequest(&renewal_messages); - LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); -} - -// Test that an offline license can be reloaded in a new session. -TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicense) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); -} - -// Test that an offline license can be reloaded in a new session, and then -// refreshed. -TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicenseWithRefresh) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - RenewalRoundTrip renewal_messages(&entry.license_messages()); - MakeRenewalRequest(&renewal_messages); - LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); -} - -// Verify that we can still reload an offline license after -// OEMCrypto_Terminate and Initialize are called. This is as close to a reboot -// as we can do in a unit test. -TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicenseWithTerminate) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - ShutDown(); // This calls OEMCrypto_Terminate. - Restart(); // This calls OEMCrypto_Initialize. - ASSERT_EQ(OEMCrypto_SUCCESS, LoadUsageTableHeader(encrypted_usage_header_)); - - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); -} - -// If we attempt to load a second license with the same usage entry as the -// first, but it has different mac keys, then the attempt should fail. This -// is how we verify that we are reloading the same license. -TEST_P(OEMCryptoUsageTableTest, BadReloadOfflineLicense) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - - // Offline license with new mac keys should fail. - Session s2; - LicenseRoundTrip license_messages2(&s2); - // Copy the response, and then change the mac keys. - license_messages2.response_data() = entry.license_messages().response_data(); - license_messages2.core_response() = entry.license_messages().core_response(); - license_messages2.response_data().mac_keys[7] ^= 42; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s2)); - // This is a valid license: it is correctly signed. - license_messages2.EncryptAndSignResponse(); - // Load the usage entry should be OK. - ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); - ASSERT_NE(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - // Now we go back to the original license response. It should load OK. - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); -} - -// An offline license should not load on the first call if the nonce is bad. -TEST_P(OEMCryptoUsageTableTest, OfflineBadNonce) { - Session s; - LicenseRoundTrip license_messages(&s); - license_messages.set_api_version(license_api_version_); - license_messages.set_control(wvoec::kControlNonceOrEntry); - license_messages.set_pst("my-pst"); - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); - for (size_t i = 0; i < license_messages.num_keys(); i++) - license_messages.response_data().keys[i].control.nonce ^= 42; - ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse()); -} - -// An offline license needs a valid pst. -TEST_P(OEMCryptoUsageTableTest, OfflineEmptyPST) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); - LicenseRoundTrip license_messages(&s); - license_messages.set_api_version(license_api_version_); - license_messages.set_control(wvoec::kControlNonceOrEntry); - // DO NOT SET PST: license_messages.set_pst(pst); - ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); - ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); -} - -// If we try to reload a license with a different PST, the attempt should -// fail. -TEST_P(OEMCryptoUsageTableTest, ReloadOfflineWrongPST) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - - Session s2; - LicenseRoundTrip license_messages2(&s2); - license_messages2.response_data() = entry.license_messages().response_data(); - license_messages2.core_response() = entry.license_messages().core_response(); - // Change the middle of the pst. - license_messages2.response_data().pst[3] ^= 'Z'; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s2)); - // This is a valid license: it is correctly signed. - license_messages2.EncryptAndSignResponse(); - // Load the usage entry should be OK. - ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); - ASSERT_NE(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); -} - -// Once a license has been deactivated, the keys can no longer be used for -// decryption. However, we can still generate a usage report. -TEST_P(OEMCryptoUsageTableTest, DeactivateOfflineLicense) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - // Reload the offline license. - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE( - entry.TestDecryptCTR()); // Should be able to decrypt. - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate. - // After deactivate, should not be able to decrypt. - ASSERT_NO_FATAL_FAILURE( - entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // Offline license can not be reused if it has been deactivated. - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); - ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s)); - s.close(); - - // But we can still generate a report. - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // Sending a release from an offline license that has been deactivate will - // only work if the license server can handle v16 licenses. This is a rare - // condition, so it is OK to break it during the transition months. - entry.license_messages().set_api_version(global_features.api_version); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); - // We could call DeactivateUsageEntry multiple times. The state should not - // change. - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); -} - -// The usage report should indicate that the keys were never used for -// decryption. -TEST_P(OEMCryptoUsageTableTest, DeactivateOfflineLicenseUnused) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - // No Decrypt. This license is unused. - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate. - // After deactivate, should not be able to decrypt. - ASSERT_NO_FATAL_FAILURE( - entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // Offline license can not be reused if it has been deactivated. - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); - ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s)); - s.close(); - - // But we can still generate a report. - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // Sending a release from an offline license that has been deactivate will - // only work if the license server can handle v16 licenses. This is a rare - // condition, so it is OK to break it during the transition months. - entry.license_messages().set_api_version(global_features.api_version); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); - // We could call DeactivateUsageEntry multiple times. The state should not - // change. - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); -} - -TEST_P(OEMCryptoUsageTableTest, SecureStop) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.close()); - // When we generate a secure stop without loading the license first, it - // should assume the server does not support core messages. - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // It should report as inactive. - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); -} - -// Test update usage table fails when passed a null pointer. -TEST_P(OEMCryptoUsageTableTest, UpdateFailsWithNullPtr) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - size_t header_buffer_length = encrypted_usage_header_.size(); - size_t entry_buffer_length = s.encrypted_usage_entry().size(); - vector buffer(entry_buffer_length); - // Now try to pass in null pointers for the buffers. This should fail. - ASSERT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_UpdateUsageEntry(s.session_id(), nullptr, &header_buffer_length, - buffer.data(), &entry_buffer_length)); - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_UpdateUsageEntry( - s.session_id(), encrypted_usage_header_.data(), - &header_buffer_length, nullptr, &entry_buffer_length)); -} - -// Class used to test usage table defragmentation. -class OEMCryptoUsageTableDefragTest : public OEMCryptoUsageTableTest { - protected: - void ReloadLicense(LicenseWithUsageEntry* entry) { - Session& s = entry->session(); - ASSERT_NO_FATAL_FAILURE(entry->OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry->TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry->GenerateVerifyReport(kActive)); - ASSERT_NO_FATAL_FAILURE(s.close()); - } - - void FailReloadLicense(LicenseWithUsageEntry* entry, - OEMCryptoResult expected_result) { - Session& s = entry->session(); - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); - ASSERT_EQ(expected_result, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); - - ASSERT_NE(OEMCrypto_SUCCESS, entry->license_messages().LoadResponse()); - ASSERT_NO_FATAL_FAILURE(s.close()); - } - - void ShrinkHeader(uint32_t new_size, - OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { - // We call OEMCrypto_ShrinkUsageTableHeader once with a zero length - // buffer, so that OEMCrypto can tell us how big the buffer should be. - size_t header_buffer_length = 0; - OEMCryptoResult sts = OEMCrypto_ShrinkUsageTableHeader( - new_size, nullptr, &header_buffer_length); - // If we are expecting success, then the first call shall return - // SHORT_BUFFER. However, if we are not expecting success, this first call - // may return either SHORT_BUFFER or the expect error. - if (expected_result == OEMCrypto_SUCCESS) { - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - } else if (sts != OEMCrypto_ERROR_SHORT_BUFFER) { - // If we got any thing from the first call, it should be the expected - // error, and we don't need to call a second time. - ASSERT_EQ(expected_result, sts); - return; - } - // If the first call resulted in SHORT_BUFFER, we should resize the buffer - // and try again. - ASSERT_LT(0u, header_buffer_length); - encrypted_usage_header_.resize(header_buffer_length); - sts = OEMCrypto_ShrinkUsageTableHeader( - new_size, encrypted_usage_header_.data(), &header_buffer_length); - // For the second call, we always demand the expected result. - ASSERT_EQ(expected_result, sts); - if (sts == OEMCrypto_SUCCESS) { - encrypted_usage_header_.resize(header_buffer_length); - } - } -}; - -// Verify that usage table entries can be moved around in the table. -TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntries) { - const size_t ENTRY_COUNT = 10; - vector entries(ENTRY_COUNT); - for (size_t i = 0; i < ENTRY_COUNT; i++) { - entries[i].set_pst("pst " + std::to_string(i)); - ASSERT_NO_FATAL_FAILURE(entries[i].MakeOfflineAndClose(this)) - << "On license " << i << " pst=" << entries[i].pst(); - wvutil::TestSleep::SyncFakeClock(); - } - for (size_t i = 0; i < ENTRY_COUNT; i++) { - ASSERT_NO_FATAL_FAILURE(entries[i].OpenAndReload(this)) - << "On license " << i << " pst=" << entries[i].pst(); - ASSERT_NO_FATAL_FAILURE(entries[i].session().close()) - << "On license " << i << " pst=" << entries[i].pst(); - } - // Move 4 to 1. - ASSERT_NO_FATAL_FAILURE( - entries[4].session().MoveUsageEntry(1, &encrypted_usage_header_)); - // Shrink header to 3 entries 0, 1 was 4, 2. - ASSERT_NO_FATAL_FAILURE(ShrinkHeader(3)); - ShutDown(); - Restart(); - ASSERT_EQ(OEMCrypto_SUCCESS, LoadUsageTableHeader(encrypted_usage_header_)); - wvutil::TestSleep::SyncFakeClock(); - ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[0])); - // Now has index 1. - ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[4])); - ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[2])); - // When 4 was moved to 1, it increased the gen. number in the header. - ASSERT_NO_FATAL_FAILURE( - FailReloadLicense(&entries[1], OEMCrypto_ERROR_GENERATION_SKEW)); - // Index 3 is beyond the end of the table. - ASSERT_NO_FATAL_FAILURE( - FailReloadLicense(&entries[3], OEMCrypto_ERROR_UNKNOWN_FAILURE)); -} - -// A usage table entry cannot be moved into an entry where an open session is -// currently using the entry. -TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntriesToOpenSession) { - LicenseWithUsageEntry entry0; - entry0.set_pst("pst 0"); - entry0.MakeOfflineAndClose(this); - LicenseWithUsageEntry entry1; - entry1.set_pst("pst 1"); - entry1.MakeOfflineAndClose(this); - - entry0.session().open(); - ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); - // s0 currently open on index 0. Expect this to fail: - ASSERT_NO_FATAL_FAILURE(entry1.session().MoveUsageEntry( - 0, &encrypted_usage_header_, OEMCrypto_ERROR_ENTRY_IN_USE)); -} - -TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntriesToInvalidHugeEntryIndex) { - LicenseWithUsageEntry entry0; - entry0.set_pst("pst 0"); - entry0.MakeOfflineAndClose(this); - entry0.session().open(); - entry0.ReloadUsageEntry(); - ASSERT_NO_FATAL_FAILURE( - OEMCrypto_MoveEntry(entry0.session().session_id(), kHugeRandomNumber)); -} - -// The usage table cannot be shrunk if any session is using an entry that -// would be deleted. -TEST_P(OEMCryptoUsageTableDefragTest, ShrinkOverOpenSessions) { - LicenseWithUsageEntry entry0; - entry0.set_pst("pst 0"); - entry0.MakeOfflineAndClose(this); - LicenseWithUsageEntry entry1; - entry1.set_pst("pst 1"); - entry1.MakeOfflineAndClose(this); - - entry0.session().open(); - ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); - entry1.session().open(); - ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry()); - // Since s0 and s1 are open, we can't shrink. - ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_ERROR_ENTRY_IN_USE)); - entry1.session().close(); // Can shrink after closing s1, even if s0 is open. - ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_SUCCESS)); -} - -// Verify the usage table size can be increased. -TEST_P(OEMCryptoUsageTableDefragTest, EnlargeHeader) { - LicenseWithUsageEntry entry0; - entry0.set_pst("pst 0"); - entry0.MakeOfflineAndClose(this); - LicenseWithUsageEntry entry1; - entry1.set_pst("pst 1"); - entry1.MakeOfflineAndClose(this); - - // Can only shrink the header -- not make it bigger. - ASSERT_NO_FATAL_FAILURE(ShrinkHeader(4, OEMCrypto_ERROR_UNKNOWN_FAILURE)); -} - -// A new header can only be created while no entries are in use. -TEST_P(OEMCryptoUsageTableDefragTest, CreateNewHeaderWhileUsingOldOne) { - LicenseWithUsageEntry entry0; - entry0.set_pst("pst 0"); - entry0.MakeOfflineAndClose(this); - LicenseWithUsageEntry entry1; - entry1.set_pst("pst 1"); - entry1.MakeOfflineAndClose(this); - - entry0.session().open(); - ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); - const bool kExpectFailure = false; - ASSERT_NO_FATAL_FAILURE(CreateUsageTableHeader(kExpectFailure)); -} - -// Verify that a usage table entry can only be loaded into the correct index -// of the table. -TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryWrongIndex) { - LicenseWithUsageEntry entry0; - entry0.set_pst("pst 0"); - entry0.MakeOfflineAndClose(this); - LicenseWithUsageEntry entry1; - entry1.set_pst("pst 1"); - entry1.MakeOfflineAndClose(this); - - entry0.session().set_usage_entry_number(1); - ASSERT_NO_FATAL_FAILURE( - FailReloadLicense(&entry0, OEMCrypto_ERROR_INVALID_SESSION)); -} - -// Verify that a usage table entry cannot be loaded if it has been altered. -TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryBadData) { - LicenseWithUsageEntry entry; - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); - vector data = s.encrypted_usage_entry(); - ASSERT_LT(0UL, data.size()); - data[0] ^= 42; - // Error could be signature or verification error. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - data.data(), data.size())); -} - -// This verifies we can actually create the required number of usage table -// entries. -TEST_P(OEMCryptoUsageTableDefragTest, ManyUsageEntries) { - // OEMCrypto is required to store at least 300 entries in the usage table - // header, but it is allowed to store more. This test verifies that if we - // keep adding entries, the error indicates a resource limit. It then - // verifies that all of the successful entries are still valid after we - // throw out the last invalid entry. - - // After API 16, we require 300 entries in the usage table. Before API 16, - // we required 200. - const size_t required_capacity = RequiredUsageSize(); - - // We try to make a much large header, and assume there is an error at some - // point. - const size_t attempt_count = required_capacity * 5; - // Count of how many entries we successfully create. - size_t successful_count = 0; - - // These entries have licenses tied to them. - std::vector> entries; - // Store the status of the last attempt to create an entry. - OEMCryptoResult status = OEMCrypto_SUCCESS; - while (successful_count < attempt_count && status == OEMCrypto_SUCCESS) { - wvutil::TestSleep::SyncFakeClock(); - LOGD("Creating license for entry %zu", successful_count); - entries.push_back( - std::unique_ptr(new LicenseWithUsageEntry())); - entries.back()->set_pst("pst " + std::to_string(successful_count)); - ASSERT_NO_FATAL_FAILURE(entries.back()->MakeOfflineAndClose(this, &status)) - << "Failed creating license for entry " << successful_count; - if (status != OEMCrypto_SUCCESS) { - // Remove the failed session. - entries.resize(entries.size() - 1); - break; - } - EXPECT_EQ(entries.back()->session().usage_entry_number(), successful_count); - successful_count++; - // We don't create a license for each entry. For every license, we'll - // create 10 empty entries. - constexpr size_t filler_count = 10; - for (size_t i = 0; i < filler_count; i++) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry(&status)) - << "Failed creating entry " << successful_count; - if (status != OEMCrypto_SUCCESS) break; - EXPECT_EQ(s.usage_entry_number(), successful_count); - successful_count++; - } - } - LOGD("successful_count = %zu", successful_count); - if (status != OEMCrypto_SUCCESS) { - // If we failed to create this many entries because of limited resources, - // then the error returned should be insufficient resources. - EXPECT_EQ(OEMCrypto_ERROR_INSUFFICIENT_RESOURCES, status) - << "Failed to create license " << successful_count - << ", with wrong error code."; - } - EXPECT_GE(successful_count, required_capacity); - wvutil::TestSleep::SyncFakeClock(); - // Shrink the table a little. - constexpr size_t small_number = 5; - size_t smaller_size = successful_count - small_number; - ASSERT_NO_FATAL_FAILURE(ShrinkHeader(static_cast(smaller_size))); - // Throw out the last license if it was in the part of the table that was - // shrunk. - if (entries.back()->session().usage_entry_number() >= smaller_size) { - entries.pop_back(); - } - // Create a few more license - for (size_t i = 0; i < small_number; i++) { - wvutil::TestSleep::SyncFakeClock(); - entries.push_back( - std::unique_ptr(new LicenseWithUsageEntry())); - entries.back()->set_pst("new pst " + std::to_string(smaller_size + i)); - entries.back()->MakeOfflineAndClose(this); - } - // Make sure that all of the licenses can be reloaded. - for (size_t i = 0; i < entries.size(); i++) { - wvutil::TestSleep::SyncFakeClock(); - Session& s = entries[i]->session(); - ASSERT_NO_FATAL_FAILURE(entries[i]->OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entries[i]->GenerateVerifyReport(kUnused)); - ASSERT_NO_FATAL_FAILURE(entries[i]->TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entries[i]->GenerateVerifyReport(kActive)); - ASSERT_NO_FATAL_FAILURE(s.close()); - } -} - -// Verify that usage entries can be created in the position of existing entry -// indexes. -TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryAPI17) { - LicenseWithUsageEntry entry0; - entry0.set_pst("pst 0"); - LicenseWithUsageEntry entry1; - entry1.set_pst("pst 1"); - - entry0.session().open(); - ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry()); - const uint32_t number = entry0.session().usage_entry_number(); - entry0.session().close(); - entry1.session().open(); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number)); -} - -// Verify that usage entries cannot replace an entry that is currently in -// use by a session. -TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryIndexInUseAPI17) { - LicenseWithUsageEntry entry0; - entry0.set_pst("pst 0"); - LicenseWithUsageEntry entry1; - entry1.set_pst("pst 1"); - - entry0.session().open(); - ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry()); - const uint32_t number = entry0.session().usage_entry_number(); - entry1.session().open(); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, - OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number)); -} - -// Verify that usage entries cannot be created if the usage entry index is -// too large. -TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryWithInvalidIndexAPI17) { - LicenseWithUsageEntry entry0; - entry0.set_pst("pst 0"); - LicenseWithUsageEntry entry1; - entry1.set_pst("pst 1"); - - entry0.session().open(); - ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry()); - const uint32_t number = entry0.session().usage_entry_number(); - entry0.session().close(); - entry1.session().open(); - ASSERT_EQ( - OEMCrypto_ERROR_UNKNOWN_FAILURE, - OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number + 42)); -} - -// Verify that usage entries cannot be created if the session already has an -// entry. -TEST_P(OEMCryptoUsageTableDefragTest, - ReuseUsageEntrySessionAlreadyHasEntryAPI17) { - LicenseWithUsageEntry entry; - entry.set_pst("pst 0"); - - // Create 5 entries in the table. - for (int i = 0; i < 5; i++) { - entry.session().open(); - ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); - entry.session().close(); - } - entry.session().open(); - ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); - const uint32_t number = entry.session().usage_entry_number(); - ASSERT_EQ( - OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, - OEMCrypto_ReuseUsageEntry(entry.session().session_id(), number - 3)); -} - -// This verifies that the usage table header can be loaded if the generation -// number is off by one, but not off by two. -TEST_P(OEMCryptoUsageTableTest, ReloadUsageTableWithSkew) { - // This also tests a few other error conditions with usage table headers. - LicenseWithUsageEntry entry; - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - - // Reload the license, and save the header. - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - vector old_usage_header_2_ = encrypted_usage_header_; - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - vector old_usage_header_1_ = encrypted_usage_header_; - vector old_usage_entry_1 = s.encrypted_usage_entry(); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - ShutDown(); - Restart(); - // Null pointer generates error. - ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_LoadUsageTableHeader( - nullptr, old_usage_header_2_.size())); - ASSERT_NO_FATAL_FAILURE(s.open()); - // Cannot load an entry if header didn't load. - ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // Modified header generates error. - vector bad_header = encrypted_usage_header_; - bad_header[3] ^= 42; - ASSERT_NE(OEMCrypto_SUCCESS, LoadUsageTableHeader(bad_header)); - ASSERT_NO_FATAL_FAILURE(s.open()); - // Cannot load an entry if header didn't load. - ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // Old by 2 generation numbers is error. - ASSERT_EQ(OEMCrypto_ERROR_GENERATION_SKEW, - LoadUsageTableHeader(old_usage_header_2_)); - ASSERT_NO_FATAL_FAILURE(s.open()); - // Cannot load an entry if header didn't load. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // Old by 1 generation numbers is just warning. - ASSERT_EQ(OEMCrypto_WARNING_GENERATION_SKEW, - LoadUsageTableHeader(old_usage_header_1_)); - // Everything else should still work. The old entry goes with the old - // header. - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - old_usage_entry_1.data(), - old_usage_entry_1.size())); - ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromSessionKey()); - ASSERT_EQ(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse()); -} - -TEST_P(OEMCryptoUsageTableTest, LoadAndReloadEntries) { - constexpr size_t kEntryCount = 10; - std::vector entries(kEntryCount); - - for (LicenseWithUsageEntry& entry : entries) { - entry.license_messages().set_api_version(license_api_version_); - } - - for (size_t i = 0; i < kEntryCount; ++i) { - const std::string create_description = - "Creating entry #" + std::to_string(i); - // Create and update a new entry. - LicenseWithUsageEntry& new_entry = entries[i]; - ASSERT_NO_FATAL_FAILURE(new_entry.MakeOfflineAndClose(this)) - << create_description; - // Reload all entries, starting with the most recently created. - for (size_t j = 0; j <= i; ++j) { - const std::string reload_description = - "Reloading entry #" + std::to_string(i - j) + - ", after creating entry #" + std::to_string(i); - LicenseWithUsageEntry& old_entry = entries[i - j]; - ASSERT_NO_FATAL_FAILURE(old_entry.session().open()); - ASSERT_NO_FATAL_FAILURE(old_entry.ReloadUsageEntry()) - << reload_description; - ASSERT_NO_FATAL_FAILURE( - old_entry.session().UpdateUsageEntry(&encrypted_usage_header_)) - << reload_description; - ASSERT_NO_FATAL_FAILURE(old_entry.session().close()); - } - } -} - -// A usage report with the wrong pst should fail. -TEST_P(OEMCryptoUsageTableTest, GenerateReportWrongPST) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE( - s.GenerateReport("wrong_pst", OEMCrypto_ERROR_WRONG_PST)); -} - -// Test usage table timing. -TEST_P(OEMCryptoUsageTableTest, TimingTest) { - LicenseWithUsageEntry entry1; - entry1.license_messages().set_api_version(license_api_version_); - Session& s1 = entry1.session(); - entry1.set_pst("my_pst_1"); - ASSERT_NO_FATAL_FAILURE(entry1.MakeOfflineAndClose(this)); - - LicenseWithUsageEntry entry2; - entry2.license_messages().set_api_version(license_api_version_); - Session& s2 = entry2.session(); - entry2.set_pst("my_pst_2"); - ASSERT_NO_FATAL_FAILURE(entry2.MakeOfflineAndClose(this)); - - LicenseWithUsageEntry entry3; - entry3.license_messages().set_api_version(license_api_version_); - Session& s3 = entry3.session(); - entry3.set_pst("my_pst_3"); - ASSERT_NO_FATAL_FAILURE(entry3.MakeOfflineAndClose(this)); - - ASSERT_NO_FATAL_FAILURE(entry1.MakeOfflineAndClose(this)); - ASSERT_NO_FATAL_FAILURE(entry2.MakeOfflineAndClose(this)); - ASSERT_NO_FATAL_FAILURE(entry3.MakeOfflineAndClose(this)); - - wvutil::TestSleep::Sleep(kLongSleep); - ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); - - ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); - - wvutil::TestSleep::Sleep(kLongSleep); - ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); - - wvutil::TestSleep::Sleep(kLongSleep); - ASSERT_NO_FATAL_FAILURE(entry1.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s1.close()); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - wvutil::TestSleep::Sleep(kLongSleep); - // This is as close to reboot as we can simulate in code. - ShutDown(); - wvutil::TestSleep::Sleep(kShortSleep); - Restart(); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), - encrypted_usage_header_.size())); - - // After a reboot, we should be able to reload keys, and generate reports. - wvutil::TestSleep::Sleep(kLongSleep); - ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(entry3.OpenAndReload(this)); - - wvutil::TestSleep::Sleep(kLongSleep); - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry1.GenerateVerifyReport(kInactiveUsed)); - ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry2.GenerateVerifyReport(kActive)); - ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry3.GenerateVerifyReport(kUnused)); -} - -// Verify the times in the usage report. For performance reasons, we allow -// the times in the usage report to be off by as much as kUsageTimeTolerance, -// which is 10 seconds. This acceptable error is called slop. This test needs -// to run long enough that the reported values are distinct, even after -// accounting for this slop. -TEST_P(OEMCryptoUsageTableTest, VerifyUsageTimes) { - LicenseWithUsageEntry entry; - entry.license_messages().set_api_version(license_api_version_); - entry.MakeAndLoadOnline(this); - Session& s = entry.session(); - - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - - const int64_t kDotIntervalInSeconds = 5; - const int64_t kIdleInSeconds = 20; - const int64_t kPlaybackLoopInSeconds = 2 * 60; - - cout << "This test verifies the elapsed time reported in the usage table " - "for a 2 minute simulated playback." - << endl; - cout << "The total time for this test is about " - << kPlaybackLoopInSeconds + 2 * kIdleInSeconds << " seconds." << endl; - cout << "Wait " << kIdleInSeconds - << " seconds to verify usage table time before playback." << endl; - - PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); - - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); - cout << "Start simulated playback..." << endl; - - int64_t dot_time = kDotIntervalInSeconds; - int64_t playback_time = 0; - const int64_t start_time = wvutil::Clock().GetCurrentTime(); - do { - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); - wvutil::TestSleep::Sleep(kShortSleep); - playback_time = wvutil::Clock().GetCurrentTime() - start_time; - ASSERT_LE(0, playback_time); - if (playback_time >= dot_time) { - cout << "."; - cout.flush(); - dot_time += kDotIntervalInSeconds; - } - } while (playback_time < kPlaybackLoopInSeconds); - cout << "\nSimulated playback time = " << playback_time << " seconds.\n"; - - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); - EXPECT_NEAR(s.pst_report().seconds_since_first_decrypt() - - s.pst_report().seconds_since_last_decrypt(), - playback_time, kUsageTableTimeTolerance); - - // We must update the usage entry BEFORE sleeping, not after. - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - - cout << "Wait another " << kIdleInSeconds - << " seconds " - "to verify usage table time since playback ended." - << endl; - PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); - - // At this point, this is what we expect: - // idle playback loop idle - // |-----|-------------------------|-----| - // |<--->| = seconds_since_last_decrypt - // |<----------------------------->| = seconds_since_first_decrypt - // |<------------------------------------| = seconds_since_license_received - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); - ASSERT_NO_FATAL_FAILURE( - entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); -} - -// This test class is only used to roll back the wall clock. It is used to -// verify that OEMCrypto's system clock is monotonic. It is should only be -// run on a device that allows an application to set the clock. -class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest { - public: - void SetUp() override { OEMCryptoUsageTableTest::SetUp(); } - - void TearDown() override { - wvutil::TestSleep::ResetRollback(); - OEMCryptoUsageTableTest::TearDown(); - } -}; - -// NOTE: This test needs root access since clock_settime messes with the -// system time in order to verify that OEMCrypto protects against rollbacks in -// usage entries. Therefore, this test is filtered if not run as root. We -// don't test roll-forward protection or instances where the user rolls back -// the time to the last decrypt call since this requires hardware-secure -// clocks to guarantee. -// -// This test overlaps two tests in parallel because they each have several -// seconds of sleeping, then we roll the system clock back, and then we sleep -// some more. -// For the first test, we use entry1. The playback duration is 6 short -// intervals. We play for 3, roll the clock back 2, and then play for 3 more. -// We then sleep until after the allowed playback duration and try to play. If -// OEMCrypto allows the rollback, then there is only 5 intervals, which is -// legal. But if OEMCrypto forbids the rollback, then there is 8 intervals of -// playback, which is not legal. -// -// For the second test, we use entry2. The rental duration is 6 short -// intervals. The times are the same as for entry1, except we do not start -// playback for entry2 until the end. - -// clang-format off -// [--][--][--][--][--][--][--] -- playback or rental limit. -// -// Here's what the system clock sees with rollback: -// [--][--][--] 3 short intervals of playback or sleep -// <------> Rollback 2 short intervals. -// [--][--][--] 3 short intervals of playback or sleep -// [--] 1 short intervals of sleep. -// -// Here's what the system clock sees without rollback: -// [--][--][--] 3 short intervals of playback or sleep -// [--][--][--] 3 short intervals of playback or sleep -// [--][--]X 2 short intervals of sleep. -// -// |<---------------------------->| 8 short intervals from license received -// until pst reports generated. -// clang-format on - -TEST_P(OEMCryptoUsageTableTestWallClock, TimeRollbackPrevention) { - cout << "This test temporarily rolls back the system time in order to " - "verify " - << "that the usage report accounts for the change. After the test, it " - << "rolls the clock back forward." << endl; - constexpr int kRollBackTime = kShortSleep * 2; - constexpr int kPlaybackCount = 3; - constexpr int kTotalTime = kShortSleep * 8; - - LicenseWithUsageEntry entry1; - entry1.license_messages() - .core_response() - .timer_limits.total_playback_duration_seconds = 7 * kShortSleep; - entry1.MakeOfflineAndClose(this); - Session& s1 = entry1.session(); - ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this)); - - LicenseWithUsageEntry entry2; - entry2.license_messages() - .core_response() - .timer_limits.rental_duration_seconds = 7 * kShortSleep; - entry2.MakeOfflineAndClose(this); - Session& s2 = entry2.session(); - ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); - - // Start with three short intervals of playback for entry1. - for (int i = 0; i < kPlaybackCount; i++) { - ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); - wvutil::TestSleep::Sleep(kShortSleep); - ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); - } - - cout << "Rolling the system time back..." << endl; - ASSERT_TRUE(wvutil::TestSleep::RollbackSystemTime(kRollBackTime)); - - // Three more short intervals of playback after the rollback. - for (int i = 0; i < kPlaybackCount; i++) { - ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); - wvutil::TestSleep::Sleep(kShortSleep); - ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); - } - - // One short interval of sleep to push us past the 6 interval duration. - wvutil::TestSleep::Sleep(2 * kShortSleep); - - // Should not be able to continue playback in entry1. - ASSERT_NO_FATAL_FAILURE( - entry1.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); - // Should not be able to start playback in entry2. - ASSERT_NO_FATAL_FAILURE( - entry2.TestDecryptCTR(true, OEMCrypto_ERROR_KEY_EXPIRED)); - - // Now we look at the usage reports: - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); - - ASSERT_NO_FATAL_FAILURE(s1.GenerateReport(entry1.pst())); - wvutil::Unpacked_PST_Report report1 = s1.pst_report(); - EXPECT_EQ(report1.status(), kActive); - EXPECT_GE(report1.seconds_since_license_received(), kTotalTime); - EXPECT_GE(report1.seconds_since_first_decrypt(), kTotalTime); - - ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(entry2.pst())); - wvutil::Unpacked_PST_Report report2 = s2.pst_report(); - EXPECT_EQ(report2.status(), kUnused); - EXPECT_GE(report2.seconds_since_license_received(), kTotalTime); -} - -// Verify that a large PST can be used with usage table entries. -TEST_P(OEMCryptoUsageTableTest, PSTLargeBuffer) { - std::string pst(kMaxPSTLength, 'a'); // A large PST. - LicenseWithUsageEntry entry(pst); - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE( - entry.TestDecryptCTR()); // Should be able to decrypt. - ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate. - // After deactivate, should not be able to decrypt. - ASSERT_NO_FATAL_FAILURE( - entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); - ASSERT_NO_FATAL_FAILURE(s.close()); -} - -// Verify that a usage entry with an invalid session cannot be used. -TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSession) { - std::string pst("pst"); - LicenseWithUsageEntry entry; - entry.license_messages().set_pst(pst); - - entry.session().open(); - ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); - entry.session().close(); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, - OEMCrypto_DeactivateUsageEntry( - entry.session().session_id(), - reinterpret_cast(pst.c_str()), pst.length())); - - entry.session().open(); - ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); - entry.session().close(); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, - OEMCrypto_MoveEntry(entry.session().session_id(), 0)); -} - -// Verify that a usage entry with an invalid session cannot be used. -TEST_P(OEMCryptoUsageTableTest, ReuseUsageEntryWithInvalidSessionAPI17) { - std::string pst("pst"); - LicenseWithUsageEntry entry; - entry.license_messages().set_pst(pst); - - entry.session().open(); - ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); - entry.session().close(); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, - OEMCrypto_ReuseUsageEntry(entry.session().session_id(), 0)); -} - -INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoUsageTableTest, - Range(kCoreMessagesAPI, kCurrentAPI + 1)); - -// These tests only work when the license has a core message. -INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableDefragTest, - Values(kCurrentAPI)); - -// These tests only work when the license has a core message. -INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableTestWallClock, - Values(kCurrentAPI)); - -/// @} } // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp new file mode 100644 index 00000000..8b78b14a --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp @@ -0,0 +1,1707 @@ +// 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_usage_table_test.h" + +using ::testing::Range; +using ::testing::Values; + +namespace wvoec { + +/// @addtogroup usage_table +/// @{ + +// Test that successive calls to PrepAndSignProvisioningRequest only increase +// the provisioning count in the ODK message +TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) { + // local struct to hold count values from core message + typedef struct counts { + uint32_t prov; + uint32_t lic; + uint32_t decrypt; + uint64_t mgn; + } counts; + + // prep and sign provisioning2 request, then extract counter values + auto provision2 = [&](counts* c) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + provisioning_messages.SignAndVerifyRequest(); + c->prov = + provisioning_messages.core_request().counter_info.provisioning_count; + c->lic = provisioning_messages.core_request().counter_info.license_count; + c->decrypt = + provisioning_messages.core_request().counter_info.decrypt_count; + c->mgn = provisioning_messages.core_request() + .counter_info.master_generation_number; + }; + + // prep and sign provisioning4 request, then extract counter values + auto provision4 = [&](counts* c) { + // Same as SessionUtil::CreateProv4OEMKey, but we can't extract counter + // values using that function + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + Provisioning40RoundTrip provisioning_messages(&s); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.PrepareSession(true)); + ASSERT_NO_FATAL_FAILURE(s.SetPublicKeyFromSubjectPublicKey( + provisioning_messages.oem_key_type(), + provisioning_messages.oem_public_key().data(), + provisioning_messages.oem_public_key().size())); + wrapped_oem_key_ = provisioning_messages.wrapped_oem_key(); + oem_public_key_ = provisioning_messages.oem_public_key(); + oem_key_type_ = provisioning_messages.oem_key_type(); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadOEMCertResponse()); + c->prov = + provisioning_messages.core_request().counter_info.provisioning_count; + c->lic = provisioning_messages.core_request().counter_info.license_count; + c->decrypt = + provisioning_messages.core_request().counter_info.decrypt_count; + c->mgn = provisioning_messages.core_request() + .counter_info.master_generation_number; + }; + + if (global_features.provisioning_method == OEMCrypto_OEMCertificate || + global_features.provisioning_method == OEMCrypto_DrmCertificate) { + GTEST_SKIP() << "Provisioning method does not increment prov counter"; + } else if (global_features.provisioning_method == OEMCrypto_Keybox) { + counts c1, c2; + provision2(&c1); + provision2(&c2); + + ASSERT_TRUE(c2.prov > c1.prov); + ASSERT_TRUE(c2.lic == c1.lic); + ASSERT_TRUE(c2.decrypt == c1.decrypt); + ASSERT_TRUE(c2.mgn == c1.mgn); + } else if (global_features.provisioning_method == + OEMCrypto_BootCertificateChain) { + counts c1, c2; + provision4(&c1); + provision4(&c2); + + ASSERT_TRUE(c2.prov > c1.prov); + ASSERT_TRUE(c2.lic == c1.lic); + ASSERT_TRUE(c2.decrypt == c1.decrypt); + ASSERT_TRUE(c2.mgn == c1.mgn); + } +} + +// Test that successive calls to PrepAndSignLicenseRequest only increase +// the license count in the ODK message +TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) { + Session s; + s.open(); + LicenseRoundTrip license_messages(&s); + InstallTestDrmKey(&s); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + uint32_t prov_count1 = + license_messages.core_request().counter_info.provisioning_count; + uint32_t lic_count1 = + license_messages.core_request().counter_info.license_count; + uint32_t decrypt_count1 = + license_messages.core_request().counter_info.decrypt_count; + uint64_t master_generation_number1 = + license_messages.core_request().counter_info.master_generation_number; + + Session s2; + s2.open(); + LicenseRoundTrip license_messages2(&s2); + InstallTestDrmKey(&s2); + ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest()); + uint32_t prov_count2 = + license_messages2.core_request().counter_info.provisioning_count; + uint32_t lic_count2 = + license_messages2.core_request().counter_info.license_count; + uint32_t decrypt_count2 = + license_messages2.core_request().counter_info.decrypt_count; + uint64_t master_generation_number2 = + license_messages2.core_request().counter_info.master_generation_number; + + ASSERT_TRUE(prov_count2 == prov_count1); + ASSERT_TRUE(lic_count2 > lic_count1); + ASSERT_TRUE(decrypt_count2 == decrypt_count1); + ASSERT_TRUE(master_generation_number2 == master_generation_number1); +} + +// Test that the license request includes the master generation number, and that +// it is incremented correctly after usage table modification (save offline +// license) and decrypt. Also test that decrypt count increments. +TEST_F(OEMCryptoSessionTests, MasterGeneration_IncrementCounterAPI18) { + if (!OEMCrypto_SupportsUsageTable()) { + GTEST_SKIP() << "Usage table not supported, so master generation number " + "does not need to be checked."; + } + Session s1; + s1.open(); + LicenseRoundTrip license_messages(&s1); + InstallTestDrmKey(&s1); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + uint32_t prov_count1 = + license_messages.core_request().counter_info.provisioning_count; + uint32_t lic_count1 = + license_messages.core_request().counter_info.license_count; + uint32_t decrypt_count1 = + license_messages.core_request().counter_info.decrypt_count; + uint64_t master_generation_number1 = + license_messages.core_request().counter_info.master_generation_number; + + // do the same as ReloadOfflineLicense to push the master generation number + // up + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(kCurrentAPI); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + + Session s2; + s2.open(); + LicenseRoundTrip license_messages2(&s2); + InstallTestDrmKey(&s2); + ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest()); + uint32_t prov_count2 = + license_messages2.core_request().counter_info.provisioning_count; + uint32_t lic_count2 = + license_messages2.core_request().counter_info.license_count; + uint32_t decrypt_count2 = + license_messages2.core_request().counter_info.decrypt_count; + uint64_t master_generation_number2 = + license_messages2.core_request().counter_info.master_generation_number; + + ASSERT_TRUE(prov_count2 == prov_count1); + ASSERT_TRUE(lic_count2 > lic_count1); + ASSERT_TRUE(decrypt_count2 > decrypt_count1); + ASSERT_TRUE(master_generation_number2 > master_generation_number1); +} +TEST_P(OEMCryptoUsageTableTest, + OEMCryptoMemoryLoadUsageEntryForHugeInvalidUsageEntryNumber) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + // Make first entry 0. + entry.MakeOfflineAndClose(this); + Session s; + s.open(); + InstallTestDrmKey(&s); + const uint32_t usage_entry_number = kHugeRandomNumber; + ASSERT_NO_FATAL_FAILURE(OEMCrypto_LoadUsageEntry( + s.session_id(), usage_entry_number, s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); +} + +// Test an online or streaming license with PST. This license requires a +// valid nonce and can only be loaded once. +TEST_P(OEMCryptoUsageTableTest, OnlineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + + // test repeated report generation + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + // Flag the entry as inactive. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + // Decrypt should fail. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); +} + +// Test the usage report when the license is loaded but the keys are never +// used for decryption. +TEST_P(OEMCryptoUsageTableTest, OnlineLicenseUnused) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // No decrypt. We do not use this license. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + // Flag the entry as inactive. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); + // Decrypt should fail. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); +} + +// Test that the usage table has been updated and saved before a report can be +// generated. +TEST_P(OEMCryptoUsageTableTest, ForbidReportWithNoUpdate) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + // Cannot generate a report without first updating the file. + ASSERT_NO_FATAL_FAILURE( + s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // Now it's OK. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + // Flag the entry as inactive. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + // Cannot generate a report without first updating the file. + ASSERT_NO_FATAL_FAILURE( + s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); + // Decrypt should fail. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// Test an online license with a license renewal. +TEST_P(OEMCryptoUsageTableTest, OnlineLicenseWithRefreshAPI16) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + + RenewalRoundTrip renewal_messages(&entry.license_messages()); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Verify that a streaming license cannot be reloaded. +TEST_P(OEMCryptoUsageTableTest, RepeatOnlineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); + Session s2; + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s2)); + s2.LoadUsageEntry(s); // Use the same entry. + ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s2)); +} + +// An offline license should not load on the first call if the nonce is bad. +TEST_P(OEMCryptoUsageTableTest, OnlineBadNonce) { + Session s; + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceEnabled | + wvoec::kControlNonceRequired); + license_messages.set_pst("my-pst"); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + for (uint32_t i = 0; i < license_messages.num_keys(); i++) + license_messages.response_data().keys[i].control.nonce ^= 42; + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse()); +} + +// A license with non-zero replay control bits needs a valid pst. +TEST_P(OEMCryptoUsageTableTest, OnlineEmptyPST) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceEnabled | + wvoec::kControlNonceRequired); + // DO NOT SET PST: license_messages.set_pst(pst); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); +} + +// A license with non-zero replay control bits needs a valid pst. +TEST_P(OEMCryptoUsageTableTest, OnlineMissingEntry) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceEnabled | + wvoec::kControlNonceRequired); + license_messages.set_pst("my-pst"); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + // ENTRY NOT CREATED: ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); +} + +// Sessions should have at most one entry at a time. This tests different +// orderings of CreateNewUsageEntry and LoadUsageEntry calls. +TEST_P(OEMCryptoUsageTableTest, CreateAndLoadMultipleEntriesAPI16) { + // Entry Count: we start each test with an empty header. + uint32_t usage_entry_number; + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + Session& s = entry.session(); + // Make first entry 0. + ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); + + // Load an entry, then try to create a second. + ASSERT_NO_FATAL_FAILURE(s.open()); + // Reload entry 0. + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); + // Create new entry 1 should fail. + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_CreateNewUsageEntry(entry.session().session_id(), + &usage_entry_number)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Create an entry, then try to load a second. + Session s2; + ASSERT_NO_FATAL_FAILURE(s2.open()); + // Create entry 1. + ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry()); + // Try to reload entry 0. + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(s2.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + // Reload an entry and a license, then try to load the same entry again. + // This reloads entry 0. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Create an entry, then try to create a second entry. + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_CreateNewUsageEntry( + s2.session_id(), &usage_entry_number)); +} + +// An entry can be loaded in only one session at a time. +TEST_P(OEMCryptoUsageTableTest, LoadEntryInMultipleSessions) { + // Entry Count: we start each test with an empty header. + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + Session& s = entry.session(); + // Make first entry 0. + ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); + const uint32_t usage_entry_number = s.usage_entry_number(); + EXPECT_EQ(usage_entry_number, 0u); // Should be only entry in this test. + + // Load an entry, then try to create a second. + ASSERT_NO_FATAL_FAILURE(s.open()); + // Reload entry 0. + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); + + // Create an entry, then try to load a second. + Session s2; + ASSERT_NO_FATAL_FAILURE(s2.open()); + // Try to load entry 0 into session 2. + ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, + OEMCrypto_LoadUsageEntry(s2.session_id(), usage_entry_number, + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); +} + +// Test generic encrypt when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoEncrypt) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 0; + vector expected_encrypted; + EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &expected_encrypted); + vector key_handle; + sts = + GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CENC, key_handle); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector encrypted(clear_buffer_.size()); + sts = OEMCrypto_Generic_Encrypt(key_handle.data(), key_handle.size(), + clear_buffer_.data(), clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, + encrypted.data()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + EXPECT_EQ(expected_encrypted, encrypted); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + encrypted.assign(clear_buffer_.size(), 0); + sts = OEMCrypto_Generic_Encrypt(key_handle.data(), key_handle.size(), + clear_buffer_.data(), clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, + encrypted.data()); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(encrypted, expected_encrypted); +} + +// Test generic decrypt when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoDecrypt) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 1; + vector encrypted; + EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &encrypted); + vector key_handle; + sts = + GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CENC, key_handle); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector resultant(encrypted.size()); + sts = OEMCrypto_Generic_Decrypt( + key_handle.data(), key_handle.size(), encrypted.data(), encrypted.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + EXPECT_EQ(clear_buffer_, resultant); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + resultant.assign(encrypted.size(), 0); + sts = OEMCrypto_Generic_Decrypt( + key_handle.data(), key_handle.size(), encrypted.data(), encrypted.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(clear_buffer_, resultant); +} + +// Test generic sign when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoSign) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 2; + vector expected_signature; + SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &expected_signature); + + vector key_handle; + sts = + GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CENC, key_handle); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + size_t gen_signature_length = 0; + sts = OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(), + clear_buffer_.data(), clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, nullptr, + &gen_signature_length); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); + vector signature(SHA256_DIGEST_LENGTH); + sts = OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(), + clear_buffer_.data(), clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, signature.data(), + &gen_signature_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(expected_signature, signature); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + signature.assign(SHA256_DIGEST_LENGTH, 0); + gen_signature_length = SHA256_DIGEST_LENGTH; + sts = OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(), + clear_buffer_.data(), clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, signature.data(), + &gen_signature_length); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(signature, expected_signature); +} + +// Test generic verify when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoVerify) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 3; + vector signature; + SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &signature); + + vector key_handle; + sts = + GetKeyHandleIntoVector(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CENC, key_handle); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + sts = OEMCrypto_Generic_Verify(key_handle.data(), key_handle.size(), + clear_buffer_.data(), clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, signature.data(), + signature.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + sts = OEMCrypto_Generic_Verify(key_handle.data(), key_handle.size(), + clear_buffer_.data(), clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, signature.data(), + signature.size()); + ASSERT_NE(OEMCrypto_SUCCESS, sts); +} + +// Test that an offline license can be loaded. +TEST_P(OEMCryptoUsageTableTest, OfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); +} + +// Test that an offline license can be loaded and that the license can be +// renewed. +TEST_P(OEMCryptoUsageTableTest, OfflineLicenseRefresh) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoad(this, wvoec::kControlNonceOrEntry); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + // License renewal message is signed by client and verified by the server. + RenewalRoundTrip renewal_messages(&entry.license_messages()); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Test that an offline license can be reloaded in a new session. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Test that an offline license can be reloaded in a new session, and then +// refreshed. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicenseWithRefresh) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + RenewalRoundTrip renewal_messages(&entry.license_messages()); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Verify that we can still reload an offline license after +// OEMCrypto_Terminate and Initialize are called. This is as close to a reboot +// as we can do in a unit test. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicenseWithTerminate) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + ShutDown(); // This calls OEMCrypto_Terminate. + Restart(); // This calls OEMCrypto_Initialize. + ASSERT_EQ(OEMCrypto_SUCCESS, LoadUsageTableHeader(encrypted_usage_header_)); + + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// If we attempt to load a second license with the same usage entry as the +// first, but it has different mac keys, then the attempt should fail. This +// is how we verify that we are reloading the same license. +TEST_P(OEMCryptoUsageTableTest, BadReloadOfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + // Offline license with new mac keys should fail. + Session s2; + LicenseRoundTrip license_messages2(&s2); + // Copy the response, and then change the mac keys. + license_messages2.response_data() = entry.license_messages().response_data(); + license_messages2.core_response() = entry.license_messages().core_response(); + license_messages2.response_data().mac_keys[7] ^= 42; + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s2)); + // This is a valid license: it is correctly signed. + license_messages2.EncryptAndSignResponse(); + // Load the usage entry should be OK. + ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + // Now we go back to the original license response. It should load OK. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); +} + +// An offline license should not load on the first call if the nonce is bad. +TEST_P(OEMCryptoUsageTableTest, OfflineBadNonce) { + Session s; + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceOrEntry); + license_messages.set_pst("my-pst"); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + for (size_t i = 0; i < license_messages.num_keys(); i++) + license_messages.response_data().keys[i].control.nonce ^= 42; + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse()); +} + +// An offline license needs a valid pst. +TEST_P(OEMCryptoUsageTableTest, OfflineEmptyPST) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceOrEntry); + // DO NOT SET PST: license_messages.set_pst(pst); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); +} + +// If we try to reload a license with a different PST, the attempt should +// fail. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineWrongPST) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + Session s2; + LicenseRoundTrip license_messages2(&s2); + license_messages2.response_data() = entry.license_messages().response_data(); + license_messages2.core_response() = entry.license_messages().core_response(); + // Change the middle of the pst. + license_messages2.response_data().pst[3] ^= 'Z'; + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s2)); + // This is a valid license: it is correctly signed. + license_messages2.EncryptAndSignResponse(); + // Load the usage entry should be OK. + ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); +} + +// Once a license has been deactivated, the keys can no longer be used for +// decryption. However, we can still generate a usage report. +TEST_P(OEMCryptoUsageTableTest, DeactivateOfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + // Reload the offline license. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR()); // Should be able to decrypt. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate. + // After deactivate, should not be able to decrypt. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Offline license can not be reused if it has been deactivated. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); + ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s)); + s.close(); + + // But we can still generate a report. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // Sending a release from an offline license that has been deactivate will + // only work if the license server can handle v16 licenses. This is a rare + // condition, so it is OK to break it during the transition months. + entry.license_messages().set_api_version(global_features.api_version); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); +} + +// The usage report should indicate that the keys were never used for +// decryption. +TEST_P(OEMCryptoUsageTableTest, DeactivateOfflineLicenseUnused) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + // No Decrypt. This license is unused. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate. + // After deactivate, should not be able to decrypt. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Offline license can not be reused if it has been deactivated. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); + ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s)); + s.close(); + + // But we can still generate a report. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // Sending a release from an offline license that has been deactivate will + // only work if the license server can handle v16 licenses. This is a rare + // condition, so it is OK to break it during the transition months. + entry.license_messages().set_api_version(global_features.api_version); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); +} + +TEST_P(OEMCryptoUsageTableTest, SecureStop) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); + // When we generate a secure stop without loading the license first, it + // should assume the server does not support core messages. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); +} + +// Test update usage table fails when passed a null pointer. +TEST_P(OEMCryptoUsageTableTest, UpdateFailsWithNullPtr) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + size_t header_buffer_length = encrypted_usage_header_.size(); + size_t entry_buffer_length = s.encrypted_usage_entry().size(); + vector buffer(entry_buffer_length); + // Now try to pass in null pointers for the buffers. This should fail. + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_UpdateUsageEntry(s.session_id(), nullptr, &header_buffer_length, + buffer.data(), &entry_buffer_length)); + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_UpdateUsageEntry( + s.session_id(), encrypted_usage_header_.data(), + &header_buffer_length, nullptr, &entry_buffer_length)); +} + +// Class used to test usage table defragmentation. +class OEMCryptoUsageTableDefragTest : public OEMCryptoUsageTableTest { + protected: + void ReloadLicense(LicenseWithUsageEntry* entry) { + Session& s = entry->session(); + ASSERT_NO_FATAL_FAILURE(entry->OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry->TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry->GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.close()); + } + + void FailReloadLicense(LicenseWithUsageEntry* entry, + OEMCryptoResult expected_result) { + Session& s = entry->session(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); + ASSERT_EQ(expected_result, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + + ASSERT_NE(OEMCrypto_SUCCESS, entry->license_messages().LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s.close()); + } + + void ShrinkHeader(uint32_t new_size, + OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { + // We call OEMCrypto_ShrinkUsageTableHeader once with a zero length + // buffer, so that OEMCrypto can tell us how big the buffer should be. + size_t header_buffer_length = 0; + OEMCryptoResult sts = OEMCrypto_ShrinkUsageTableHeader( + new_size, nullptr, &header_buffer_length); + // If we are expecting success, then the first call shall return + // SHORT_BUFFER. However, if we are not expecting success, this first call + // may return either SHORT_BUFFER or the expect error. + if (expected_result == OEMCrypto_SUCCESS) { + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + } else if (sts != OEMCrypto_ERROR_SHORT_BUFFER) { + // If we got any thing from the first call, it should be the expected + // error, and we don't need to call a second time. + ASSERT_EQ(expected_result, sts); + return; + } + // If the first call resulted in SHORT_BUFFER, we should resize the buffer + // and try again. + ASSERT_LT(0u, header_buffer_length); + encrypted_usage_header_.resize(header_buffer_length); + sts = OEMCrypto_ShrinkUsageTableHeader( + new_size, encrypted_usage_header_.data(), &header_buffer_length); + // For the second call, we always demand the expected result. + ASSERT_EQ(expected_result, sts); + if (sts == OEMCrypto_SUCCESS) { + encrypted_usage_header_.resize(header_buffer_length); + } + } +}; + +// Verify that usage table entries can be moved around in the table. +TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntries) { + const size_t ENTRY_COUNT = 10; + vector entries(ENTRY_COUNT); + for (size_t i = 0; i < ENTRY_COUNT; i++) { + entries[i].set_pst("pst " + std::to_string(i)); + ASSERT_NO_FATAL_FAILURE(entries[i].MakeOfflineAndClose(this)) + << "On license " << i << " pst=" << entries[i].pst(); + wvutil::TestSleep::SyncFakeClock(); + } + for (size_t i = 0; i < ENTRY_COUNT; i++) { + ASSERT_NO_FATAL_FAILURE(entries[i].OpenAndReload(this)) + << "On license " << i << " pst=" << entries[i].pst(); + ASSERT_NO_FATAL_FAILURE(entries[i].session().close()) + << "On license " << i << " pst=" << entries[i].pst(); + } + // Move 4 to 1. + ASSERT_NO_FATAL_FAILURE( + entries[4].session().MoveUsageEntry(1, &encrypted_usage_header_)); + // Shrink header to 3 entries 0, 1 was 4, 2. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(3)); + ShutDown(); + Restart(); + ASSERT_EQ(OEMCrypto_SUCCESS, LoadUsageTableHeader(encrypted_usage_header_)); + wvutil::TestSleep::SyncFakeClock(); + ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[0])); + // Now has index 1. + ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[4])); + ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[2])); + // When 4 was moved to 1, it increased the gen. number in the header. + ASSERT_NO_FATAL_FAILURE( + FailReloadLicense(&entries[1], OEMCrypto_ERROR_GENERATION_SKEW)); + // Index 3 is beyond the end of the table. + ASSERT_NO_FATAL_FAILURE( + FailReloadLicense(&entries[3], OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// A usage table entry cannot be moved into an entry where an open session is +// currently using the entry. +TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntriesToOpenSession) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); + // s0 currently open on index 0. Expect this to fail: + ASSERT_NO_FATAL_FAILURE(entry1.session().MoveUsageEntry( + 0, &encrypted_usage_header_, OEMCrypto_ERROR_ENTRY_IN_USE)); +} + +TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntriesToInvalidHugeEntryIndex) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + entry0.session().open(); + entry0.ReloadUsageEntry(); + ASSERT_NO_FATAL_FAILURE( + OEMCrypto_MoveEntry(entry0.session().session_id(), kHugeRandomNumber)); +} + +// The usage table cannot be shrunk if any session is using an entry that +// would be deleted. +TEST_P(OEMCryptoUsageTableDefragTest, ShrinkOverOpenSessions) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); + entry1.session().open(); + ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry()); + // Since s0 and s1 are open, we can't shrink. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_ERROR_ENTRY_IN_USE)); + entry1.session().close(); // Can shrink after closing s1, even if s0 is open. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_SUCCESS)); +} + +// Verify the usage table size can be increased. +TEST_P(OEMCryptoUsageTableDefragTest, EnlargeHeader) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + // Can only shrink the header -- not make it bigger. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(4, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// A new header can only be created while no entries are in use. +TEST_P(OEMCryptoUsageTableDefragTest, CreateNewHeaderWhileUsingOldOne) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); + const bool kExpectFailure = false; + ASSERT_NO_FATAL_FAILURE(CreateUsageTableHeader(kExpectFailure)); +} + +// Verify that a usage table entry can only be loaded into the correct index +// of the table. +TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryWrongIndex) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().set_usage_entry_number(1); + ASSERT_NO_FATAL_FAILURE( + FailReloadLicense(&entry0, OEMCrypto_ERROR_INVALID_SESSION)); +} + +// Verify that a usage table entry cannot be loaded if it has been altered. +TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryBadData) { + LicenseWithUsageEntry entry; + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); + vector data = s.encrypted_usage_entry(); + ASSERT_LT(0UL, data.size()); + data[0] ^= 42; + // Error could be signature or verification error. + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + data.data(), data.size())); +} + +// This verifies we can actually create the required number of usage table +// entries. +TEST_P(OEMCryptoUsageTableDefragTest, ManyUsageEntries) { + // OEMCrypto is required to store at least 300 entries in the usage table + // header, but it is allowed to store more. This test verifies that if we + // keep adding entries, the error indicates a resource limit. It then + // verifies that all of the successful entries are still valid after we + // throw out the last invalid entry. + + // After API 16, we require 300 entries in the usage table. Before API 16, + // we required 200. + const size_t required_capacity = RequiredUsageSize(); + + // We try to make a much large header, and assume there is an error at some + // point. + const size_t attempt_count = required_capacity * 5; + // Count of how many entries we successfully create. + size_t successful_count = 0; + + // These entries have licenses tied to them. + std::vector> entries; + // Store the status of the last attempt to create an entry. + OEMCryptoResult status = OEMCrypto_SUCCESS; + while (successful_count < attempt_count && status == OEMCrypto_SUCCESS) { + wvutil::TestSleep::SyncFakeClock(); + LOGD("Creating license for entry %zu", successful_count); + entries.push_back( + std::unique_ptr(new LicenseWithUsageEntry())); + entries.back()->set_pst("pst " + std::to_string(successful_count)); + ASSERT_NO_FATAL_FAILURE(entries.back()->MakeOfflineAndClose(this, &status)) + << "Failed creating license for entry " << successful_count; + if (status != OEMCrypto_SUCCESS) { + // Remove the failed session. + entries.resize(entries.size() - 1); + break; + } + EXPECT_EQ(entries.back()->session().usage_entry_number(), successful_count); + successful_count++; + // We don't create a license for each entry. For every license, we'll + // create 10 empty entries. + constexpr size_t filler_count = 10; + for (size_t i = 0; i < filler_count; i++) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry(&status)) + << "Failed creating entry " << successful_count; + if (status != OEMCrypto_SUCCESS) break; + EXPECT_EQ(s.usage_entry_number(), successful_count); + successful_count++; + } + } + LOGD("successful_count = %zu", successful_count); + if (status != OEMCrypto_SUCCESS) { + // If we failed to create this many entries because of limited resources, + // then the error returned should be insufficient resources. + EXPECT_EQ(OEMCrypto_ERROR_INSUFFICIENT_RESOURCES, status) + << "Failed to create license " << successful_count + << ", with wrong error code."; + } + EXPECT_GE(successful_count, required_capacity); + wvutil::TestSleep::SyncFakeClock(); + // Shrink the table a little. + constexpr size_t small_number = 5; + size_t smaller_size = successful_count - small_number; + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(static_cast(smaller_size))); + // Throw out the last license if it was in the part of the table that was + // shrunk. + if (entries.back()->session().usage_entry_number() >= smaller_size) { + entries.pop_back(); + } + // Create a few more license + for (size_t i = 0; i < small_number; i++) { + wvutil::TestSleep::SyncFakeClock(); + entries.push_back( + std::unique_ptr(new LicenseWithUsageEntry())); + entries.back()->set_pst("new pst " + std::to_string(smaller_size + i)); + entries.back()->MakeOfflineAndClose(this); + } + // Make sure that all of the licenses can be reloaded. + for (size_t i = 0; i < entries.size(); i++) { + wvutil::TestSleep::SyncFakeClock(); + Session& s = entries[i]->session(); + ASSERT_NO_FATAL_FAILURE(entries[i]->OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entries[i]->GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entries[i]->TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entries[i]->GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.close()); + } +} + +// Verify that usage entries can be created in the position of existing entry +// indexes. +TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryAPI17) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry()); + const uint32_t number = entry0.session().usage_entry_number(); + entry0.session().close(); + entry1.session().open(); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number)); +} + +// Verify that usage entries cannot replace an entry that is currently in +// use by a session. +TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryIndexInUseAPI17) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry()); + const uint32_t number = entry0.session().usage_entry_number(); + entry1.session().open(); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, + OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number)); +} + +// Verify that usage entries cannot be created if the usage entry index is +// too large. +TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryWithInvalidIndexAPI17) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.session().CreateNewUsageEntry()); + const uint32_t number = entry0.session().usage_entry_number(); + entry0.session().close(); + entry1.session().open(); + ASSERT_EQ( + OEMCrypto_ERROR_UNKNOWN_FAILURE, + OEMCrypto_ReuseUsageEntry(entry1.session().session_id(), number + 42)); +} + +// Verify that usage entries cannot be created if the session already has an +// entry. +TEST_P(OEMCryptoUsageTableDefragTest, + ReuseUsageEntrySessionAlreadyHasEntryAPI17) { + LicenseWithUsageEntry entry; + entry.set_pst("pst 0"); + + // Create 5 entries in the table. + for (int i = 0; i < 5; i++) { + entry.session().open(); + ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); + entry.session().close(); + } + entry.session().open(); + ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); + const uint32_t number = entry.session().usage_entry_number(); + ASSERT_EQ( + OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, + OEMCrypto_ReuseUsageEntry(entry.session().session_id(), number - 3)); +} + +// This verifies that the usage table header can be loaded if the generation +// number is off by one, but not off by two. +TEST_P(OEMCryptoUsageTableTest, ReloadUsageTableWithSkew) { + // This also tests a few other error conditions with usage table headers. + LicenseWithUsageEntry entry; + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + // Reload the license, and save the header. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + vector old_usage_header_2_ = encrypted_usage_header_; + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + vector old_usage_header_1_ = encrypted_usage_header_; + vector old_usage_entry_1 = s.encrypted_usage_entry(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + ShutDown(); + Restart(); + // Null pointer generates error. + ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_LoadUsageTableHeader( + nullptr, old_usage_header_2_.size())); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Cannot load an entry if header didn't load. + ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Modified header generates error. + vector bad_header = encrypted_usage_header_; + bad_header[3] ^= 42; + ASSERT_NE(OEMCrypto_SUCCESS, LoadUsageTableHeader(bad_header)); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Cannot load an entry if header didn't load. + ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Old by 2 generation numbers is error. + ASSERT_EQ(OEMCrypto_ERROR_GENERATION_SKEW, + LoadUsageTableHeader(old_usage_header_2_)); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Cannot load an entry if header didn't load. + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Old by 1 generation numbers is just warning. + ASSERT_EQ(OEMCrypto_WARNING_GENERATION_SKEW, + LoadUsageTableHeader(old_usage_header_1_)); + // Everything else should still work. The old entry goes with the old + // header. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + old_usage_entry_1.data(), + old_usage_entry_1.size())); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromSessionKey()); + ASSERT_EQ(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse()); +} + +TEST_P(OEMCryptoUsageTableTest, LoadAndReloadEntries) { + constexpr size_t kEntryCount = 10; + std::vector entries(kEntryCount); + + for (LicenseWithUsageEntry& entry : entries) { + entry.license_messages().set_api_version(license_api_version_); + } + + for (size_t i = 0; i < kEntryCount; ++i) { + const std::string create_description = + "Creating entry #" + std::to_string(i); + // Create and update a new entry. + LicenseWithUsageEntry& new_entry = entries[i]; + ASSERT_NO_FATAL_FAILURE(new_entry.MakeOfflineAndClose(this)) + << create_description; + // Reload all entries, starting with the most recently created. + for (size_t j = 0; j <= i; ++j) { + const std::string reload_description = + "Reloading entry #" + std::to_string(i - j) + + ", after creating entry #" + std::to_string(i); + LicenseWithUsageEntry& old_entry = entries[i - j]; + ASSERT_NO_FATAL_FAILURE(old_entry.session().open()); + ASSERT_NO_FATAL_FAILURE(old_entry.ReloadUsageEntry()) + << reload_description; + ASSERT_NO_FATAL_FAILURE( + old_entry.session().UpdateUsageEntry(&encrypted_usage_header_)) + << reload_description; + ASSERT_NO_FATAL_FAILURE(old_entry.session().close()); + } + } +} + +// A usage report with the wrong pst should fail. +TEST_P(OEMCryptoUsageTableTest, GenerateReportWrongPST) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE( + s.GenerateReport("wrong_pst", OEMCrypto_ERROR_WRONG_PST)); +} + +// Test usage table timing. +TEST_P(OEMCryptoUsageTableTest, TimingTest) { + LicenseWithUsageEntry entry1; + entry1.license_messages().set_api_version(license_api_version_); + Session& s1 = entry1.session(); + entry1.set_pst("my_pst_1"); + ASSERT_NO_FATAL_FAILURE(entry1.MakeOfflineAndClose(this)); + + LicenseWithUsageEntry entry2; + entry2.license_messages().set_api_version(license_api_version_); + Session& s2 = entry2.session(); + entry2.set_pst("my_pst_2"); + ASSERT_NO_FATAL_FAILURE(entry2.MakeOfflineAndClose(this)); + + LicenseWithUsageEntry entry3; + entry3.license_messages().set_api_version(license_api_version_); + Session& s3 = entry3.session(); + entry3.set_pst("my_pst_3"); + ASSERT_NO_FATAL_FAILURE(entry3.MakeOfflineAndClose(this)); + + ASSERT_NO_FATAL_FAILURE(entry1.MakeOfflineAndClose(this)); + ASSERT_NO_FATAL_FAILURE(entry2.MakeOfflineAndClose(this)); + ASSERT_NO_FATAL_FAILURE(entry3.MakeOfflineAndClose(this)); + + wvutil::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); + + wvutil::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); + + wvutil::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry1.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + wvutil::TestSleep::Sleep(kLongSleep); + // This is as close to reboot as we can simulate in code. + ShutDown(); + wvutil::TestSleep::Sleep(kShortSleep); + Restart(); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), + encrypted_usage_header_.size())); + + // After a reboot, we should be able to reload keys, and generate reports. + wvutil::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry3.OpenAndReload(this)); + + wvutil::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry1.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry2.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry3.GenerateVerifyReport(kUnused)); +} + +// Verify the times in the usage report. For performance reasons, we allow +// the times in the usage report to be off by as much as kUsageTimeTolerance, +// which is 10 seconds. This acceptable error is called slop. This test needs +// to run long enough that the reported values are distinct, even after +// accounting for this slop. +TEST_P(OEMCryptoUsageTableTest, VerifyUsageTimes) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + + const int64_t kDotIntervalInSeconds = 5; + const int64_t kIdleInSeconds = 20; + const int64_t kPlaybackLoopInSeconds = 2 * 60; + + cout << "This test verifies the elapsed time reported in the usage table " + "for a 2 minute simulated playback." + << endl; + cout << "The total time for this test is about " + << kPlaybackLoopInSeconds + 2 * kIdleInSeconds << " seconds." << endl; + cout << "Wait " << kIdleInSeconds + << " seconds to verify usage table time before playback." << endl; + + PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + cout << "Start simulated playback..." << endl; + + int64_t dot_time = kDotIntervalInSeconds; + int64_t playback_time = 0; + const int64_t start_time = wvutil::Clock().GetCurrentTime(); + do { + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + wvutil::TestSleep::Sleep(kShortSleep); + playback_time = wvutil::Clock().GetCurrentTime() - start_time; + ASSERT_LE(0, playback_time); + if (playback_time >= dot_time) { + cout << "."; + cout.flush(); + dot_time += kDotIntervalInSeconds; + } + } while (playback_time < kPlaybackLoopInSeconds); + cout << "\nSimulated playback time = " << playback_time << " seconds.\n"; + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + EXPECT_NEAR(s.pst_report().seconds_since_first_decrypt() - + s.pst_report().seconds_since_last_decrypt(), + playback_time, kUsageTableTimeTolerance); + + // We must update the usage entry BEFORE sleeping, not after. + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + + cout << "Wait another " << kIdleInSeconds + << " seconds " + "to verify usage table time since playback ended." + << endl; + PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); + + // At this point, this is what we expect: + // idle playback loop idle + // |-----|-------------------------|-----| + // |<--->| = seconds_since_last_decrypt + // |<----------------------------->| = seconds_since_first_decrypt + // |<------------------------------------| = seconds_since_license_received + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// This test class is only used to roll back the wall clock. It is used to +// verify that OEMCrypto's system clock is monotonic. It is should only be +// run on a device that allows an application to set the clock. +class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest { + public: + void SetUp() override { OEMCryptoUsageTableTest::SetUp(); } + + void TearDown() override { + wvutil::TestSleep::ResetRollback(); + OEMCryptoUsageTableTest::TearDown(); + } +}; + +// NOTE: This test needs root access since clock_settime messes with the +// system time in order to verify that OEMCrypto protects against rollbacks in +// usage entries. Therefore, this test is filtered if not run as root. We +// don't test roll-forward protection or instances where the user rolls back +// the time to the last decrypt call since this requires hardware-secure +// clocks to guarantee. +// +// This test overlaps two tests in parallel because they each have several +// seconds of sleeping, then we roll the system clock back, and then we sleep +// some more. +// For the first test, we use entry1. The playback duration is 6 short +// intervals. We play for 3, roll the clock back 2, and then play for 3 more. +// We then sleep until after the allowed playback duration and try to play. If +// OEMCrypto allows the rollback, then there is only 5 intervals, which is +// legal. But if OEMCrypto forbids the rollback, then there is 8 intervals of +// playback, which is not legal. +// +// For the second test, we use entry2. The rental duration is 6 short +// intervals. The times are the same as for entry1, except we do not start +// playback for entry2 until the end. + +// clang-format off +// [--][--][--][--][--][--][--] -- playback or rental limit. +// +// Here's what the system clock sees with rollback: +// [--][--][--] 3 short intervals of playback or sleep +// <------> Rollback 2 short intervals. +// [--][--][--] 3 short intervals of playback or sleep +// [--] 1 short intervals of sleep. +// +// Here's what the system clock sees without rollback: +// [--][--][--] 3 short intervals of playback or sleep +// [--][--][--] 3 short intervals of playback or sleep +// [--][--]X 2 short intervals of sleep. +// +// |<---------------------------->| 8 short intervals from license received +// until pst reports generated. +// clang-format on + +TEST_P(OEMCryptoUsageTableTestWallClock, TimeRollbackPrevention) { + cout << "This test temporarily rolls back the system time in order to " + "verify " + << "that the usage report accounts for the change. After the test, it " + << "rolls the clock back forward." << endl; + constexpr int kRollBackTime = kShortSleep * 2; + constexpr int kPlaybackCount = 3; + constexpr int kTotalTime = kShortSleep * 8; + + LicenseWithUsageEntry entry1; + entry1.license_messages() + .core_response() + .timer_limits.total_playback_duration_seconds = 7 * kShortSleep; + entry1.MakeOfflineAndClose(this); + Session& s1 = entry1.session(); + ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this)); + + LicenseWithUsageEntry entry2; + entry2.license_messages() + .core_response() + .timer_limits.rental_duration_seconds = 7 * kShortSleep; + entry2.MakeOfflineAndClose(this); + Session& s2 = entry2.session(); + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); + + // Start with three short intervals of playback for entry1. + for (int i = 0; i < kPlaybackCount; i++) { + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + wvutil::TestSleep::Sleep(kShortSleep); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + } + + cout << "Rolling the system time back..." << endl; + ASSERT_TRUE(wvutil::TestSleep::RollbackSystemTime(kRollBackTime)); + + // Three more short intervals of playback after the rollback. + for (int i = 0; i < kPlaybackCount; i++) { + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + wvutil::TestSleep::Sleep(kShortSleep); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + } + + // One short interval of sleep to push us past the 6 interval duration. + wvutil::TestSleep::Sleep(2 * kShortSleep); + + // Should not be able to continue playback in entry1. + ASSERT_NO_FATAL_FAILURE( + entry1.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); + // Should not be able to start playback in entry2. + ASSERT_NO_FATAL_FAILURE( + entry2.TestDecryptCTR(true, OEMCrypto_ERROR_KEY_EXPIRED)); + + // Now we look at the usage reports: + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + + ASSERT_NO_FATAL_FAILURE(s1.GenerateReport(entry1.pst())); + wvutil::Unpacked_PST_Report report1 = s1.pst_report(); + EXPECT_EQ(report1.status(), kActive); + EXPECT_GE(report1.seconds_since_license_received(), kTotalTime); + EXPECT_GE(report1.seconds_since_first_decrypt(), kTotalTime); + + ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(entry2.pst())); + wvutil::Unpacked_PST_Report report2 = s2.pst_report(); + EXPECT_EQ(report2.status(), kUnused); + EXPECT_GE(report2.seconds_since_license_received(), kTotalTime); +} + +// Verify that a large PST can be used with usage table entries. +TEST_P(OEMCryptoUsageTableTest, PSTLargeBuffer) { + std::string pst(kMaxPSTLength, 'a'); // A large PST. + LicenseWithUsageEntry entry(pst); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR()); // Should be able to decrypt. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate. + // After deactivate, should not be able to decrypt. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE(s.close()); +} + +// Verify that a usage entry with an invalid session cannot be used. +TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSession) { + std::string pst("pst"); + LicenseWithUsageEntry entry; + entry.license_messages().set_pst(pst); + + entry.session().open(); + ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); + entry.session().close(); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, + OEMCrypto_DeactivateUsageEntry( + entry.session().session_id(), + reinterpret_cast(pst.c_str()), pst.length())); + + entry.session().open(); + ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); + entry.session().close(); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, + OEMCrypto_MoveEntry(entry.session().session_id(), 0)); +} + +// Verify that a usage entry with an invalid session cannot be used. +TEST_P(OEMCryptoUsageTableTest, ReuseUsageEntryWithInvalidSessionAPI17) { + std::string pst("pst"); + LicenseWithUsageEntry entry; + entry.license_messages().set_pst(pst); + + entry.session().open(); + ASSERT_NO_FATAL_FAILURE(entry.session().CreateNewUsageEntry()); + entry.session().close(); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, + OEMCrypto_ReuseUsageEntry(entry.session().session_id(), 0)); +} + +INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoUsageTableTest, + Range(kCoreMessagesAPI, kCurrentAPI + 1)); + +// These tests only work when the license has a core message. +INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableDefragTest, + Values(kCurrentAPI)); + +// These tests only work when the license has a core message. +INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableTestWallClock, + Values(kCurrentAPI)); + +/// @} +} // namespace wvoec \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h new file mode 100644 index 00000000..9ffd3fc9 --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h @@ -0,0 +1,379 @@ +// 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 +#include +#include + +#include "OEMCryptoCENC.h" +#include "log.h" +#include "oemcrypto_basic_test.h" +#include "oemcrypto_license_test.h" +#include "test_sleep.h" + +namespace wvoec { + +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 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& in_buffer, + vector* 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& in_buffer, + vector* 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& in_buffer, + vector* 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& in_buffer, + vector* 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(&fuzzed_structure), + sizeof(fuzzed_structure)); + AppendToFile(file_name, reinterpret_cast(iv), + wvoec::KEY_IV_SIZE); + AppendSeparator(file_name); + AppendToFile(file_name, reinterpret_cast(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(&fuzzed_structure), + sizeof(fuzzed_structure)); + AppendToFile(file_name, reinterpret_cast(iv), + wvoec::KEY_IV_SIZE); + AppendSeparator(file_name); + AppendToFile(file_name, reinterpret_cast(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(&fuzzed_structure), + sizeof(fuzzed_structure)); + AppendToFile(file_name, reinterpret_cast(clear_buffer), + clear_buffer_length); + AppendSeparator(file_name); + AppendToFile(file_name, reinterpret_cast(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(&fuzzed_structure), + sizeof(fuzzed_structure)); + AppendToFile(file_name, reinterpret_cast(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 expected_encrypted; + EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); + vector 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 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 encrypted; + EncryptBuffer(key_index, clear_buffer_, &encrypted); + vector 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 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 expected_signature; + SignBuffer(key_index, clear_buffer_, &expected_signature); + + vector 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 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 signature; + SignBuffer(key_index, clear_buffer_, &signature); + if (alter_data) { + signature[0] ^= 42; + } + if (signature.size() < signature_size) { + signature.resize(signature_size); + } + + vector 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 clear_buffer_; + vector 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& encrypted_usage_header) { + return OEMCrypto_LoadUsageTableHeader(encrypted_usage_header.data(), + encrypted_usage_header.size()); + } +}; + +} // namespace wvoec + +#endif // CDM_OEMCRYPTO_USAGE_TABLE_TEST_ \ No newline at end of file From ea3d31987931204be8df568393a15b3dadec1b01 Mon Sep 17 00:00:00 2001 From: "John \"Juce\" Bruce" Date: Mon, 27 Mar 2023 19:41:13 -0700 Subject: [PATCH 07/20] Add CHANGELOG entry for OPK v17.1.1 Merge from Widevine repo of http://go/wvgerrit/169062 Bug: 269670984 Merged from https://widevine-internal-review.googlesource.com/167378 Merged from https://widevine-internal-review.googlesource.com/167369 Change-Id: I309aff7aa0e7f662893f20e54975009c427a525f --- libwvdrmengine/oemcrypto/CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libwvdrmengine/oemcrypto/CHANGELOG.md b/libwvdrmengine/oemcrypto/CHANGELOG.md index eef79c27..6ce869a5 100644 --- a/libwvdrmengine/oemcrypto/CHANGELOG.md +++ b/libwvdrmengine/oemcrypto/CHANGELOG.md @@ -163,6 +163,19 @@ OS. due to an edge case in the implementation of WPTI_GenerateRandomCertificateKeyPair(). +## [OPK Version 17.1.1][v17.1+opk-v17.1.1] + +This release only affects OPK and not any other part of OEMCrypto. 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. + ## [Version 17.1][v17.1] This release contains a major change to the build process for the OP-TEE port, @@ -324,3 +337,4 @@ 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 From 54e6b3d45d8f6b5043661aa9e328895021f5c644 Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Mon, 27 Mar 2023 19:41:19 -0700 Subject: [PATCH 08/20] Small changes to refactored unit tests Merge from Widevine repo of http://go/wvgerrit/169064 This CL should cleanup some minor issues that existed after the initial CLs refactoring the unit tests went in. The issues fixed should be: 1) duplicate decrypt tests 2) decrypt tests added to be run 3) removed unecessary header files 4) refactored some provisioning tests that I had previously overlooked Bug: 253779846 Merged from https://widevine-internal-review.googlesource.com/167537 Change-Id: Ic474fbcf69a08c0482b5e74d0c80be2cd16702d8 --- libwvdrmengine/oemcrypto/test/common.mk | 1 + .../oemcrypto/test/oemcrypto_basic_test.h | 2 +- .../oemcrypto/test/oemcrypto_decrypt_test.cpp | 6 +- .../oemcrypto/test/oemcrypto_decrypt_test.h | 3 - .../oemcrypto/test/oemcrypto_license_test.cpp | 5 +- .../oemcrypto/test/oemcrypto_license_test.h | 2 - .../test/oemcrypto_provisioning_test.cpp | 248 ++- .../test/oemcrypto_provisioning_test.h | 78 +- .../oemcrypto/test/oemcrypto_test.cpp | 1417 +---------------- 9 files changed, 325 insertions(+), 1437 deletions(-) diff --git a/libwvdrmengine/oemcrypto/test/common.mk b/libwvdrmengine/oemcrypto/test/common.mk index dba1ee3b..ce7a3c86 100644 --- a/libwvdrmengine/oemcrypto/test/common.mk +++ b/libwvdrmengine/oemcrypto/test/common.mk @@ -17,6 +17,7 @@ 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 \ diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_basic_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_basic_test.h index abe0f35a..2cbe235b 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_basic_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_basic_test.h @@ -12,8 +12,8 @@ #include #include "OEMCryptoCENC.h" -#include "oec_session_util.h" #include "oemcrypto_session_tests_helper.h" + namespace wvoec { const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value); diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp index 74945cdb..22c810c0 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp @@ -4,11 +4,7 @@ // #include "oemcrypto_decrypt_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::Combine; diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h index 4e49e2b9..9f9b5005 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h @@ -12,11 +12,8 @@ #include "OEMCryptoCENC.h" #include "log.h" #include "oec_decrypt_fallback_chain.h" -#include "oec_session_util.h" #include "oemcrypto_basic_test.h" #include "oemcrypto_license_test.h" -#include "oemcrypto_resource_test.h" -#include "oemcrypto_session_tests_helper.h" namespace wvoec { diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp index 9b8f6cf0..25cf89be 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp @@ -4,10 +4,7 @@ // #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" namespace wvoec { diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h index d07ff2b1..fd59d153 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h @@ -12,11 +12,9 @@ #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; diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp index fb39fb02..ea687eea 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp @@ -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,250 @@ 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 message(128, 0); + std::vector 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) { + 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())); +} + /// @} } // namespace wvoec \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h index 4b595703..2233842f 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h @@ -8,11 +8,12 @@ #define CDM_OEMCRYPTO_PROVISIONING_TEST_ #include -#include "oemcrypto_basic_test.h" #include "OEMCryptoCENC.h" -#include "oec_session_util.h" -#include "oemcrypto_session_tests_helper.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 @@ -39,6 +40,77 @@ class OEMCryptoProv30Test : public OEMCryptoClientTest {}; // This class is for tests that have boot certificate chain instead of a keybox. class OEMCryptoProv40Test : public OEMCryptoClientTest {}; +// +// Certificate Root of Trust Tests +// +class OEMCryptoLoadsCertificate : public OEMCryptoSessionTestKeyboxTest { + protected: + void TestPrepareProvisioningRequestForHugeBufferLengths( + const std::function 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 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 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 #endif // CDM_OEMCRYPTO_PROVISIONING_TEST_ \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 23c0040e..54140c52 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -77,6 +77,7 @@ #include "oemcrypto_corpus_generator_helper.h" #include "oemcrypto_fuzz_structs.h" #include "oemcrypto_license_test.h" +#include "oemcrypto_provisioning_test.h" #include "oemcrypto_resource_test.h" #include "oemcrypto_session_tests_helper.h" #include "oemcrypto_types.h" @@ -654,88 +655,6 @@ TEST_F(OEMCryptoMemoryLicenseTest, /// @} -/// @addtogroup decrypt -/// @{ - -// 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 key_handle; - OEMCryptoResult sts = GetKeyHandleIntoVector( - session_.session_id(), reinterpret_cast(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 in_buffer(256); - vector 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); - } -} - -/// @} - /// @addtogroup entitle /// @{ @@ -1059,145 +978,6 @@ TEST_P(OEMCryptoEntitlementLicenseTest, ReassociateEntitledKeySessionAPI17) { /// @} -/// @addtogroup decrypt -/// @{ - -// '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 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 in_buffer(256); - vector 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 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 in_buffer(256); - vector 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 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 in_buffer(256); - vector 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 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 in_buffer(256); - vector 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); -} - -/// @} - /// @addtogroup security /// @{ @@ -1593,67 +1373,6 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoLicenseOverflowTest, /// @} -/// @addtogroup decrypt -/// @{ - -// 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 { - 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); - } - } -}; - -TEST_P(OEMCryptoSessionTestDecryptWithHDCP, DecryptAPI09) { - // Test parameterized by HDCP version. - DecryptWithHDCP(static_cast(GetParam())); -} -INSTANTIATE_TEST_SUITE_P(TestHDCP, OEMCryptoSessionTestDecryptWithHDCP, - Range(1, 6)); - -/// @} - /// @addtogroup cas /// @{ @@ -1886,1140 +1605,6 @@ INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoRefreshTestAPI16, /// @} -/// @addtogroup decrypt -/// @{ - -// 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(&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(&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()); -} - -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 encrypted_buffer; - std::vector clear_buffer; // OEMCrypto store clear output here. - std::vector truth_buffer; // Truth data for clear text. - OEMCrypto_SampleDescription description; - std::vector 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> { - 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 subsample_sizes) { - // This is just sugar for having one sample with the given subsamples in it. - SetSampleSizes({subsample_sizes}); - } - - void SetSampleSizes(std::vector> 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& 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(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(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(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(&hash), - sizeof(hash)); - } - - // Build an array of just the sample descriptions. - std::vector 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 samples_; - std::vector key_handle_; -}; - -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 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 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 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 subsample_sizes; - subsample_sizes.push_back({100000, 100000}); - SetSubsampleSizes(subsample_sizes); - LoadLicense(); - MakeBuffers(); - EncryptData(); - // Build an array of just the sample descriptions. - std::vector 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(kCurrentAPI - 2, kCurrentAPI + 1)); - -/// @} - -/// @addtogroup provision -/// @{ - -// -// Certificate Root of Trust Tests -// -class OEMCryptoLoadsCertificate : public OEMCryptoSessionTestKeyboxTest { - protected: - void TestPrepareProvisioningRequestForHugeBufferLengths( - const std::function 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 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 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); - } -}; - -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 message(128, 0); - std::vector 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) { - 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())); -} - -/// @} - /// @addtogroup security /// @{ From d3183f504e777ecaa8e11db1954f7f8193a219c9 Mon Sep 17 00:00:00 2001 From: "John \"Juce\" Bruce" Date: Mon, 27 Mar 2023 19:41:25 -0700 Subject: [PATCH 09/20] Remove V17 backwards-compatibility decrypt functions Merge from Widevine repo of http://go/wvgerrit/169066 Now that we only have to support the v18 API, we can drop the v17 versions of these functions. For SelectKey, the new function fully replaces it, so it has been removed. For the other functions, the v18 functions were calling the v17 functions previously. Now, they have been rolled together. These functions were not actually deprecated in the OEMCryptoCENC.h header to allow OPK's serialization generator to still support them for backwards-compatibility. Now that they are gone, this patch also deprecates the functions. Bug: 240995221 Merged from https://widevine-internal-review.googlesource.com/167338 Change-Id: I10261142121d4de8c96e2cd5fac570f7b536a82e --- .../oemcrypto/include/OEMCryptoCENC.h | 66 ++----------------- 1 file changed, 6 insertions(+), 60 deletions(-) diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index 2ceb4315..c0e72abd 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -5594,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, @@ -5613,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, @@ -5632,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, @@ -5653,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, @@ -5674,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, @@ -5697,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, From 64521717d85427c75e84e2c9a795f874b95189d7 Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Mon, 27 Mar 2023 19:41:31 -0700 Subject: [PATCH 10/20] Use GTEST_SKIP to skip prov 3.0 tests Merge from Widevine repo of http://go/wvgerrit/169068 We want to transition to using GTEST_SKIP to skip unit tests instead of modifying the GTEST_FILTER variable. This does so for provisioning 3.0 tests. Bug: 251240681 Merged from https://widevine-internal-review.googlesource.com/167498 Change-Id: I997e1051f3bd7925bc69cf1b269a5bbbae8031b7 --- libwvdrmengine/oemcrypto/test/oec_device_features.cpp | 2 -- .../oemcrypto/test/oemcrypto_provisioning_test.cpp | 3 +++ .../oemcrypto/test/oemcrypto_provisioning_test.h | 9 ++++++++- libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp | 6 ++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp index 0660ca53..ca471a85 100644 --- a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp @@ -164,8 +164,6 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { 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*"); diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp index ea687eea..1c0ddd27 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp @@ -768,6 +768,9 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4_API16) { // 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_); diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h index 2233842f..48e5e8c8 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h @@ -35,7 +35,14 @@ 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 {}; diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 54140c52..c2a7a9f7 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -1739,6 +1739,9 @@ TEST_F(OEMCryptoLoadsCertificate, TEST_F( OEMCryptoLoadsCertificate, OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncMessageKeyLengthProv30) { + if (global_features.provisioning_method != OEMCrypto_OEMCertificate) { + GTEST_SKIP() << "Test for Prov 3.0 devices only."; + } TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( [](size_t response_message_length, ProvisioningRoundTrip* provisioning_messages) { @@ -1752,6 +1755,9 @@ TEST_F( TEST_F( OEMCryptoLoadsCertificate, OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncMessageKeyOffsetProv30) { + if (global_features.provisioning_method != OEMCrypto_OEMCertificate) { + GTEST_SKIP() << "Test for Prov 3.0 devices only."; + } TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( [](size_t response_message_length, ProvisioningRoundTrip* provisioning_messages) { From 803617b7831c1a105843f6349935837f55322fe0 Mon Sep 17 00:00:00 2001 From: Matt Feddersen Date: Mon, 27 Mar 2023 19:41:34 -0700 Subject: [PATCH 11/20] Document RSA keypair issue on OP-TEE 64-bit Merge from Widevine repo of http://go/wvgerrit/169069 Merged from https://widevine-internal-review.googlesource.com/167604 Bug: 275264353 Test: luci tests Change-Id: Ib77b2f7d3855779dd6d97696d6066ad361e6e416 --- libwvdrmengine/oemcrypto/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libwvdrmengine/oemcrypto/CHANGELOG.md b/libwvdrmengine/oemcrypto/CHANGELOG.md index 6ce869a5..ee841319 100644 --- a/libwvdrmengine/oemcrypto/CHANGELOG.md +++ b/libwvdrmengine/oemcrypto/CHANGELOG.md @@ -102,7 +102,11 @@ OEMCrypto_BuildInformation(), the `WTPI_BUILD_INFO` macro is no longer required. 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. + 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 From d7ee89bab0ce0fdeb3ad5fba0ea724651cd6de40 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Mon, 27 Mar 2023 19:41:38 -0700 Subject: [PATCH 12/20] Filter Cast Reciver tests Merge from Widevine repo of http://go/wvgerrit/169070 This turns on the cast receiver tests for any device that claims to support this feature. Previously, we had to explicitly request these tests on the command line. But since they do not pass for Prov 4.0, we fitler them out in this case and reference a bug tracking that work. We also switch to using GTEST_SKIP to skip the tests instead of modifying the GTEST_FILTER. Bug: 251240681 Bug: 269310676 Bug: 259455058 Bug: 259454969 Merged from https://widevine-internal-review.googlesource.com/166497 Change-Id: I1bcd749243a474b3f638547aa43c2805e86731af --- libwvdrmengine/cdm/core/test/test_base.cpp | 8 +++++++- libwvdrmengine/oemcrypto/test/oec_device_features.cpp | 2 -- libwvdrmengine/oemcrypto/test/oemcrypto_security_test.cpp | 2 ++ libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp | 7 +++++++ libwvdrmengine/oemcrypto/test/oemcrypto_test_main.cpp | 8 +++++++- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/libwvdrmengine/cdm/core/test/test_base.cpp b/libwvdrmengine/cdm/core/test/test_base.cpp index 192d87ff..4d3365e6 100644 --- a/libwvdrmengine/cdm/core/test/test_base.cpp +++ b/libwvdrmengine/cdm/core/test/test_base.cpp @@ -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) { diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp index ca471a85..5084cc30 100644 --- a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp @@ -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,7 +160,6 @@ 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_BootCertificateChain) diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_security_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_security_test.cpp index 15be27f7..dc39198a 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_security_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_security_test.cpp @@ -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_)); diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index c2a7a9f7..c8604f14 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -2310,6 +2310,13 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1) { // those devices. class OEMCryptoCastReceiverTest : public OEMCryptoLoadsCertificateAlternates { protected: + void SetUp() override { + OEMCryptoLoadsCertificateAlternates::SetUp(); + if (!global_features.cast_receiver) { + GTEST_SKIP() << "OEMCrypto does not support CAST Receiver functionality"; + } + } + vector encode(uint8_t type, const vector& substring) { vector result; result.push_back(type); diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test_main.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test_main.cpp index 8a702cf4..0779b11c 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test_main.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test_main.cpp @@ -46,7 +46,13 @@ int main(int argc, char** argv) { } wvutil::g_cutoff = static_cast(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 From 8dd8fc5a799ae2979650ad09656bec98e2585d8c Mon Sep 17 00:00:00 2001 From: "John \"Juce\" Bruce" Date: Mon, 27 Mar 2023 19:41:47 -0700 Subject: [PATCH 13/20] Update CHANGELOG for late-breaking OPK v17.1.1 changes Merge from Widevine repo of http://go/wvgerrit/169073 Due to the late-breaking maximum_minor_version change, we had to revise the CHANGELOG on the release branch. This patch ports this change to the development branches. Bug: 275264353 Test: luci tests Change-Id: I46a18bd05ad1ae2afc766eaaf39c563f82f4eeea --- libwvdrmengine/oemcrypto/CHANGELOG.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/libwvdrmengine/oemcrypto/CHANGELOG.md b/libwvdrmengine/oemcrypto/CHANGELOG.md index ee841319..564ed078 100644 --- a/libwvdrmengine/oemcrypto/CHANGELOG.md +++ b/libwvdrmengine/oemcrypto/CHANGELOG.md @@ -169,16 +169,18 @@ OS. ## [OPK Version 17.1.1][v17.1+opk-v17.1.1] -This release only affects OPK and not any other part of OEMCrypto. 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 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] From 2f45350921bf90b38f4c4ac876fb1d8d4b4fdf4c Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Mon, 27 Mar 2023 19:41:50 -0700 Subject: [PATCH 14/20] Update oemcrypto unit tests version number Merge from Widevine repo of http://go/wvgerrit/169074 And update a few scripts that check for version number. Merged from https://widevine-internal-review.googlesource.com/167657 Bug: 275264353 Test: luci tests Change-Id: Ic3c16323e993075c9bfe206fc73bf82c0e67f65b --- libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h | 2 +- .../oemcrypto/test/oemcrypto_basic_test.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index c0e72abd..87eb1f60 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -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 diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_basic_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_basic_test.cpp index f9cf44a4..719af194 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_basic_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_basic_test.cpp @@ -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(ODK_MAJOR_VERSION)); OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel(); EXPECT_GT(level, OEMCrypto_Level_Unknown); EXPECT_LE(level, OEMCrypto_Level3); From 6c834acc50424089adcc9351e760a653157912b5 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Mon, 27 Mar 2023 19:41:53 -0700 Subject: [PATCH 15/20] Update test updates and known issues to ChangeLog Merge from Widevine repo of http://go/wvgerrit/169075 Merged from https://widevine-internal-review.googlesource.com/167677 Bug: 275264353 Test: luci tests Change-Id: I247e5fd73ad53d526a662badbca43520fed5f1bd --- libwvdrmengine/oemcrypto/CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/libwvdrmengine/oemcrypto/CHANGELOG.md b/libwvdrmengine/oemcrypto/CHANGELOG.md index 564ed078..c5535518 100644 --- a/libwvdrmengine/oemcrypto/CHANGELOG.md +++ b/libwvdrmengine/oemcrypto/CHANGELOG.md @@ -18,6 +18,24 @@ 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 @@ -166,6 +184,9 @@ OS. 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] @@ -344,3 +365,4 @@ Public release for OEMCrypto API and ODK library version 16.4. [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 From 225a3e50ede934940980d6ce39894892444eec55 Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Mon, 27 Mar 2023 19:41:56 -0700 Subject: [PATCH 16/20] Use GTEST_SKIP to skip prov40 tests Merge from Widevine repo of http://go/wvgerrit/169076 We want to transition to using GTEST_SKIP to skip unit tests instead of modifying the GTEST_FILTER variable. This does so for provisioning 4.0 tests. Bug: 251240681 Merged from https://widevine-internal-review.googlesource.com/167497 Change-Id: I65a879fba24b199bd115980bdd556c123fcc1cdc --- libwvdrmengine/oemcrypto/test/oec_device_features.cpp | 2 -- .../oemcrypto/test/oemcrypto_provisioning_test.h | 9 ++++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp index 5084cc30..28614f20 100644 --- a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp @@ -162,8 +162,6 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); if (!supports_cas) FilterOut(&filter, "*CasOnly*"); if (derive_key_method == NO_METHOD) FilterOut(&filter, "*SessionTest*"); - 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*"); diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h index 48e5e8c8..ad47cbaa 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h @@ -45,7 +45,14 @@ class OEMCryptoProv30Test : public OEMCryptoClientTest { }; // 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 From f83698a164db2e6000c3957ad81a51a222ea09e0 Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Mon, 27 Mar 2023 19:42:06 -0700 Subject: [PATCH 17/20] Refactor missed provisioning and renewal tests Merge from Widevine repo of http://go/wvgerrit/169079 Bug: 253779846 Merged from https://widevine-internal-review.googlesource.com/167738 Change-Id: If8fc484f02fc1544977f1fb3a5fe1fa42d7367d7 --- .../oemcrypto/test/oemcrypto_license_test.cpp | 167 ++++++++ .../oemcrypto/test/oemcrypto_license_test.h | 46 +++ .../test/oemcrypto_provisioning_test.cpp | 190 +++++++++ .../test/oemcrypto_provisioning_test.h | 1 + .../oemcrypto/test/oemcrypto_test.cpp | 368 ------------------ .../test/oemcrypto_usage_table_test.h | 42 -- 6 files changed, 404 insertions(+), 410 deletions(-) diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp index 25cf89be..1031cf9a 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp @@ -6,6 +6,9 @@ #include "oemcrypto_license_test.h" #include "platform.h" +#include "test_sleep.h" + +using ::testing::Range; namespace wvoec { @@ -800,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 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 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(kCurrentAPI - 1, kCurrentAPI + 1)); + +// These tests only work when the license has a core message. +INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoRefreshTestAPI16, + Range(kCoreMessagesAPI, kCurrentAPI + 1)); + /// @} } // namespace wvoec \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h index fd59d153..0d88c6e6 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h @@ -359,6 +359,52 @@ class LicenseWithUsageEntry { 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_ \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp index 1c0ddd27..a091abbc 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp @@ -868,5 +868,195 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionLargeBuffer) { 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) { + 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> sessions; + std::vector> 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(new Session())); + licenses.push_back(std::unique_ptr( + 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 public_key(buffer_size); + size_t public_key_size = buffer_size; + std::vector public_key_signature(buffer_size); + size_t public_key_signature_size = buffer_size; + std::vector 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 \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h index ad47cbaa..03942a01 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h @@ -10,6 +10,7 @@ #include #include "OEMCryptoCENC.h" +#include "oec_extra_test_keys.h" #include "oemcrypto_basic_test.h" #include "oemcrypto_license_test.h" #include "oemcrypto_resource_test.h" diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index c8604f14..8304fe2f 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -1432,179 +1432,6 @@ INSTANTIATE_TEST_SUITE_P(TestHDCP, OEMCryptoSessionTestLoadCasKeysWithHDCP, Range(1, 6)); /// @} -/// @addtogroup renewal -/// @{ - -// -// Load, Refresh Keys Test -// - -// This class is for the refresh tests that should only be run on licenses with -// a core message. -class OEMCryptoRefreshTestAPI16 : public OEMCryptoRefreshTest {}; - -// 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 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 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(kCurrentAPI - 1, kCurrentAPI + 1)); - -// These tests only work when the license has a core message. -INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoRefreshTestAPI16, - Range(kCoreMessagesAPI, kCurrentAPI + 1)); - -/// @} - /// @addtogroup security /// @{ @@ -1770,201 +1597,6 @@ TEST_F( /// @} -/// @addtogroup provision -/// @{ - -// 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) { - 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> sessions; - std::vector> 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(new Session())); - licenses.push_back(std::unique_ptr( - 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 public_key(buffer_size); - size_t public_key_size = buffer_size; - std::vector public_key_signature(buffer_size); - size_t public_key_signature_size = buffer_size; - std::vector 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(); -} - -/// @} - /// @addtogroup security /// @{ diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h index 9ffd3fc9..ce5d7ab9 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h @@ -19,48 +19,6 @@ namespace wvoec { -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 testing the generic crypto functionality. class OEMCryptoGenericCryptoTest : public OEMCryptoRefreshTest { protected: From 0972c59fc079962baa2766a82fe4afcfaf895d4d Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Mon, 27 Mar 2023 19:42:09 -0700 Subject: [PATCH 18/20] Filter CAS tests Merge from Widevine repo of http://go/wvgerrit/169080 We want to transition to using GTEST_SKIP to skip unit tests instead of modifying the GTEST_FILTER variable. This does so for tests that require CAS support. Bug: 251240681 Merged from https://widevine-internal-review.googlesource.com/167739 Change-Id: Ifb971bf01e2c21fe672bbe4bfa15c797456256ef --- .../oemcrypto/test/oec_device_features.cpp | 1 - .../oemcrypto/test/oemcrypto_test.cpp | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp index 28614f20..788a7959 100644 --- a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp @@ -160,7 +160,6 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { provisioning_method == OEMCrypto_BootCertificateChain) FilterOut(&filter, "OEMCryptoLoadsCert*"); if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); - if (!supports_cas) FilterOut(&filter, "*CasOnly*"); if (derive_key_method == NO_METHOD) FilterOut(&filter, "*SessionTest*"); if (!supports_rsa_3072) FilterOut(&filter, "*RSAKey3072*"); if (api_version < 17) FilterOut(&filter, "*API17*"); diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 8304fe2f..5dd260a4 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -294,6 +294,9 @@ TEST_P(OEMCryptoEntitlementLicenseTest, LoadEntitlementKeysAPI17) { } TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysAPI17) { + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } LoadEntitlementLicense(); uint32_t key_session_id = 0; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( @@ -344,6 +347,9 @@ TEST_P(OEMCryptoEntitlementLicenseTest, */ TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysNoEntitlementKeysAPI17) { + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } license_messages_.set_license_type(OEMCrypto_EntitlementLicense); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -380,6 +386,9 @@ TEST_P(OEMCryptoEntitlementLicenseTest, TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysWrongEntitlementKeysAPI17) { + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } LoadEntitlementLicense(); uint32_t key_session_id = 0; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( @@ -414,6 +423,9 @@ TEST_P(OEMCryptoEntitlementLicenseTest, TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysWrongEntitledKeySessionAPI17) { + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } LoadEntitlementLicense(); uint32_t key_session_id = 0; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( @@ -452,6 +464,9 @@ TEST_P(OEMCryptoEntitlementLicenseTest, TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysOemcryptoSessionAPI17) { + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } LoadEntitlementLicense(); uint32_t key_session_id = 0; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( @@ -1425,6 +1440,9 @@ class OEMCryptoSessionTestLoadCasKeysWithHDCP : public OEMCryptoSessionTests, }; TEST_P(OEMCryptoSessionTestLoadCasKeysWithHDCP, CasOnlyLoadCasKeysAPI17) { + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } // Test parameterized by HDCP version. LoadCasKeysWithHDCP(static_cast(GetParam())); } From f2cc88c0f7ca5e74c87d94fca9dea786469b3d54 Mon Sep 17 00:00:00 2001 From: Ian Benz Date: Mon, 27 Mar 2023 19:42:29 -0700 Subject: [PATCH 19/20] Add MemorySanitizer support for opk_ta tests Merge from Widevine repo of http://go/wvgerrit/169087 - Add opk_ta_msan platform and CI script - Fix uninitialized memory reads identified by MemorySanitizer Merged from https://widevine-internal-review.googlesource.com/163237 Merged from https://widevine-internal-review.googlesource.com/168058 Bug: 260089091 Change-Id: If33f061631da92a41b9c507843772f104b76515f --- libwvdrmengine/cdm/core/include/cdm_session.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 553f69a6..b6e7152a 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -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_; From 6d494fa76c185241974b0f00eaa26782a5fc43b4 Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Mon, 27 Mar 2023 19:42:35 -0700 Subject: [PATCH 20/20] Filter RSA 3072 tests Merge from Widevine repo of http://go/wvgerrit/169089 We want to transition to using GTEST_SKIP to skip unit tests instead of modifying the GTEST_FILTER variable. This does so for tests that require RSA 3072 support. Note: I think part of this CL got lost in go/wvgerrit/167740, so this is adding the rest in. Bug: 251240681 Merged from https://widevine-internal-review.googlesource.com/168237 Change-Id: I3002f705f7e3f4b38d0e5efef355e5c3f3529218 --- libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp index a091abbc..88182c3a 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp @@ -898,6 +898,9 @@ class OEMCryptoLoadsCertVariousKeys : public OEMCryptoLoadsCertificate { // 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)); }