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_; 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/CHANGELOG.md b/libwvdrmengine/oemcrypto/CHANGELOG.md index 11d38aa0..c5535518 100644 --- a/libwvdrmengine/oemcrypto/CHANGELOG.md +++ b/libwvdrmengine/oemcrypto/CHANGELOG.md @@ -2,6 +2,207 @@ [TOC] +## [Version 18.1][v18.1] + +OEMCrypto V18.0 consisted of header files only. This release includes tests and +OPK. There have been minor API changes since v18.0, so the version number has +been bumped to 18.1 + +In general, new v18 OEMCrypto features have been implemented in the OPK; since +those have been covered already in the published v18 headers, they will not be +discussed in detail here. + +This OPK release includes OEMCrypto v18 changes outlined in the document "WV +Modular DRM Version 18.1 Delta". In addition, quite a few OPK-specific changes +have been added since the last release. Major changes are described in more +detail below in individual sections, followed by a consolidated list of minor +changes. + +### OEMCrypto Unit Tests + +The unit tests have been updated to test all v18.1 functionality. + +We have also refactored the unit tests into several files grouped by category. + +Previously, the unit tests modifed the `GTEST_FILTER` in the file +`oec_device_features.cpp` in order to skip tests of functionality. For example +keybox tests are skipped for devices that use Provisioning 4.0. We have begun +replacing the modification of the `GTEST_FILTER` with the GTest macro +`GTEST_SKIP`. Previously, skipped tests would not show up on the list of running +tests in stdout. Now, any skipped test will start to run, and then a message +will explain why it is being skipped. A list of skipped tests will be listed to +stdout at the end of the test run. + +The seed corpus for the oemcrypto fuzz tests has been updated using the updated +unit tests. + +### REE-side hooks + +In oemcrypto/opk/serialization/ree/GEN_oemcrypto_api.c, new ifdef checks have +been added in OEMCrypto_Initialize, OEMCrypto_Terminate, OEMCrypto_DecryptCENC, +and OEMCrypto_LoadLicense. Depending on the macros, liboemcrypto can be compiled +with hooks that execute before, after, or instead of the listed OEMCrypto +functions (or some combination of the three). If a hook macro is defined, the +default behavior is to call that hook function instead of calling into the TEE +-- to do both, at least one more macro must be defined. For more detail, see +GEN_oemcrypto_api.c directly. + +For example, defining the macro `OPK_PRE_HOOK_OEMCRYPTO_DECRYPTCENC` will +execute `OPK_PreHook_OEMCrypto_DecryptCENC()` instead of calling into the TEE. +The same arguments will be passed to this function, which needs a custom +implementation. One use case for this is custom decryption hardware that is +accessible from the REE and can accept opaque decryption key handles intended +for the TEE. The above macro can be used to execute the hook function instead of +calling the TEE, and the hook function can pass the key handle and encrypted +content to the decrypt hardware directly, bypassing the TEE. + +Another example is in `OEMCrypto_Initialize()`. Defining the macro +`OPK_PRE_HOOK_OEMCRYPTO_INITIALIZE` will execute a call to +`OPK_Prehook_OEMCrypto_Initialize()` instead of calling into the TEE. Defining +another macro `OPK_HOOK_AND_USE_TA_OEMCRYPTO_INITIALIZE` will include the call +to the TEE after the pre-hook. The implementer is free to define custom REE-side +initialization logic, and continue to let the TA finish its initialization as +well. + +One more use case: `OEMCrypto_LoadLicense()`. Some implementers have requested +the ability to call a custom function after LoadLicense() returns in order to +parse the license response and control output restrictions accordingly from the +REE (HDCP, watermarking, etc). To do this, define +`OPK_POST_HOOK_OEMCRYPTO_LOAD_LICENSE` and +`OPK_HOOK_AND_USE_TA_OEMCRYPTO_LOAD_LICENSE` at build time and provide +a function implementation for `OPK_Posthook_OEMCrypto_LoadLicense()`. The post +hook function will execute after `OEMCrypto_LoadLicense()` comes back from the +TEE and will use the same function arguments. + +If no macros are defined, there is no change to GEN_oemcrypto_api.c and all +OEMCrypto calls will be forwarded to the TEE without hooks. + +### Configuration macros + +Previously, a platform-specific port needed to define hard coded values in the +implementation of `wtpi_config_interface.h`, as well as macro definitions in +`wtpi_config_macros.h`. These values defined which features were available in +the build, but they were difficult to modify for a new build, and it was often +unclear what configuration values were enabled for a particular build. + +These have been replaced with a single header file consisting of only macro +definitions. For a full list of these configuration macros with default values, +see oemcrypto/opk/oemcrypto_ta/wtpi_reference/config/default.h. + +Each platform-specific port needs to provide configuration values in a file +called `opk_config.h`. The OPK code looks for this file at compilation time. To +provide flexibility at build time, these macro definitions should be `#ifndef` +checked. This way, macros can be defined in the cflags to override the default +values if desired (eg build a version that only changes the provisioning method +by setting the `OPK_CONFIG_PROVISIONING_METHOD` macro in the cflags). The OP-TEE, +Trusty, and Linux reference ports all have `opk_config.h` files that can be used +as examples. + +As part of the OEMCrypto v18 changes, OEMCrypto_BuildInformation() now outputs +JSON-formatted text. If the OPK is built in debug mode, the build information +will also include all of the configuration macro values as a new JSON field. +The intent is to improve the debugging experience by providing as much +configuration information as possible. With the changes to +OEMCrypto_BuildInformation(), the `WTPI_BUILD_INFO` macro is no longer required. + +### OP-TEE port changes + +- Add implementations for provisioning 4.0 WTPI functions. This requires the + third party library open-dice. +- Pre-allocate crypto handles for DecryptCENC. Since this is + a performance-sensitive path, allocate once up front instead of per + DecryptCENC call. +- Reduce compiler warnings. +- Add support for RSA CAST receiver signing. +- Add QEMUv8 target. +- Move der_parse and related files into the wtpi_impl directory. +- Bugfix: Randomly generated ECC key in v17.1 sometimes was smaller than the + expected keysize. Fixed to include leading zeroes if needed. +- Bugfix: WPTI_GenerateRandomCertificateKeyPair() was implemented incorrectly. + It did not return the correct minimum size, used the wrong mbedtls key type, + and did not free allocated resources. Fixed all three issues. Please note + that on 64-bit targets, WPTI_GenerateRandomCertificateKeyPair() will exhaust + the default memory pool that OP-TEE uses for mbedtls. We suggest increasing + MPI_MEMPOOL_SIZE from 12k to 14k in optee_os/lib/libutee/tee_api_arith_mpi.c + to avoid this. + +### Trusty port changes + +In v17.1, the Trusty port did not compile against the OPK. This has been fixed +in v18, with the code moved one directory deeper to a folder named `reference`. + +Implementers looking to create a port based on this reference code are +encouraged to copy the `reference` folder and modify it, instead of modifying +the existing code directly. + +The Trusty port still requires a full download of AOSP Trusty, and must be built +into the Trusty kernel as a user module. In the future we plan to support +standalone TA builds that can be compiled or sideloaded into Trusty OS. + +At this time, there is still no easy way to test builds of the Trusty port +without hardware. QEMU support is planned for a future release. + +### Linux testing port + +A new folder has been created under `oemcrypto/opk/ports` for an implementation +that can run on Linux. Please note that this is a testing-only insecure +implementation, as all code executes in the REE. + +The oemcrypto_unittests and wtpi_unittests applications are the same. The "TA" +is a Linux application that spins a while loop until it receives a message, then +executes that call. The transport interface between liboemcrypto and this fake +TA uses Linux shmem APIs to pass messages back and forth. + +Again, this is not to be used in any kind of production release. The fake TA is +only intended as an easier way to test the REE-TEE code path without a trusted +OS. + +### Other changes + +- Provisioning 4 WTPI functions moved to wtpi_provisioning_4_interface.h. Some + new functions added such as WTPI_GetSignedCsrPayload() +- Provisioning 4 WTPI tests improved to test correctness of BCC and CoseSign1 + payloads, but requires new third party library COSE-C to do so. +- WTPI unit tests can be skipped based on configuration values. Currently + provisioning 4 WTPI functions are skipped if the configured provisioning + method is different. +- Calling an OEMCrypto function with shared buffer arguments could fail if the + underlying buffer was larger than the available shared memory. This would + occur before reaching the TEE in the serialization code, and would return the + default OEMCrypto_ERROR_UNKNOWN_FAILURE error. Now it returns + OEMCrypto_ERROR_BUFFER_TOO_LARGE. +- Fixed a memory leak in the asymmetric key table management code, where key + handles were not freed at session termination. +- Fixed a bug in the serialization code, where `uint8_t iv[16]` parameters were + not passed correctly through to the TEE, eg in AES operations. A NULL input + would always show up as a valid pointer to the TEE. This is now fixed and NULL + inputs show up as NULL in the TEE. + +### Known bugs + +- In the OP-TEE port, WTPI unit tests that use randomly generated ECC keys + occasionally (1/100) fail. The exact cause is unknown, but it appears to be + due to an edge case in the implementation of + WPTI_GenerateRandomCertificateKeyPair(). +- The OPK does not support Cast Receiver functionality when using Provisioning + 4.0. +- The OPK does not yet support MediaCAS functionality. + +## [OPK Version 17.1.1][v17.1+opk-v17.1.1] + +This release fixes a flaw in the OPK code that could allow content that requires +HDCP 2 to output over a display connection that only supports HDCP 1. This bug +would only be triggered if the WTPI implementation reports the minor version +number of HDCP 1 connections. If your implementation of +`WTPI_CurrentHDCPCapability()` ever returns `HDCP_V1_0`, `HDCP_V1_1`, +`HDCP_V1_2`, `HDCP_V1_3`, or `HDCP_V1_4`, your device is vulnerable and you +should take this patch urgently. If your implementation of +`WTPI_CurrentHDCPCapability()` only ever returns `HDCP_V1` for HDCP 1 +connections or does not support HDCP 1, then your device is not affected. You +will not need to change your WTPI implementation to apply this patch. + +This release also fixes the value of `maximum_minor_version` in ODK. + ## [Version 17.1][v17.1] This release contains a major change to the build process for the OP-TEE port, @@ -163,3 +364,5 @@ Public release for OEMCrypto API and ODK library version 16.4. [v17+test-updates+opk]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v17+test-updates+opk [v17+test-updates+opk+mk]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v17+test-updates+opk+mk [v17.1]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v17.1 +[v17.1+opk-v17.1.1]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v17.1+opk-v17.1.1 +[v18.1]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v18.1 diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index f0f0144f..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 @@ -1037,6 +1037,10 @@ OEMCryptoResult OEMCrypto_GenerateDerivedKeys( * OEMCrypto_LoadLicense() proceed in the same manner for license requests using * RSA or using a Widevine keybox token. * + * This function is also used to derive keys before processing a Cast + * Certificate provisioning response in OEMCrypto_LoadProvisioning(). + * See [Cast Receiver](../../cast) for more details. + * * @verification * If the RSA key's allowed_schemes is not kSign_RSASSA_PSS, then no keys are * derived and the error OEMCrypto_ERROR_INVALID_KEY is returned. An RSA @@ -3862,11 +3866,14 @@ OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( * key and signing key generated using an algorithm at least as strong as * that in GenerateDerivedKeys. * - * First, OEMCrypto shall verify the signature of the message using - * HMAC-SHA256 with the derived mac_key[server]. The signature verification - * shall use a constant-time algorithm (a signature mismatch will always take - * the same time as a successful comparison). The signature is over the - * entire message buffer starting at message with length message_length. If + * First, OEMCrypto shall verify the signature of the message using the correct + * algorithm depending on if the device supports Provisioning 2.0, 3.0 or 4.0. + * + * For Provisioning 2.0, OEMCrypto shall verify the signature of the message + * using HMAC-SHA256 with the derived mac_key[server]. The signature + * verification shall use a constant-time algorithm (a signature mismatch will + * always take the same time as a successful comparison). The signature is over + * the entire message buffer starting at message with length message_length. If * the signature verification fails, ignore all other arguments and return * OEMCrypto_ERROR_SIGNATURE_FAILURE. * @@ -3874,7 +3881,10 @@ OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( * and encrypt_key with a call to OEMCrypto_DeriveKeysFromSessionKey() or * OEMCrypto_GenerateDerivedKeys(). * - * The function ODK_ParseProvisioning is called to parse the message. If it + * For Provisioning 3.0 and 4.0, the signature is not verified. + * + * After the signature is verified, + * the function ODK_ParseProvisioning is called to parse the message. If it * returns an error, OEMCrypto shall return that error to the CDM layer. The * function ODK_ParseProvisioning is described in the document "Widevine Core * Message Serialization". @@ -4151,22 +4161,31 @@ OEMCryptoResult OEMCrypto_GenerateRSASignature( RSA_Padding_Scheme padding_scheme); /** - * OEMCrypto will use OEMCrypto_PrepAndSignProvisioningRequest(), as described - * in the document "Widevine Core Message Serialization", to prepare the core - * message. If it returns an error, the error should be returned by OEMCrypto - * to the CDM layer. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall - * compute the signature of the entire message. The entire message is the - * buffer starting at message with length message_length. + * OEMCrypto will use ODK_PrepareCoreProvisioningRequest() or + * ODK_PrepareCoreProvisioning40Request(), as described in the document + * "Widevine Core Message Serialization", to prepare the core message. + * ODK_PrepareCoreProvisioningRequest() for Provisioning 2 or 3, and + * ODK_PrepareCoreProvisioning40Request() for Provisioning 4. If the ODK + * function returns an error, the error should be returned by OEMCrypto to the + * CDM layer. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall compute the + * signature of the entire message. The entire message is the buffer starting at + * message with length message_length. * * For a device that has a keybox, i.e. Provisioning 2.0, OEMCrypto will sign * the request with the session's derived client mac key from the previous * call to OEMCrypto_GenerateDerivedKeys(). * - * For a device that has an OEM Certificate, i.e. Provisioning 3.0, OEMCrypto - * will sign the request with the private key associated with the OEM + * For Provisioning 3.0, i.e. a device that has a baked in OEM Certificate, + * OEMCrypto will sign the request with the private key associated with the OEM * Certificate. The key shall have been loaded by a previous call to * OEMCrypto_LoadDRMPrivateKey(). * + * For Provisioning 4.0, i.e. a device that uses a Boot Chain Certificate to + * request and OEM cert, a request for an OEM cert is signed by the OEM private + * key. A request for a DRM cert is signed by the DRM private key. The DRM cert + * that was generated on the device in OEMCrypto_GenerateCertificateKeyPair() is + * signed by the OEM cert private key. + * * Refer to the Signing Messages Sent to a Server section above for more * details. * @@ -5575,17 +5594,9 @@ OEMCryptoResult OEMCrypto_GetRandom(uint8_t* random_data, /** * OEMCrypto_SelectKey + * @deprecated * Not required for the current version of OEMCrypto. Declared here to * help with backward compatibility. - * - * TODO(b/240995221): Deprecate and remove params. - * @param[in] session: handle for the crypto or entitled key session to be used. - * @param[in] content_key_id: pointer to the content Key ID. - * @param[in] content_key_id_length: length of the content Key ID, in bytes. - * From 1 to 16, inclusive. - * @param[in] cipher_mode: whether the key should be prepared for CTR mode or - * CBC mode when used in later calls to DecryptCENC. This should be ignored - * when the key is used for Generic Crypto calls. */ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, const uint8_t* content_key_id, @@ -5594,18 +5605,9 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, /** * OEMCrypto_DecryptCENC_V17 + * @deprecated * Not required for the current version of OEMCrypto. Declared here to * help with backward compatibility. - * - * TODO(b/240995221): Deprecate and remove params. - * @param[in] session: Crypto or entitled session identifier. The crypto session - * in which decrypt is to be performed. - * @param[in] samples: A caller-owned array of OEMCrypto_SampleDescription - * structures. Each entry in this array contains one sample of the content. - * @param[in] samples_length: The length of the array pointed to by the samples - * parameter. - * @param[in] pattern: A caller-owned structure indicating the encrypt/skip - * pattern as specified in the ISO-CENC standard. */ OEMCryptoResult OEMCrypto_DecryptCENC_V17( OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples, @@ -5613,19 +5615,9 @@ OEMCryptoResult OEMCrypto_DecryptCENC_V17( /** * OEMCrypto_Generic_Encrypt_V17 + * @deprecated * Not required for the current version of OEMCrypto. Declared here to * help with backward compatibility. - * - * TODO(b/240995221): Deprecate and remove params. - * @param[in] session: crypto or entitled key session identifier. - * @param[in] in_buffer: pointer to memory containing data to be encrypted. - * @param[in] in_buffer_length: length of the buffer, in bytes. The algorithm - * may restrict in_buffer_length to be a multiple of block size. - * @param[in] iv: IV for encrypting data. Size is 128 bits. - * @param[in] algorithm: Specifies which encryption algorithm to use. - * Currently, only CBC 128 mode is allowed for encryption. - * @param[out] out_buffer: pointer to buffer in which encrypted data should be - * stored. */ OEMCryptoResult OEMCrypto_Generic_Encrypt_V17( OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* in_buffer, @@ -5634,19 +5626,9 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt_V17( /** * OEMCrypto_Generic_Decrypt_V17 + * @deprecated * Not required for the current version of OEMCrypto. Declared here to * help with backward compatibility. - * - * TODO(b/240995221): Deprecate and remove params. - * @param[in] session: crypto or entitled key session identifier. - * @param[in] in_buffer: pointer to memory containing data to be encrypted. - * @param[in] in_buffer_length: length of the buffer, in bytes. The algorithm - * may restrict in_buffer_length to be a multiple of block size. - * @param[in] iv: IV for encrypting data. Size is 128 bits. - * @param[in] algorithm: Specifies which encryption algorithm to use. - * Currently, only CBC 128 mode is allowed for decryption. - * @param[out] out_buffer: pointer to buffer in which decrypted data should be - * stored. */ OEMCryptoResult OEMCrypto_Generic_Decrypt_V17( OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* in_buffer, @@ -5655,19 +5637,9 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt_V17( /** * OEMCrypto_Generic_Sign_V17 + * @deprecated * Not required for the current version of OEMCrypto. Declared here to * help with backward compatibility. - * - * TODO(b/240995221): Deprecate and remove params. - * @param[in] session: crypto or entitled key session identifier. - * @param[in] buffer: pointer to memory containing data to be encrypted. - * @param[in] buffer_length: length of the buffer, in bytes. - * @param[in] algorithm: Specifies which algorithm to use. - * @param[out] signature: pointer to buffer in which signature should be - * stored. May be null on the first call in order to find required buffer - * size. - * @param[in,out] signature_length: (in) length of the signature buffer, in - * bytes. (out) actual length of the signature */ OEMCryptoResult OEMCrypto_Generic_Sign_V17(OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* buffer, @@ -5678,16 +5650,9 @@ OEMCryptoResult OEMCrypto_Generic_Sign_V17(OEMCrypto_SESSION session, /** * OEMCrypto_Generic_Verify_V17 + * @deprecated * Not required for the current version of OEMCrypto. Declared here to * help with backward compatibility. - * - * TODO(b/240995221): Deprecate and remove params. - * @param[in] session: crypto or entitled key session identifier. - * @param[in] buffer: pointer to memory containing data to be encrypted. - * @param[in] buffer_length: length of the buffer, in bytes. - * @param[in] algorithm: Specifies which algorithm to use. - * @param[in] signature: pointer to buffer in which signature resides. - * @param[in] signature_length: length of the signature buffer, in bytes. */ OEMCryptoResult OEMCrypto_Generic_Verify_V17( OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* buffer, 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); diff --git a/libwvdrmengine/oemcrypto/test/common.mk b/libwvdrmengine/oemcrypto/test/common.mk index 309abca9..ce7a3c86 100644 --- a/libwvdrmengine/oemcrypto/test/common.mk +++ b/libwvdrmengine/oemcrypto/test/common.mk @@ -17,8 +17,10 @@ LOCAL_SRC_FILES:= \ oemcrypto_corpus_generator_helper.cpp \ oemcrypto_session_tests_helper.cpp \ oemcrypto_basic_test.cpp \ + oemcrypto_decrypt_test.cpp \ oemcrypto_license_test.cpp \ oemcrypto_provisioning_test.cpp \ + oemcrypto_usage_table_test.cpp \ oemcrypto_test.cpp \ oemcrypto_test_android.cpp \ oemcrypto_test_main.cpp \ 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); } diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp index 0660ca53..788a7959 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,13 +160,7 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { provisioning_method == OEMCrypto_BootCertificateChain) FilterOut(&filter, "OEMCryptoLoadsCert*"); if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); - if (!cast_receiver) FilterOut(&filter, "*CastReceiver*"); - if (!supports_cas) FilterOut(&filter, "*CasOnly*"); if (derive_key_method == NO_METHOD) FilterOut(&filter, "*SessionTest*"); - if (provisioning_method - != OEMCrypto_OEMCertificate) FilterOut(&filter, "*Prov30*"); - if (provisioning_method != OEMCrypto_BootCertificateChain) - FilterOut(&filter, "*Prov40*"); if (!supports_rsa_3072) FilterOut(&filter, "*RSAKey3072*"); if (api_version < 17) FilterOut(&filter, "*API17*"); if (api_version < 18) FilterOut(&filter, "*API18*"); 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); 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 new file mode 100644 index 00000000..22c810c0 --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.cpp @@ -0,0 +1,686 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. +// + +#include "oemcrypto_decrypt_test.h" + +#include "test_sleep.h" + +using ::testing::Combine; +using ::testing::Range; +using ::testing::Values; + +namespace wvoec { + +// Cannot decrypt without first getting a key handle. +TEST_P(OEMCryptoLicenseTest, FailDecryptWithoutGettingAHandle) { + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_NO_FATAL_FAILURE( + session_.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// Cannot decrypt with an old key handle. +TEST_P(OEMCryptoLicenseTest, FailDecryptWithOldKeyHandle) { + Session donor_session; + LicenseRoundTrip license_messages2(&donor_session); + ASSERT_NO_FATAL_FAILURE(donor_session.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&donor_session)); + ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages2.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages2.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(donor_session.TestDecryptCTR()); + + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + // Inject the donor session's key handle into |session_| and then close the + // donor, which should render the handle invalid. + session_.key_handle() = donor_session.key_handle(); + donor_session.close(); + + ASSERT_NO_FATAL_FAILURE( + session_.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// SelectKey should fail if we attempt to select a key that has not been loaded. +// Also, the error should be NO_CONTENT_KEY. +// This test should pass for v15 devices, except that the exact error code was +// not specified until v16. +TEST_P(OEMCryptoLicenseTest, SelectKeyNotThereAPI16) { + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + const char* key_id = "no_key"; + vector 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..9f9b5005 --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_decrypt_test.h @@ -0,0 +1,427 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. +// +// Test data for OEMCrypto unit tests. +// +#ifndef CDM_OEMCRYPTO_DECRYPT_TEST_ +#define CDM_OEMCRYPTO_DECRYPT_TEST_ + +#include + +#include "OEMCryptoCENC.h" +#include "log.h" +#include "oec_decrypt_fallback_chain.h" +#include "oemcrypto_basic_test.h" +#include "oemcrypto_license_test.h" + +namespace wvoec { + +// Used to test the different HDCP versions. This test is parameterized by the +// required HDCP version in the key control block. +class OEMCryptoSessionTestDecryptWithHDCP : public OEMCryptoSessionTests, + public WithParamInterface { + 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.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp index 9b8f6cf0..1031cf9a 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.cpp @@ -4,11 +4,11 @@ // #include "oemcrypto_license_test.h" -#include "log.h" -#include "oemcrypto_basic_test.h" -#include "oemcrypto_resource_test.h" -#include "oemcrypto_session_tests_helper.h" + #include "platform.h" +#include "test_sleep.h" + +using ::testing::Range; namespace wvoec { @@ -803,5 +803,169 @@ TEST_F(OEMCryptoSessionTests, CheckMinimumPatchLevel) { } } +// +// Load, Refresh Keys Test +// + +// Refresh keys should work if the license uses a nonce. +TEST_P(OEMCryptoRefreshTest, RefreshWithNonce) { + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); +} + +// Refresh keys should work if the license does not use a nonce. +TEST_P(OEMCryptoRefreshTest, RefreshNoNonce) { + license_messages_.set_control(0); + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); +} + +// Refresh keys should NOT work if a license has not been loaded. +TEST_P(OEMCryptoRefreshTestAPI16, RefreshNoLicense) { + Session s; + s.open(); + constexpr size_t message_size = kMaxCoreMessage + 42; + std::vector 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 139ecad9..0d88c6e6 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h @@ -10,11 +10,12 @@ #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 +238,173 @@ class OEMCryptoLicenseTest : public OEMCryptoLicenseTestAPI16, } }; +// Test usage table functionality. +class LicenseWithUsageEntry { + public: + LicenseWithUsageEntry(const std::string& pst = "my_pst") + : session_(), + license_messages_(&session_), + generic_crypto_(false), + time_license_received_(0), + time_first_decrypt_(0), + time_last_decrypt_(0), + active_(true) { + license_messages_.set_pst(pst); + } + + void MakeAndLoadOnline(OEMCryptoSessionTests* test) { + MakeAndLoad(test, + wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired); + } + + // If status in not a nullptr, then creating a new entry is allowed to fail, + // and its error code is stored in status. + void MakeOfflineAndClose(OEMCryptoSessionTests* test, + OEMCryptoResult* status = nullptr) { + MakeAndLoad(test, wvoec::kControlNonceOrEntry, status); + if (status != nullptr && *status != OEMCrypto_SUCCESS) { + ASSERT_NO_FATAL_FAILURE(session_.close()); + return; + } + ASSERT_NO_FATAL_FAILURE( + session_.UpdateUsageEntry(&(test->encrypted_usage_header_))); + ASSERT_NO_FATAL_FAILURE(GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(session_.close()); + } + + // If status in not a nullptr, then creating a new entry is allowed to fail, + // and its error code is stored in status. + void MakeAndLoad(SessionUtil* util, uint32_t control, + OEMCryptoResult* status = nullptr) { + license_messages_.set_control(control); + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(util->InstallTestDrmKey(&session_)); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + if (generic_crypto_) { + ASSERT_NO_FATAL_FAILURE( + license_messages_.CreateResponseWithGenericCryptoKeys()); + } else { + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + } + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry(status)); + if (status != nullptr && *status != OEMCrypto_SUCCESS) return; + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + time_license_received_ = wvutil::Clock().GetCurrentTime(); + } + + void OpenAndReload(SessionUtil* util) { + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(session_.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(util->InstallTestDrmKey(&session_)); + ASSERT_NO_FATAL_FAILURE(session_.GenerateDerivedKeysFromSessionKey()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + } + + // Test decrypt, and update the decrypt times for the pst report. + void TestDecryptCTR(bool select_key_first = true, + OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { + session_.TestDecryptCTR(select_key_first, expected_result); + time_last_decrypt_ = wvutil::Clock().GetCurrentTime(); + if (time_first_decrypt_ == 0) time_first_decrypt_ = time_last_decrypt_; + } + + void DeactivateUsageEntry() { + active_ = false; + if (ShouldGenerateCorpus()) { + const std::string file_name = + GetFileName("oemcrypto_deactivate_usage_entry_fuzz_seed_corpus"); + AppendToFile(file_name, pst().c_str(), pst().length()); + } + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_DeactivateUsageEntry( + session_.session_id(), + reinterpret_cast(pst().c_str()), pst().length())); + } + + void GenerateVerifyReport(OEMCrypto_Usage_Entry_Status status) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst())); + Test_PST_Report expected(pst(), status); + ASSERT_NO_FATAL_FAILURE( + session_.VerifyReport(expected, time_license_received_, + time_first_decrypt_, time_last_decrypt_)); + // The PST report was signed above. Below we verify that the entire message + // that is sent to the server will be signed by the right mac keys. + RenewalRoundTrip renewal_messages(&license_messages_); + renewal_messages.set_is_release(!active_); + ASSERT_NO_FATAL_FAILURE(renewal_messages.SignAndVerifyRequest()); + } + + void ReloadUsageEntry() { + session_.ReloadUsageEntry(); + session_.set_mac_keys(license_messages_.response_data().mac_keys); + } + + const std::string& pst() const { return license_messages_.pst(); } + void set_pst(const std::string& pst) { license_messages_.set_pst(pst); } + LicenseRoundTrip& license_messages() { return license_messages_; } + Session& session() { return session_; } + void set_generic_crypto(bool generic_crypto) { + generic_crypto_ = generic_crypto; + } + + private: + Session session_; + LicenseRoundTrip license_messages_; + bool generic_crypto_; + int64_t time_license_received_; + int64_t time_first_decrypt_; + int64_t time_last_decrypt_; + bool active_; +}; + +class OEMCryptoRefreshTest : public OEMCryptoLicenseTest { + protected: + void SetUp() override { + OEMCryptoLicenseTest::SetUp(); + // These values allow us to run a few simple duration tests or just start + // playback right away. All times are in seconds since the license was + // signed. + // Soft expiry false means timers are strictly enforce. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = false; + // Playback may begin immediately. + timer_limits_.earliest_playback_start_seconds = 0; + // First playback may be within the first two seconds. + timer_limits_.rental_duration_seconds = kDuration; + // Once started, playback may last two seconds without a renewal. + timer_limits_.initial_renewal_duration_seconds = kDuration; + // Total playback is not limited. + timer_limits_.total_playback_duration_seconds = 0; + } + + void LoadLicense() { + license_messages_.core_response().timer_limits = timer_limits_; + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + } + + void MakeRenewalRequest(RenewalRoundTrip* renewal_messages) { + ASSERT_NO_FATAL_FAILURE(renewal_messages->SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(renewal_messages->CreateDefaultResponse()); + } + + void LoadRenewal(RenewalRoundTrip* renewal_messages, + OEMCryptoResult expected_result) { + ASSERT_NO_FATAL_FAILURE(renewal_messages->EncryptAndSignResponse()); + ASSERT_EQ(expected_result, renewal_messages->LoadResponse()); + } + + ODK_TimerLimits timer_limits_; +}; + +// This class is for the refresh tests that should only be run on licenses with +// a core message. +class OEMCryptoRefreshTestAPI16 : public OEMCryptoRefreshTest {}; + } // namespace wvoec #endif // CDM_OEMCRYPTO_LICENSE_TEST_ \ 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 fb39fb02..88182c3a 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,446 @@ TEST_F(OEMCryptoProv40Test, ProvisionDrmCert) { ASSERT_EQ(s.IsPublicKeySet(), true); } +TEST_F(OEMCryptoLoadsCertificate, PrepAndSignLicenseRequestCounterAPI18) { + ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_)); + s.GenerateNonce(); + + size_t core_message_length = 100; + std::vector 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) { + if (global_features.provisioning_method != OEMCrypto_OEMCertificate) { + GTEST_SKIP() << "Test for Prov 3.0 devices only."; + } + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + // Encrypt and sign once, so that we can use the size of the response. + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + // If the offset is before the end, but the offset+length is bigger, then + // the message should be rejected. + provisioning_messages.core_response().encrypted_message_key.offset = + provisioning_messages.encrypted_response_buffer().size() + 1; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey verifies the message signature. +// TODO(b/144186970): This test should also run on Prov 3.0 devices. +TEST_F(OEMCryptoLoadsCertificate, + CertificateProvisionBadSignatureKeyboxTestAPI16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + provisioning_messages.response_signature()[4] ^= 42; // bad signature. + ASSERT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey verifies the nonce is current. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce_API16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + provisioning_messages.core_request().nonce ^= 42; // bad nonce. + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, + provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey verifies the RSA key is valid. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRSAKey) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + provisioning_messages.response_data().rsa_key[4] ^= 42; // bad key. + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey verifies the RSA key is valid. +// TODO(b/144186970): This test should also run on Prov 3.0 devices. +TEST_F(OEMCryptoLoadsCertificate, + CertificateProvisionBadRSAKeyKeyboxTestAPI16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + size_t rsa_offset = + provisioning_messages.core_response().enc_private_key.offset; + // Offsets are relative to the message body, after the core message. + rsa_offset += provisioning_messages.serialized_core_message().size(); + rsa_offset += 4; // Change the middle of the key. + provisioning_messages.encrypted_response_buffer()[rsa_offset] ^= 42; + ASSERT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey accepts the maximum message size. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionLargeBuffer) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + const size_t max_size = GetResourceValue(kLargeMessageSize); + provisioning_messages.set_message_size(max_size); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + // We should not be able to find the rsa key in the wrapped key. It should + // be encrypted. + EXPECT_EQ(nullptr, find(provisioning_messages.wrapped_rsa_key(), + provisioning_messages.encoded_rsa_key())); +} + +// Test that a wrapped RSA key can be loaded. +TEST_F(OEMCryptoLoadsCertificate, LoadWrappedRSAKey) { + ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_)); +} + +class OEMCryptoLoadsCertVariousKeys : public OEMCryptoLoadsCertificate { + public: + void TestKey(const uint8_t* key, size_t key_length) { + encoded_rsa_key_.assign(key, key + key_length); + ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.SetRsaPublicKeyFromPrivateKeyInfo( + encoded_rsa_key_.data(), encoded_rsa_key_.size())); + ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_)); + + LicenseRoundTrip license_messages(&s); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); + } +}; + +// Test a 3072 bit RSA key certificate. +TEST_F(OEMCryptoLoadsCertVariousKeys, TestLargeRSAKey3072) { + if (!global_features.supports_rsa_3072) { + GTEST_SKIP() << "OEMCrypto does not support RSA 3072"; + } + TestKey(kTestRSAPKCS8PrivateKeyInfo3_3072, + sizeof(kTestRSAPKCS8PrivateKeyInfo3_3072)); +} + +// Test an RSA key certificate which has a private key generated using the +// Carmichael totient. +TEST_F(OEMCryptoLoadsCertVariousKeys, TestCarmichaelRSAKey) { + TestKey(kTestKeyRSACarmichael_2048, sizeof(kTestKeyRSACarmichael_2048)); +} + +TEST_F(OEMCryptoLoadsCertVariousKeys, TestCarmichaelNonZeroNormalDer) { + TestKey(kCarmichaelNonZeroNormalDer, kCarmichaelNonZeroNormalDerLen); +} + +TEST_F(OEMCryptoLoadsCertVariousKeys, TestCarmichaelNonZeroShortDer) { + TestKey(kCarmichaelNonZeroShortDer, kCarmichaelNonZeroShortDerLen); +} + +TEST_F(OEMCryptoLoadsCertVariousKeys, TestCarmichaelZeroNormalDer) { + TestKey(kCarmichaelZeroNormalDer, kCarmichaelZeroNormalDerLen); +} + +TEST_F(OEMCryptoLoadsCertVariousKeys, TestCarmichaelZeroShortDer) { + TestKey(kCarmichaelZeroShortDer, kCarmichaelZeroShortDerLen); +} + +TEST_F(OEMCryptoLoadsCertVariousKeys, TestDualNonZeroNormalDer) { + TestKey(kDualNonZeroNormalDer, kDualNonZeroNormalDerLen); +} + +TEST_F(OEMCryptoLoadsCertVariousKeys, TestDualNonZeroShortDer) { + TestKey(kDualNonZeroShortDer, kDualNonZeroShortDerLen); +} + +TEST_F(OEMCryptoLoadsCertVariousKeys, TestDualZeroNormalDer) { + TestKey(kDualZeroNormalDer, kDualZeroNormalDerLen); +} + +TEST_F(OEMCryptoLoadsCertVariousKeys, TestDualZeroShortDer) { + TestKey(kDualZeroShortDer, kDualZeroShortDerLen); +} + +TEST_F(OEMCryptoLoadsCertVariousKeys, TestEulerNonZeroNormalDer) { + TestKey(kEulerNonZeroNormalDer, kEulerNonZeroNormalDerLen); +} + +TEST_F(OEMCryptoLoadsCertVariousKeys, TestEulerZeroNormalDer) { + TestKey(kEulerZeroNormalDer, kEulerZeroNormalDerLen); +} + +// This tests that two sessions can use different RSA keys simultaneously. +TEST_F(OEMCryptoLoadsCertificate, TestMultipleRSAKeys) { + ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); + Session s1; // Session s1 loads the default rsa key, but doesn't use it + // until after s2 uses its key. + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s1.SetRsaPublicKeyFromPrivateKeyInfo( + encoded_rsa_key_.data(), encoded_rsa_key_.size())); + ASSERT_NO_FATAL_FAILURE(s1.LoadWrappedRsaDrmKey(wrapped_drm_key_)); + + Session s2; // Session s2 uses a different rsa key. + encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeyInfo4_2048, + kTestRSAPKCS8PrivateKeyInfo4_2048 + + sizeof(kTestRSAPKCS8PrivateKeyInfo4_2048)); + ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.SetRsaPublicKeyFromPrivateKeyInfo( + encoded_rsa_key_.data(), encoded_rsa_key_.size())); + ASSERT_NO_FATAL_FAILURE(s2.LoadWrappedRsaDrmKey(wrapped_drm_key_)); + LicenseRoundTrip license_messages2(&s2); + ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages2.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages2.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); + s2.close(); + + // After s2 has loaded its rsa key, we continue using s1's key. + LicenseRoundTrip license_messages1(&s1); + ASSERT_NO_FATAL_FAILURE(license_messages1.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages1.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages1.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages1.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); +} + +// This tests the maximum number of DRM private keys that OEMCrypto can load +TEST_F(OEMCryptoLoadsCertificate, TestMaxDRMKeys) { + const size_t max_total_keys = GetResourceValue(kMaxTotalDRMPrivateKeys); + std::vector> 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 4b595703..03942a01 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h @@ -8,11 +8,13 @@ #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 "oec_extra_test_keys.h" +#include "oemcrypto_basic_test.h" +#include "oemcrypto_license_test.h" +#include "oemcrypto_resource_test.h" + namespace wvoec { // Tests using this class are only used for devices with a keybox. They are not @@ -34,10 +36,95 @@ class OEMCryptoKeyboxTest : public OEMCryptoClientTest { }; // This class is for tests that have an OEM Certificate instead of a keybox. -class OEMCryptoProv30Test : public OEMCryptoClientTest {}; +class OEMCryptoProv30Test : public OEMCryptoClientTest { + void SetUp() override { + OEMCryptoClientTest::SetUp(); + if (global_features.provisioning_method != OEMCrypto_OEMCertificate) { + GTEST_SKIP() << "Test for Prov 3.0 devices only."; + } + } +}; // This class is for tests that have boot certificate chain instead of a keybox. -class OEMCryptoProv40Test : public OEMCryptoClientTest {}; +class OEMCryptoProv40Test : public OEMCryptoClientTest { + void SetUp() override { + OEMCryptoClientTest::SetUp(); + if (global_features.provisioning_method != OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for Prov 4.0 devices only."; + } + } +}; + +// +// Certificate Root of Trust Tests +// +class OEMCryptoLoadsCertificate : public OEMCryptoSessionTestKeyboxTest { + protected: + void TestPrepareProvisioningRequestForHugeBufferLengths( + const std::function 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 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 b851d97d..5dd260a4 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -77,9 +77,11 @@ #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" +#include "oemcrypto_usage_table_test.h" #include "platform.h" #include "string_conversions.h" #include "test_sleep.h" @@ -292,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( @@ -342,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()); @@ -378,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( @@ -412,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( @@ -450,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( @@ -653,88 +670,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 /// @{ @@ -1058,145 +993,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 /// @{ @@ -1592,67 +1388,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 /// @{ @@ -1705,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())); } @@ -1712,1354 +1450,6 @@ INSTANTIATE_TEST_SUITE_P(TestHDCP, OEMCryptoSessionTestLoadCasKeysWithHDCP, Range(1, 6)); /// @} -/// @addtogroup renewal -/// @{ - -// -// 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. -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 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 /// @{ @@ -3194,6 +1584,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) { @@ -3207,6 +1600,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) { @@ -3219,201 +1615,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 /// @{ @@ -3759,6 +1960,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); @@ -4667,278 +2875,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,1861 +3437,4 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoGenericCryptoTest, INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoGenericCryptoKeyIdLengthTest, Range(kCoreMessagesAPI, kCurrentAPI + 1)); /// @} - -/// @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(); } - - 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_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 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..ce5d7ab9 --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.h @@ -0,0 +1,337 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. +// +// Test data for OEMCrypto unit tests. +// +#ifndef CDM_OEMCRYPTO_USAGE_TABLE_TEST_ +#define CDM_OEMCRYPTO_USAGE_TABLE_TEST_ + +#include +#include +#include + +#include "OEMCryptoCENC.h" +#include "log.h" +#include "oemcrypto_basic_test.h" +#include "oemcrypto_license_test.h" +#include "test_sleep.h" + +namespace wvoec { + +// This class is for testing the generic crypto functionality. +class OEMCryptoGenericCryptoTest : public OEMCryptoRefreshTest { + protected: + // buffer_size_ must be a multiple of encryption block size, 16. We'll use a + // reasonable number of blocks for most of the tests. + OEMCryptoGenericCryptoTest() : buffer_size_(160) {} + + void SetUp() override { + OEMCryptoRefreshTest::SetUp(); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE( + license_messages_.CreateResponseWithGenericCryptoKeys()); + InitializeClearBuffer(); + } + + void InitializeClearBuffer() { + clear_buffer_.assign(buffer_size_, 0); + for (size_t i = 0; i < clear_buffer_.size(); i++) { + clear_buffer_[i] = 1 + i % 250; + } + for (size_t i = 0; i < wvoec::KEY_IV_SIZE; i++) { + iv_[i] = i; + } + } + + void ResizeBuffer(size_t new_size) { + buffer_size_ = new_size; + InitializeClearBuffer(); // Re-initialize the clear buffer. + } + + void EncryptAndLoadKeys() { + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + } + + // Encrypt the buffer with the specified key made in + // CreateResponseWithGenericCryptoKeys. + void EncryptBuffer(unsigned int key_index, const vector& 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