diff --git a/libwvdrmengine/Android.mk b/libwvdrmengine/Android.mk index 9ed6e2ef..31f2e751 100644 --- a/libwvdrmengine/Android.mk +++ b/libwvdrmengine/Android.mk @@ -263,5 +263,4 @@ include $(BUILD_SHARED_LIBRARY) include vendor/widevine/libwvdrmengine/level3/Android.mk include vendor/widevine/libwvdrmengine/mediacrypto/Android.mk include vendor/widevine/libwvdrmengine/mediadrm/Android.mk -include vendor/widevine/libwvdrmengine/oemcrypto/odk/Android.mk include vendor/widevine/libwvdrmengine/vts/vendor_module/Android.mk diff --git a/libwvdrmengine/cdm/Android.bp b/libwvdrmengine/cdm/Android.bp index 0ca5469f..ae262960 100644 --- a/libwvdrmengine/cdm/Android.bp +++ b/libwvdrmengine/cdm/Android.bp @@ -8,7 +8,8 @@ METRICS_SRC_DIR = "metrics/src" cc_library_static { name: "libcdm", - cflags: ["-DDYNAMIC_ADAPTER"], + cflags: ["-DDYNAMIC_ADAPTER", + "-DTEST_OEMCRYPTO_V15"], include_dirs: [ "vendor/widevine/libwvdrmengine/cdm/core/include", diff --git a/libwvdrmengine/cdm/core/include/content_key_session.h b/libwvdrmengine/cdm/core/include/content_key_session.h index fc3f3570..9961c3b3 100644 --- a/libwvdrmengine/cdm/core/include/content_key_session.h +++ b/libwvdrmengine/cdm/core/include/content_key_session.h @@ -51,8 +51,8 @@ class ContentKeySession : public KeySession { // Decrypt for ContentKeySession OEMCryptoResult Decrypt( const CdmDecryptionParameters& params, - OEMCrypto_DestBufferDesc& buffer_descriptor, - OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) override; + OEMCrypto_DestBufferDesc& buffer_descriptor, size_t additional_offset, + const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) override; protected: virtual OEMCryptoResult LoadKeysAsLicenseType( diff --git a/libwvdrmengine/cdm/core/include/key_session.h b/libwvdrmengine/cdm/core/include/key_session.h index 52e98136..92cb140b 100644 --- a/libwvdrmengine/cdm/core/include/key_session.h +++ b/libwvdrmengine/cdm/core/include/key_session.h @@ -40,8 +40,8 @@ class KeySession { CdmCipherMode cipher_mode) = 0; virtual OEMCryptoResult Decrypt( const CdmDecryptionParameters& params, - OEMCrypto_DestBufferDesc& buffer_descriptor, - OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) = 0; + OEMCrypto_DestBufferDesc& buffer_descriptor, size_t additional_offset, + const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) = 0; protected: metrics::CryptoMetrics* metrics_; diff --git a/libwvdrmengine/cdm/core/src/content_key_session.cpp b/libwvdrmengine/cdm/core/src/content_key_session.cpp index 455a121d..84411a9e 100644 --- a/libwvdrmengine/cdm/core/src/content_key_session.cpp +++ b/libwvdrmengine/cdm/core/src/content_key_session.cpp @@ -107,18 +107,34 @@ OEMCryptoResult ContentKeySession::SelectKey(const std::string& key_id, // Decrypt for ContentKeySession OEMCryptoResult ContentKeySession::Decrypt( const CdmDecryptionParameters& params, - OEMCrypto_DestBufferDesc& buffer_descriptor, - OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) { - OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED; -#if 0 // TODO(b/135285640): fix this. + OEMCrypto_DestBufferDesc& buffer_descriptor, size_t additional_offset, + const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) { + // TODO(b/135285640): fix this. At the moment, all this does is send one + // subsample. No checks are in place. + OEMCryptoResult sts; - M_TIME(sts = OEMCrypto_DecryptCENC( - oec_session_id_, params.encrypt_buffer, params.encrypt_length, - params.is_encrypted, &(*params.iv).front(), params.block_offset, - &buffer_descriptor, &pattern_descriptor, params.subsample_flags), + OEMCrypto_SampleDescription sample; // Just one for now. + OEMCrypto_SubSampleDescription subsample; + sample.buffers.input_data = params.encrypt_buffer + additional_offset; + sample.buffers.input_data_length = params.encrypt_length; + sample.buffers.output_descriptor = buffer_descriptor; + memcpy(sample.iv, params.iv->data(), KEY_IV_SIZE); + sample.subsamples = &subsample; + sample.subsamples_length = 1; + if (params.is_encrypted) { + subsample.num_bytes_clear = 0; + subsample.num_bytes_encrypted = params.encrypt_length; + } else { + subsample.num_bytes_clear = params.encrypt_length; + subsample.num_bytes_encrypted = 0; + } + subsample.subsample_flags = params.subsample_flags; + subsample.block_offset = params.block_offset; + + M_TIME(sts = OEMCrypto_DecryptCENC(oec_session_id_, &sample, 1, + &pattern_descriptor), metrics_, oemcrypto_decrypt_cenc_, sts, metrics::Pow2Bucket(params.encrypt_length)); -#endif return sts; } diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index cc6b2f56..f7658dbc 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -541,6 +541,8 @@ bool CryptoSession::GetApiMinorVersion(SecurityLevel security_level, WithOecReadLock("GetApiMinorVersion", [&] { *minor_version = OEMCrypto_MinorAPIVersion(security_level); }); + // Record the minor version into the metrics. + metrics_->oemcrypto_minor_api_version_.Record(*minor_version); return true; } @@ -811,10 +813,12 @@ CdmResponseType CryptoSession::PrepareAndSignLicenseRequest( // First call is intended to determine the required size of the // output buffers. WithOecSessionLock("PrepareAndSignLicenseRequest", [&] { - M_TIME(sts = OEMCrypto_PrepAndSignLicenseRequest( - oec_session_id_, nullptr, message.size(), &core_message_length, - nullptr, &signature_length), - metrics_, oemcrypto_prep_and_sign_license_request_, sts); + M_TIME( + sts = OEMCrypto_PrepAndSignLicenseRequest( + oec_session_id_, + reinterpret_cast(const_cast(message.data())), + message.size(), &core_message_length, nullptr, &signature_length), + metrics_, oemcrypto_prep_and_sign_license_request_, sts); }); if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { @@ -939,10 +943,12 @@ CdmResponseType CryptoSession::PrepareAndSignRenewalRequest( // First call is intended to determine the required size of the // output buffers. WithOecSessionLock("PrepareAndSignRenewalRequest", [&] { - M_TIME(sts = OEMCrypto_PrepAndSignRenewalRequest( - oec_session_id_, nullptr, message.size(), &core_message_length, - nullptr, &signature_length), - metrics_, oemcrypto_prep_and_sign_renewal_request_, sts); + M_TIME( + sts = OEMCrypto_PrepAndSignRenewalRequest( + oec_session_id_, + reinterpret_cast(const_cast(message.data())), + message.size(), &core_message_length, nullptr, &signature_length), + metrics_, oemcrypto_prep_and_sign_renewal_request_, sts); }); if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { @@ -1056,10 +1062,12 @@ CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest( // First call is intended to determine the required size of the // output buffers. WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] { - M_TIME(sts = OEMCrypto_PrepAndSignProvisioningRequest( - oec_session_id_, nullptr, message.size(), &core_message_length, - nullptr, &signature_length), - metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts); + M_TIME( + sts = OEMCrypto_PrepAndSignProvisioningRequest( + oec_session_id_, + reinterpret_cast(const_cast(message.data())), + message.size(), &core_message_length, nullptr, &signature_length), + metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts); }); if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { @@ -1318,8 +1326,8 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { } WithOecSessionLock("Decrypt() calling key_session_->Decrypt()", [&] { - sts = - key_session_->Decrypt(params, buffer_descriptor, pattern_descriptor); + sts = key_session_->Decrypt(params, buffer_descriptor, 0, + pattern_descriptor); }); if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) { @@ -1817,6 +1825,9 @@ bool CryptoSession::GetMaximumUsageTableEntries(SecurityLevel security_level, WithOecReadLock("GetMaxUsageTableEntries", [&] { *number_of_entries = OEMCrypto_MaximumUsageTableHeaderSize(security_level); }); + // Record the number of entries into the metrics. + metrics_->oemcrypto_maximum_usage_table_header_size_.Record( + *number_of_entries); return *number_of_entries >= kMinimumUsageTableEntriesSupported; } @@ -2562,18 +2573,12 @@ OEMCryptoResult CryptoSession::DecryptInChunks( // of AES blocks long. OEMCryptoResult sts; sts = OEMCrypto_ERROR_NOT_IMPLEMENTED; -#if 0 // TODO(b/135285640): fix this. WithOecSessionLock("DecryptInChunks", [&] { - M_TIME( - sts = OEMCrypto_DecryptCENC( - oec_session_id_, params.encrypt_buffer + additional_offset, - chunk_size, params.is_encrypted, &iv.front(), params.block_offset, - &buffer_descriptor, &pattern_descriptor, subsample_flags), - metrics_, oemcrypto_decrypt_cenc_, sts, - metrics::Pow2Bucket(chunk_size)); + M_TIME(sts = key_session_->Decrypt(params, buffer_descriptor, + additional_offset, pattern_descriptor), + metrics_, oemcrypto_decrypt_cenc_, sts, + metrics::Pow2Bucket(chunk_size)); }); -#endif - if (sts != OEMCrypto_SUCCESS) { return sts; } diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp index abdf9a6f..6d6baea2 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp @@ -30,6 +30,7 @@ #include "file_store.h" #include "level3.h" +#include "license_protocol.pb.h" #include "log.h" #include "metrics_collections.h" #include "odk.h" @@ -37,6 +38,7 @@ #include "wv_cdm_constants.h" using namespace wvoec3; +using video_widevine::ProvisioningResponse; using wvcdm::kLevel3; using wvcdm::kLevelDefault; @@ -410,6 +412,16 @@ struct FunctionPointers { L1_RefreshKeys_V14_t RefreshKeys_V14; }; +size_t GetOffset(const std::string& message, const std::string& field) { + size_t pos = message.find(field); + if (pos == std::string::npos) { + LOGE("Cannot find the |field| offset in message: field = %s", + field.c_str()); + pos = 0; + } + return pos; +} + // The WatchDog looks after a worker thread that is trying to initialize L3. // Once in a rare while, the L3 init does not finish and eats up CPU cycles. // If that happens, the watchdog thread will give up and return an error. @@ -952,7 +964,7 @@ class Adapter { // TODO(b/139814713): implement V16 DecryptCENC for Haystack L3 // level3_.LoadLicense = Level3_LoadLicense; level3_.LoadEntitledContentKeys = Level3_LoadEntitledContentKeys; - // TODO(b/135285640): fix this. + // TODO(b/139814713): fix this. // level3_.LoadRenewal = Level3_LoadRenewal; level3_.RefreshKeys = Level3_RefreshKeys; level3_.QueryKeyControl = Level3_QueryKeyControl; @@ -2085,12 +2097,14 @@ extern "C" OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, const FunctionPointers* fcn = gAdapter->GetFunctionPointers(kLevelDefault); if (!fcn) return OEMCrypto_ERROR_INVALID_SESSION; if (fcn->version < 10) return OEMCrypto_ERROR_NOT_IMPLEMENTED; + if (fcn->LoadTestKeybox != nullptr) { + return fcn->LoadTestKeybox(buffer, length); + } if (fcn->LoadTestKeybox_V13 != nullptr) { // Old versions might use wrong keybox, but this is the best we can do. return fcn->LoadTestKeybox_V13(); } - if (fcn->LoadTestKeybox == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return fcn->LoadTestKeybox(buffer, length); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; } extern "C" OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid() { @@ -2164,57 +2178,55 @@ extern "C" OEMCryptoResult OEMCrypto_LoadProvisioning( pair.session, message, message_length, core_message_length, signature, signature_length, wrapped_private_key, wrapped_private_key_length); } - if (pair.fcn->GetDeviceID == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - std::vector device_id(ODK_DEVICE_ID_LEN_MAX); - size_t device_id_length = device_id.size(); - if (pair.fcn->GetDeviceID(device_id.data(), &device_id_length) != - OEMCrypto_SUCCESS) { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - } - ODK_ParsedProvisioning parsed_response; - ODK_NonceValues nonce_values; - nonce_values.nonce = pair.nonce; - nonce_values.api_version = pair.fcn->version; - nonce_values.session_id = pair.session; - const OEMCryptoResult result = ODK_ParseProvisioning( - message, message_length, core_message_length, &nonce_values, - device_id.data(), device_id_length, &parsed_response); - if (result != OEMCrypto_SUCCESS) { - LOGE("ODK Error %d", result); - return result; - } if (pair.fcn->GetProvisioningMethod == nullptr) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } const OEMCrypto_ProvisioningMethod method = pair.fcn->GetProvisioningMethod(); + + const std::string signed_message( + reinterpret_cast(message), + reinterpret_cast(message + message_length)); + ProvisioningResponse provisioning_response; + if (!provisioning_response.ParseFromString(signed_message)) { + LOGE("Failed to parse signed provisioining response"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const std::string& enc_rsa_key = provisioning_response.device_rsa_key(); + const std::string& iv = provisioning_response.device_rsa_key_iv(); + const std::string& nonce = provisioning_response.nonce(); + const uint8_t* msg_rsa_key = message + GetOffset(signed_message, enc_rsa_key); + const uint8_t* msg_rsa_key_iv = message + GetOffset(signed_message, iv); + const uint32_t* msg_nonce = reinterpret_cast( + message + GetOffset(signed_message, nonce)); if (method == OEMCrypto_Keybox) { if (pair.fcn->RewrapDeviceRSAKey == nullptr) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } return pair.fcn->RewrapDeviceRSAKey( pair.session, message, message_length, signature, signature_length, - &pair.nonce, message + parsed_response.enc_private_key.offset, - parsed_response.enc_private_key.length, - message + parsed_response.enc_private_key_iv.offset, + msg_nonce, msg_rsa_key, enc_rsa_key.size(), msg_rsa_key_iv, wrapped_private_key, wrapped_private_key_length); } else if (method == OEMCrypto_OEMCertificate) { if (pair.fcn->RewrapDeviceRSAKey30 == nullptr) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } + const std::string& wrapping_key = (provisioning_response.has_wrapping_key()) + ? provisioning_response.wrapping_key() + : std::string(); + const uint8_t* msg_wrapping_key = + message + GetOffset(signed_message, wrapping_key); return pair.fcn->RewrapDeviceRSAKey30( - pair.session, &pair.nonce, - message + parsed_response.encrypted_message_key.offset, - parsed_response.encrypted_message_key.length, - message + parsed_response.enc_private_key.offset, - parsed_response.enc_private_key.length, - message + parsed_response.enc_private_key_iv.offset, - wrapped_private_key, wrapped_private_key_length); + pair.session, msg_nonce, msg_wrapping_key, wrapping_key.size(), + msg_rsa_key, enc_rsa_key.size(), msg_rsa_key_iv, wrapped_private_key, + wrapped_private_key_length); } else { // Otherwise, provisioning method does not support LoadProvisioning. LOGE("Backwards compatibility code invalid: %d", method); return OEMCrypto_ERROR_NOT_IMPLEMENTED; } } +// This function is still used by the unit tests when driving a v15 OEMCrypto. +// This is needed on Android because some devices have not upgraded to v16. extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( OEMCrypto_SESSION session, const uint32_t* nonce, const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, @@ -2233,6 +2245,8 @@ extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( wrapped_rsa_key_length); } +// This function is still used by the unit tests when driving a v15 OEMCrypto. +// This is needed on Android because some devices have not upgraded to v16. extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length, const uint32_t* nonce, diff --git a/libwvdrmengine/cdm/metrics/include/metrics_collections.h b/libwvdrmengine/cdm/metrics/include/metrics_collections.h index 2d24dc99..66389233 100644 --- a/libwvdrmengine/cdm/metrics/include/metrics_collections.h +++ b/libwvdrmengine/cdm/metrics/include/metrics_collections.h @@ -291,6 +291,8 @@ class CryptoMetrics { oemcrypto_load_renewal_; EventMetric oemcrypto_load_provisioning_; + ValueMetric oemcrypto_minor_api_version_; + ValueMetric oemcrypto_maximum_usage_table_header_size_; }; // This class contains session-scoped metrics. All properties and diff --git a/libwvdrmengine/cdm/metrics/src/metrics.proto b/libwvdrmengine/cdm/metrics/src/metrics.proto index 5ed05753..c220746c 100644 --- a/libwvdrmengine/cdm/metrics/src/metrics.proto +++ b/libwvdrmengine/cdm/metrics/src/metrics.proto @@ -93,7 +93,7 @@ message WvCdmMetrics { // This contains metrics that were captured at the CryptoSession level. These // include CryptoSession metrics and most OEMCrypto metrics. - // next id: 83 + // next id: 85 message CryptoMetrics { // Crypto Session Metrics. optional ValueMetric crypto_session_security_level = 1; @@ -190,6 +190,8 @@ message WvCdmMetrics { repeated DistributionMetric oemcrypto_load_license_time_us = 80; repeated DistributionMetric oemcrypto_load_renewal_time_us = 81; repeated DistributionMetric oemcrypto_load_provisioning_time_us = 82; + optional ValueMetric oemcrypto_minor_api_version = 83; + optional ValueMetric oemcrypto_maximum_usage_table_header_size = 84; } // This contains metrics that were captured within a CdmSession. This contains diff --git a/libwvdrmengine/cdm/metrics/src/metrics_collections.cpp b/libwvdrmengine/cdm/metrics/src/metrics_collections.cpp index a159c54d..956266fa 100644 --- a/libwvdrmengine/cdm/metrics/src/metrics_collections.cpp +++ b/libwvdrmengine/cdm/metrics/src/metrics_collections.cpp @@ -209,6 +209,10 @@ void CryptoMetrics::Serialize(WvCdmMetrics::CryptoMetrics *crypto_metrics) crypto_metrics->mutable_oemcrypto_load_renewal_time_us()); oemcrypto_load_provisioning_.ToProto( crypto_metrics->mutable_oemcrypto_load_provisioning_time_us()); + crypto_metrics->set_allocated_oemcrypto_minor_api_version( + oemcrypto_minor_api_version_.ToProto()); + crypto_metrics->set_allocated_oemcrypto_maximum_usage_table_header_size( + oemcrypto_maximum_usage_table_header_size_.ToProto()); } SessionMetrics::SessionMetrics() : session_id_(""), completed_(false) {} diff --git a/libwvdrmengine/cdm/metrics/test/metrics_collections_unittest.cpp b/libwvdrmengine/cdm/metrics/test/metrics_collections_unittest.cpp index 24dcb428..527bff45 100644 --- a/libwvdrmengine/cdm/metrics/test/metrics_collections_unittest.cpp +++ b/libwvdrmengine/cdm/metrics/test/metrics_collections_unittest.cpp @@ -439,6 +439,8 @@ TEST_F(CryptoMetricsTest, AllCryptoMetrics) { crypto_metrics.oemcrypto_set_decrypt_hash_ .Increment(OEMCrypto_ERROR_INIT_FAILED); crypto_metrics.oemcrypto_resource_rating_tier_.Record(123); + crypto_metrics.oemcrypto_minor_api_version_.Record(234); + crypto_metrics.oemcrypto_maximum_usage_table_header_size_.Record(321); WvCdmMetrics::CryptoMetrics actual; crypto_metrics.Serialize(&actual); @@ -527,6 +529,9 @@ TEST_F(CryptoMetricsTest, AllCryptoMetrics) { EXPECT_EQ("sandbox", actual.oemcrypto_set_sandbox().string_value()); EXPECT_GT(actual.oemcrypto_set_decrypt_hash_size(), 0); EXPECT_EQ(123, actual.oemcrypto_resource_rating_tier().int_value()); + EXPECT_EQ(234, actual.oemcrypto_minor_api_version().int_value()); + EXPECT_EQ(321, + actual.oemcrypto_maximum_usage_table_header_size().int_value()); } } // namespace metrics diff --git a/libwvdrmengine/docs/License_Duration_and_Renewal.pdf b/libwvdrmengine/docs/License_Duration_and_Renewal.pdf index ef6dca59..832960b3 100644 Binary files a/libwvdrmengine/docs/License_Duration_and_Renewal.pdf and b/libwvdrmengine/docs/License_Duration_and_Renewal.pdf differ diff --git a/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v14.pdf b/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v14.pdf deleted file mode 100644 index 99396c78..00000000 Binary files a/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v14.pdf and /dev/null differ diff --git a/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf b/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf index 6894bd86..90e78a42 100644 Binary files a/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf and b/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf differ diff --git a/libwvdrmengine/docs/Widevine_Core_Message_Serialization.pdf b/libwvdrmengine/docs/Widevine_Core_Message_Serialization.pdf index 9b7af8b3..47bca117 100644 Binary files a/libwvdrmengine/docs/Widevine_Core_Message_Serialization.pdf and b/libwvdrmengine/docs/Widevine_Core_Message_Serialization.pdf differ diff --git a/libwvdrmengine/docs/Widevine_Modular_DRM_Version_16_Delta.pdf b/libwvdrmengine/docs/Widevine_Modular_DRM_Version_16_Delta.pdf index b97af8d4..d75e0733 100644 Binary files a/libwvdrmengine/docs/Widevine_Modular_DRM_Version_16_Delta.pdf and b/libwvdrmengine/docs/Widevine_Modular_DRM_Version_16_Delta.pdf differ diff --git a/libwvdrmengine/docs/Widevine_OEMCrypto_Version_Compatibility.pdf b/libwvdrmengine/docs/Widevine_OEMCrypto_Version_Compatibility.pdf index 301fb66b..b18975e5 100644 Binary files a/libwvdrmengine/docs/Widevine_OEMCrypto_Version_Compatibility.pdf and b/libwvdrmengine/docs/Widevine_OEMCrypto_Version_Compatibility.pdf differ diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index c54d6ea1..f50ce0eb 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -147,13 +147,22 @@ typedef struct { * OEMCrypto_SubSampleDescription Structure * * Description: - * This structure is used as parameters in the OEMCrypto_DecryptCENC function. + * This structure is used as parameters in the OEMCrypto_DecryptCENC + * function. In the DASH specification, a sample is composed of multiple + * samples, and each subsample is composed of two regions. The first region + * is clear unprotected data. We also call this clear data or unencrypted + * data. Immediately following the clear region is the protected region. The + * protected region is encrypted or encrypted with a pattern. The pattern and + * number of bytes that are encrypted in the protected region is discussed in + * this document when we talk about the function OEMCryptoDecryptCENC. For + * historic reasons, this document also calls the protected region the + * encrypted region. * * Fields: - * [in] num_bytes_clear: The number of clear bytes in this subsample. The - * clear bytes come before the encrypted bytes. - * [in] num_bytes_encrypted: The number of encrypted bytes in this subsample. - * The encrypted bytes come after the clear bytes. + * [in] num_bytes_clear: The number of unprotected bytes in this subsample. + * The clear bytes come before the encrypted bytes. + * [in] num_bytes_encrypted: The number of protected bytes in this subsample. + * The protected bytes come after the clear bytes. * [in] subsample_flags: bitwise flags indicating if this is the first, * middle, or last subsample in a sample. 1 = first subsample, 2 = last * subsample, 3 = both first and last subsample, 0 = neither first nor last @@ -231,14 +240,6 @@ typedef enum OEMCryptoCipherMode { OEMCrypto_CipherMode_CBC, } OEMCryptoCipherMode; -/** OEMCrypto_LicenseType is used in LoadKeys to indicate if the key objects - * are for content keys, or for entitlement keys. - */ -typedef enum OEMCrypto_LicenseType { - OEMCrypto_ContentLicense = 0, - OEMCrypto_EntitlementLicense = 1 -} OEMCrypto_LicenseType; - /* * OEMCrypto_EntitledContentKeyObject * Contains encrypted content key data for loading into the sessions keytable. @@ -358,12 +359,6 @@ typedef enum OEMCrypto_ProvisioningMethod { OEMCrypto_OEMCertificate = 3 // Device has factory installed OEM certificate. } OEMCrypto_ProvisioningMethod; -/* Private key type used in OEMCrypto_LoadDRMPrivateKey. */ -typedef enum OEMCrypto_PrivateKeyType { - OEMCrypto_RSA_Private_Key, - OEMCrypto_ECC_Private_Key, -} OEMCrypto_PrivateKeyType; - /* * Flags indicating public/private key types supported. */ @@ -833,6 +828,11 @@ OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( * the function ODK_SetNonceValue(&nonce_values, nonce). The ODK functions * are documented in "Widevine Core Message Serialization". * + * This function shall only be called at most once per open session. It shall + * only be called before signing either a provisioning request or a license + * request. If an attempt is made to generate a nonce while in the wrong + * state, an error of OEMCrypto_ERROR_INVALID_CONTEXT is returned. + * * Parameters: * [in] session: handle for the session to be used. * [out] nonce: pointer to memory to receive the computed nonce. @@ -957,10 +957,12 @@ OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( * key. The entire message is the buffer starting at message with length * message_length. * - * If nonce_values.api_level is 15, then OEMCrypto shall compute the + * If nonce_values.api_major_version is 15, then OEMCrypto shall compute the * signature of the message body using the session's client renewal mac key. * The message body is the buffer starting at message+core_message_size with - * length message_length-core_message_size. + * length message_length-core_message_size. If the session has not had a + * license loaded, it will use the usage entries client mac key to sign the + * message body. * * This function generates a HMAC-SHA256 signature using the mac_key[client] * for license request signing under the license server protocol for CENC. @@ -1162,7 +1164,8 @@ OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); * The mac_key and encrypt_key were generated and stored by the previous call * to OEMCrypto_GenerateDerivedKeys() or * OEMCrypto_DeriveKeysFromSessionKey(). The nonce was generated and stored - * by the previous call to OEMCrypto_GenerateNonce(). + * in the session's nonce_values by the previous call to + * OEMCrypto_GenerateNonce(). * * This session's elapsed time clock is started at 0. The clock will be used * in OEMCrypto_DecryptCENC(). @@ -1492,10 +1495,13 @@ OEMCryptoResult OEMCrypto_LoadKeys( * OEMCrypto_ERROR_LICENSE_INACTIVE is returned. * 24. The data in enc_mac_keys_iv is not identical to the 16 bytes before * enc_mac_keys. If it is, return OEMCrypto_ERROR_INVALID_CONTEXT. + * * Usage Table and Provider Session Token (pst) + * The function ODK_ParseLicense takes several parameters that may need more + * explanation. * The parameter usage_entry_present shall be set to true if a usage entry - * was created or loaded for this session. This parameter is passed into - * ODK_ParseLicense and used for usage entry verification. + * was created or loaded for this session. This parameter is used by + * ODK_ParseLicense for usage entry verification. * The parameter initial_license_load shall be false if the usage entry was * loaded. If there is no usage entry or if the usage entry was created with * OEMCrypto_CreateNewUsageEntry, then initial_license_load shall be true. @@ -1712,6 +1718,29 @@ OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( * signature verification shall use a constant-time algorithm (a signature * mismatch will always take the same time as a successful comparison). * + * The key control from the first OEMCrypto_KeyRefreshObject in the key_array + * shall be extracted. If it is encrypted, as described below, it shall be + * decrypted. The duration from the key control shall be extracted and + * converted to host byte order. This duration shall be passed to the + * function ODK_RefreshV15Values as the parameter new_key_duration. + * + * If the KeyRefreshObject's key_control_iv has zero length, then the + * key_control is not encrypted. If the key_control_iv is specified, then + * key_control is encrypted with the first 128 bits of the corresponding + * content key. + * + * If the KeyRefreshObject's key_id has zero length, then it is an error for + * the key_control_iv to have nonzero length. OEMCrypto shall return an error + * of OEMCrypto_ERROR_INVALID_CONTEXT. + * + * If the session's license_type is OEMCrypto_ContentLicense, and the + * KeyRefreshObject's key_id is not null, then the entry in the keytable with + * the matching content_key_id is used. + * + * If the session's license_type is OEMCrypto_EntitlementLicense, and the + * KeyRefreshObject's key_id is not null, then the entry in the keytable with + * the matching entitlment_key_id is used. + * * The function ODK_RefreshV15Values shall be called to update the clock * values. See the document "Widevine Core Message Serialization" for the * documentation of the ODK library functions. @@ -1782,19 +1811,14 @@ OEMCryptoResult OEMCrypto_RefreshKeys( * Updates the clock values and resets the renewal timer for the current * session. * - * OEMCrypto shall verify the signature of the message using the session's - * renewal mac key for the server. If the signature does not match, OEMCrypto - * returns OEMCrypto_ERROR_SIGNATURE_FAILURE. + * OEMCrypto shall verify the signature of the entire message using the + * session's renewal mac key for the server. The entire message is the buffer + * starting at message with length message_length. If the signature does not + * match, OEMCrypto returns OEMCrypto_ERROR_SIGNATURE_FAILURE. * - * If nonce_values.api_level is 16, then OEMCrypto shall verify the signature - * of the entire message using the session's server renewal mac key. The - * entire message is the buffer starting at message with length - * message_length. - * - * If nonce_values.api_level is 15, then OEMCrypto shall compute the - * signature of the message body using the session's server renewal mac key. - * The entire message is the buffer starting at message+core_message_size - * with length message_length-core_message_size. + * OEMCrypto shall verify that nonce_values.api_major_version is 16. If not, + * return the error OEMCrypto_ERROR_INVALID_CONTEXT. Legacy licenses will use + * the function OEMCrypto_RefreshKeys instead of OEMCrypto_LoadRenewal. * * If the signature passes, OEMCrypto shall use the function * ODK_ParseRenewal, as described in the document "Widevine Core Message @@ -2178,51 +2202,34 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * The 'cbcs' scheme is OEMCrypto_CipherMode_CBC with an encryption pattern. * Only some of the bytes in the encrypted portion of each subsample are * encrypted. In the pattern parameter, the encrypt and skip fields will - * usually be non-zero. This mode allows devices to decrypt FMP4 HLS content - * and SAMPLE-AES HLS content. + * usually be non-zero. This mode allows devices to decrypt FMP4 HLS content, + * SAMPLE-AES HLS content, as well as content using the DASH 'cbcs' scheme. * * The skip field of OEMCrypto_CENCEncryptPatternDesc may also be zero. If * the skip field is zero, then patterns are not in use and all crypto blocks - * in the encrypted subsample are encrypted. It is not valid for the encrypt - * field to be zero. + * in the encrypted part of the subsample are encrypted. It is not valid for + * the encrypt field to be zero. * * The length of a crypto block in AES-128 is 16 bytes. In the 'cbcs' scheme, - * if an encrypted subsample has a length that is not a multiple of 16 bytes, - * then the final bytes that do not make up a full crypto block should be - * treated as clear and should not be decrypted. The following diagram - * provides an example: + * if the encrypted part of a subsample has a length that is not a multiple + * of 16 bytes, then the final bytes that do not make up a full crypto block + * are clear and should never be decrypted. The following diagram provides an + * example: * * (See drawing in "Widevine Modular DRM Security Integration Guide") * + * Whether any given protected block is actually encrypted also depends on + * the pattern. But the bytes at the end that do not make up a full crypto + * block will never be encrypted, regardless of what the pattern is. Even if + * the pattern says to decrypt every protected block, these bytes are clear + * and should not be decrypted. + * * Of course, if the encrypted subsample has a length that is a multiple of - * 16 bytes, the final bytes should be decrypted. The following diagram - * provides an example: + * 16 bytes, all the bytes in it are protected, and they may need to be + * decrypted following the pattern. The following diagram provides an example: * * (See drawing in "Widevine Modular DRM Security Integration Guide") * - * If the encrypted subsample has a length that is not an even multiple of - * the pattern length, then there may also be extra clear blocks at the end. - * - * If there are not enough bytes at the end of the encrypted subsample to - * complete an iteration of the encrypted part of the pattern, even if there - * are enough bytes to make a full crypto block, then the final bytes that do - * not fill the encrypted part of the pattern should be treated as clear and - * should not be decrypted. The following diagram provides an example: - * - * (See drawing in "Widevine Modular DRM Security Integration Guide") - * - * If there are enough bytes at the end of the encrypted subsample to - * complete an iteration of the encrypted part of the pattern, even if there - * are not enough bytes to complete the clear part of the pattern, then the - * bytes that fill the encrypted part of the pattern should be treated as - * encrypted. The following diagram provides an example: - * - * (See drawing in "Widevine Modular DRM Security Integration Guide") - * - * This behavior is specified by the ISO-CENC standard. Refer to the ISO-CENC - * standard, section 9.6, for full details of how patterns are to be applied - * to content. - * * INITIALIZATION VECTOR BETWEEN SUBSAMPLES: * * The IV is specified for the initial subsample in a sample in the iv field @@ -2491,7 +2498,6 @@ OEMCryptoResult OEMCrypto_DecryptCENC( * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Buffer Sizes: - * OEMCrypto shall support subsample sizes and total input buffer sizes as * specified by its resource rating tier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is @@ -3235,7 +3241,7 @@ uint32_t OEMCrypto_APIVersion(void); * number allows the calling application to avoid version mis-match errors, * because this API is part of a shared library. * - * The minor version specified in this document is 1. Any OEM that returns + * The minor version specified in this document is 2. Any OEM that returns * this version number guarantees it passes all unit tests associated with * this version. * @@ -3762,13 +3768,6 @@ uint32_t OEMCrypto_GetAnalogOutputFlags(void); * sessions with 4 keys each (80 total), but it does not need to support 20 * sessions with 20 keys each. * - * Living room devices refer to devices such as TVs, set top boxes, game - * consoles, blu-ray players etc. These devices tend to have reduced UI and - * frequently are dedicated to playing video. For these devices, we expect - * there to be more memory dedicated to video playback and video - * applications. The number of sessions required for living room devices is - * larger than for mobile devices. - * * The message size that is needed for a license with a large number of keys * is larger than in previous versions. The message size limit applies to all * functions that sign or verify messages. It also applies to the size of @@ -3780,11 +3779,14 @@ uint32_t OEMCrypto_GetAnalogOutputFlags(void); * should also support a higher frame rate. Platforms may enforce these * values. For example Android will enforce a frame rate via a GTS test. * + * Note on units: We will use KiB to mean 1024 bytes and MiB to mean 1024 KiB, + * as described at https://en.wikipedia.org/wiki/Kibibyte. + * * +--------------------------------+---------+----------+---------+---------+ * |Resource Rating Tier |1 - Low |2 - Medium|3 - High |4 - Very | * | | | | | High | * +--------------------------------+---------+----------+---------+---------+ - * |Minimum Sample size |1 MB |2 MB |4 MB |16 MB | + * |Minimum Sample size |1 MiB |2 MiB |4 MiB |16 MiB | * +--------------------------------+---------+----------+---------+---------+ * |Minimum Number of Subsamples |10 |16 |32 |64 | * | (H264 or HEVC) | | | | | @@ -3795,16 +3797,12 @@ uint32_t OEMCrypto_GetAnalogOutputFlags(void); * |Minimum Number of Subsamples |72 |144 |288 |576 | * |(AV1) | | | | | * +--------------------------------+---------+----------+---------+---------+ - * |Minimum subsample buffer size |100 KB |500 KB |1 MB |4 MB | + * |Minimum subsample buffer size |100 KiB |500 KiB |1 MiB |4 MiB | * +--------------------------------+---------+----------+---------+---------+ - * |Minimum Generic crypto buffer |10 KB |100 KB |500 KB |1 MB | + * |Minimum Generic crypto buffer |10 KiB |100 KiB |500 KiB |1 MiB | * |size | | | | | * +--------------------------------+---------+----------+---------+---------+ - * |Minimum number of open sessions |10 |20 |20 |30 | - * |(mobile devices) | | | | | - * +--------------------------------+---------+----------+---------+---------+ - * |Minimum number of open sessions |10 |100 |100 |100 | - * |(living room devices) | | | | | + * |Minimum number of open sessions |10 |20 |30 |40 | * +--------------------------------+---------+----------+---------+---------+ * |Minimum number of keys per |4 |20 |20 |30 | * |session | | | | | @@ -4304,6 +4302,11 @@ OEMCryptoResult OEMCrypto_CreateNewUsageEntry(OEMCrypto_SESSION session, * usage entry associated with it, the error * OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES is returned. * + * Before version API 16, the usage entry stored the time that the license + * was loaded. This value is now interpreted as the time that the licence + * request was signed. This can be achieved by simply renaming the field and + * using the same value when reloading an older entry. + * * Parameters: * [in] session: handle for the session to be used. * [in] usage_entry_number: index of existing usage entry. @@ -4522,6 +4525,10 @@ OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session, * HMAC SHA1 signature is used to prevent a rogue application from using * OMECrypto_GenerateSignature to forge a Usage Report. * + * Before version 16 of this API, seconds_since_license_received was reported + * instead of seconds_since_license_signed. For any practical bookkeeping + * purposes, these events are essentially at the same time. + * * Devices that do not implement a Session Usage Table may return * OEMCrypto_ERROR_NOT_IMPLEMENTED. * @@ -4985,7 +4992,6 @@ OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(OEMCrypto_SESSION session, /****************************************************************************/ /****************************************************************************/ - #ifdef __cplusplus } #endif diff --git a/libwvdrmengine/oemcrypto/odk/Android.bp b/libwvdrmengine/oemcrypto/odk/Android.bp index bbbe288a..7aa8eb0a 100644 --- a/libwvdrmengine/oemcrypto/odk/Android.bp +++ b/libwvdrmengine/oemcrypto/odk/Android.bp @@ -1,4 +1,6 @@ - +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. // ---------------------------------------------------------------- // Builds libwv_odk.a, The ODK Library (libwv_odk) is used by @@ -16,6 +18,7 @@ cc_library_static { "src/odk_overflow.c", "src/odk_serialize.c", "src/odk_timer.c", + "src/odk_util.c", "src/serialization_base.c", ], proprietary: true, diff --git a/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h b/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h index 53aba87b..e8f2ad90 100644 --- a/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h +++ b/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h @@ -106,6 +106,21 @@ typedef enum OEMCrypto_Usage_Entry_Status { kInactiveUnused = 4, } OEMCrypto_Usage_Entry_Status; +/* + * OEMCrypto_LicenseType is used in the license message to indicate if the key + * objects are for content keys, or for entitlement keys. + */ +typedef enum OEMCrypto_LicenseType { + OEMCrypto_ContentLicense = 0, + OEMCrypto_EntitlementLicense = 1 +} OEMCrypto_LicenseType; + +/* Private key type used in the provisioning response. */ +typedef enum OEMCrypto_PrivateKeyType { + OEMCrypto_RSA_Private_Key = 0, + OEMCrypto_ECC_Private_Key = 1, +} OEMCrypto_PrivateKeyType; + /* * OEMCrypto_Substring * diff --git a/libwvdrmengine/oemcrypto/odk/include/core_message_serialize.h b/libwvdrmengine/oemcrypto/odk/include/core_message_serialize.h index 38c5c51f..ffa43f5b 100644 --- a/libwvdrmengine/oemcrypto/odk/include/core_message_serialize.h +++ b/libwvdrmengine/oemcrypto/odk/include/core_message_serialize.h @@ -30,10 +30,12 @@ namespace serialize { * Parameters: * [in] parsed_lic * [in] core_request + * [in] core_request_sha256 * [out] oemcrypto_core_message */ bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, std::string* oemcrypto_core_message); /** @@ -41,9 +43,11 @@ bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, * * Parameters: * [in] core_request + * [in] renewal_duration_seconds * [out] oemcrypto_core_message */ bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, std::string* oemcrypto_core_message); /** diff --git a/libwvdrmengine/oemcrypto/odk/include/core_message_serialize_proto.h b/libwvdrmengine/oemcrypto/odk/include/core_message_serialize_proto.h index 9b7cdad2..0f494de8 100644 --- a/libwvdrmengine/oemcrypto/odk/include/core_message_serialize_proto.h +++ b/libwvdrmengine/oemcrypto/odk/include/core_message_serialize_proto.h @@ -30,13 +30,16 @@ namespace serialize { * * Parameters: * [in] serialized_license - * serialized video_widevine::License - * [in] core_request - * [out] oemcrypto_core_message + serialized video_widevine::License + * [in] core_request oemcrypto core message from request. + * [in] core_request_sha256 - hash of serialized core request. + * [in] nonce_required - if the device should require a nonce match. + * [out] oemcrypto_core_message - the serialized oemcrypto core response. */ bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, const ODK_LicenseRequest& core_request, const std::string& core_request_sha256, + const bool nonce_required, std::string* oemcrypto_core_message); /** diff --git a/libwvdrmengine/oemcrypto/odk/include/core_message_types.h b/libwvdrmengine/oemcrypto/odk/include/core_message_types.h index 2b53aab8..488e5d21 100644 --- a/libwvdrmengine/oemcrypto/odk/include/core_message_types.h +++ b/libwvdrmengine/oemcrypto/odk/include/core_message_types.h @@ -64,7 +64,8 @@ namespace oemcrypto_core_message { * Input structure for CreateCoreLicenseResponse */ struct ODK_LicenseRequest { - uint32_t api_version; + uint16_t api_minor_version; + uint16_t api_major_version; uint32_t nonce; uint32_t session_id; }; @@ -74,7 +75,8 @@ struct ODK_LicenseRequest { * Input structure for CreateCoreRenewalResponse */ struct ODK_RenewalRequest { - uint32_t api_version; + uint16_t api_minor_version; + uint16_t api_major_version; uint32_t nonce; uint32_t session_id; uint64_t playback_time_seconds; @@ -85,7 +87,8 @@ struct ODK_RenewalRequest { * Input structure for CreateCoreProvisioningResponse */ struct ODK_ProvisioningRequest { - uint32_t api_version; + uint16_t api_minor_version; + uint16_t api_major_version; uint32_t nonce; uint32_t session_id; std::string device_id; diff --git a/libwvdrmengine/oemcrypto/odk/include/odk.h b/libwvdrmengine/oemcrypto/odk/include/odk.h index c6db79d5..6bbf36fb 100644 --- a/libwvdrmengine/oemcrypto/odk/include/odk.h +++ b/libwvdrmengine/oemcrypto/odk/include/odk.h @@ -65,7 +65,7 @@ extern "C" { * [out] timer_limits: the session's timer limits. * [out] clock_values: the session's clock values. * [out] nonce_values: the session's ODK nonce values. - * [in] api_version: the API version of OEMCrypto. + * [in] api_major_version: the API version of OEMCrypto. * [in] session_id: the session id of the newly created session. * * Returns: @@ -78,7 +78,7 @@ extern "C" { OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, - uint32_t api_version, + uint32_t api_major_version, uint32_t session_id); /* @@ -127,7 +127,9 @@ OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, * * Description: * This function sets the values in the clock_values structure. It shall be - * called from OEMCrypto_LoadUsageEntry. + * called from OEMCrypto_LoadUsageEntry. When a usage entry from a v15 or + * earlier license is loaded, the value time_of_license_loaded shall be used + * in place of time_of_license_signed. * * Parameters: * [in/out] clock_values: the session's clock data. @@ -231,7 +233,7 @@ OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, * * Description: * This function modifies the session's clock values to indicate that the - * license has been deactiviated. It shall be called from + * license has been deactivated. It shall be called from * OEMCrypto_DeactivateUsageEntry * * Parameters: @@ -256,8 +258,8 @@ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values); * * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignLicenseRequest. * - * NOTE: if message pointer is null and/or input core_message_size is zero, - * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * core_message_size to the size needed. * * Parameters: @@ -271,7 +273,7 @@ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values); * * Returns: * OEMCrypto_SUCCESS - * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small * OEMCrypto_ERROR_INVALID_CONTEXT * * Version: @@ -292,8 +294,14 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest( * * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignRenewalRequest. * - * NOTE: if message pointer is null and/or input core_message_size is zero, - * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * If status in clock_values indicates that a license has not been loaded, + * then this is a license release. The ODK library will change the value of + * nonce_values.api_major_version to 15. This will make + * OEMCrypto_PrepAndSignRenewalRequest sign just the message body, as it does + * for all legacy licenses. + * + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * core_message_size to the size needed. * * Parameters: @@ -303,31 +311,33 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest( * [in/out] core_message_size: length of the core message at the beginning of * the message. (in) size of buffer reserved for the core message, in * bytes. (out) actual length of the core message, in bytes. - * [in] nonce_values: pointer to the session's nonce data. - * [in] clock_values: the session's clock values. + * [in/out] nonce_values: pointer to the session's nonce data. + * [in/out] clock_values: the session's clock values. * [in] system_time_seconds: the current time on OEMCrypto's clock, in * seconds. * * Returns: * OEMCrypto_SUCCESS - * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small * OEMCrypto_ERROR_INVALID_CONTEXT * * Version: * This method is new in version 16 of the API. */ -OEMCryptoResult ODK_PrepareCoreRenewalRequest( - uint8_t* message, size_t message_length, size_t* core_message_size, - const ODK_NonceValues* nonce_values, ODK_ClockValues* clock_values, - uint64_t system_time_seconds); +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds); /* * ODK_PrepareCoreProvisioningRequest * * Description: * Modifies the message to include a core provisioning request at the - * beginning of the message buffer. The values in nonce_values, clock_values - * and system_time_seconds are used to populate the message. + * beginning of the message buffer. The values in nonce_values are used to + * populate the message. * * This shall be called by OEMCrypto from * OEMCrypto_PrepAndSignProvisioningRequest. @@ -336,8 +346,8 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest( * OEMCrypto_GetDeviceID. The device ID shall be unique to the device, and * stable across reboots and factory resets for an L1 device. * - * NOTE: if message pointer is null and/or input core_message_size is zero, - * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * core_message_size to the size needed. * * Parameters: @@ -356,7 +366,7 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest( * * Returns: * OEMCrypto_SUCCESS - * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small * OEMCrypto_ERROR_INVALID_CONTEXT * * Version: @@ -373,7 +383,7 @@ OEMCryptoResult ODK_PrepareCoreProvisioningRequest( * Description: * This function sets all limits in the timer_limits struct to the * key_duration and initializes the other values. The field - * nonce_values.api_level will be set to 15. It shall be called from + * nonce_values.api_major_version will be set to 15. It shall be called from * OEMCrypto_LoadKeys when loading a legacy license. * * Parameters: @@ -403,8 +413,12 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, * ODK_RefreshV15Values * * Description: - * This function updates the clock_values as needed if the renewal is - * accepted. The field nonce_values.api_level is verified to be 15. + * This function updates the clock_values as needed if a v15 renewal is + * accepted. The field nonce_values.api_major_version is verified to be 15. + * + * This is called from OEMCrypto_RefreshKeys for a valid license renewal. + * OEMCrypto shall pass in the current system time, and the key duration from + * the first object in the OEMCrypto_KeyRefreshObject. * * Parameters: * [in] timer_limits: The session's timer limits. @@ -412,6 +426,8 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, * [in] nonce_values: The session's ODK nonce values. * [in] system_time_seconds: The current time on the system clock, as * described in the document "License Duration and Renewal". + * [in] new_key_duration: The duration from the first + * OEMCrypto_KeyRefreshObject in key_array. * [out] timer_value: set to the new timer value. Only used if the return * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a * hardware timer. @@ -432,27 +448,43 @@ OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, const ODK_NonceValues* nonce_values, uint64_t system_time_seconds, + uint32_t new_key_duration, uint64_t* timer_value); /* * ODK_ParseLicense * * Description: - * The function ODK_ParseLicense will parse the message and verify - * - * 1. Either the nonce matches the one passed in or the license does not - * require a nonce. - * 2. The API version of the message matches. - * 3. The session id matches. - * The function ODK_ParseLicense will parse the message and set each - * substring pointer to point to a location in the message body. The message - * body is the buffer starting at message + core_message_length with size - * message_length - core_message_length. + * The function ODK_ParseLicense will parse the message and verify fields in + * the message. * * If the message does not parse correctly, ODK_VerifyAndParseLicense will * return ODK_ERROR_CORE_MESSAGE that OEMCrypto should return to the CDM - * layer above. If the api in the message is larger than 16, then - * ODK_UNSUPPORTED_API is returned. + * layer above. + * + * If the API in the message is not 16, then ODK_UNSUPPORTED_API is returned. + * + * If initial_license_load is true, and nonce_required in the license is + * true, then the ODK library shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. If + * verification fails, then it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * If initial_license_load is false, and nonce_required is true, then + * ODK_ParseLicense will set the values in nonce_values from those in the + * message. + * + * The function ODK_ParseLicense will verify that each substring points to a + * location in the message body. The message body is the buffer starting at + * message + core_message_length with size message_length - + * core_message_length. + * + * If initial_license_load is true, then ODK_ParseLicense shall verify that + * the parameter request_hash matches request_hash in the parsed license. If + * verification fails, then it shall return ODK_ERROR_CORE_MESSAGE. This was + * computed by OEMCrypto when the license was requested. + * + * If usage_entry_present is true, then ODK_ParseLicense shall verify that + * the pst in the license has a nonzero length. * * Parameters: * [in] message: pointer to the message buffer. @@ -463,6 +495,8 @@ OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, * false when called for OEMCrypto_ReloadLicense. * [in] usage_entry_present: true if the session has a new usage entry * associated with it created via OEMCrypto_CreateNewUsageEntry. + * [in] request_hash: the hash of the license request core message. This was + * computed by OEMCrypto when the license request was signed. * [in/out] timer_limits: The session's timer limits. These will be updated. * [in/out] clock_values: The session's clock values. These will be updated. * [in/out] nonce_values: The session's nonce values. These will be updated. @@ -470,7 +504,7 @@ OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, * * Returns: * OEMCrypto_SUCCESS - * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there + * ODK_ERROR_CORE_MESSAGE: if the message did not parse correctly, or there * were other incorrect values. An error should be returned to the CDM * layer. * ODK_UNSUPPORTED_API @@ -490,9 +524,12 @@ OEMCryptoResult ODK_ParseLicense( * ODK_ParseRenewal * * Description: - * The function ODK_ParseRenewal will parse the message and verify that the - * nonce values match those in the license. If the message does not parse - * correctly, an error of ODK_ERROR_CORE_MESSAGE is returned. + * The function ODK_ParseRenewal will parse the message and verify its + * contents. If the message does not parse correctly, an error of + * ODK_ERROR_CORE_MESSAGE is returned. + * + * ODK_ParseRenewal shall verify that all fields in nonce_values match those + * in the license. Otherwise it shall return OEMCrypto_ERROR_INVALID_NONCE. * * After parsing the message, this function updates the clock_values based on * the timer_limits and the current system time. If playback may not @@ -504,8 +541,8 @@ OEMCryptoResult ODK_ParseLicense( * ODK_DISABLE_TIMER, then playback time is not limited. * * If OEMCrypto uses a hardware timer, and this function returns - * ODK_SET_TIMER, then the timer should be set to the value pointed to by - * timer_value. + * ODK_SET_TIMER, then OEMCrypto shall set the timer to the value pointed to + * by timer_value. * * Parameters: * [in] message: pointer to the message buffer. @@ -522,15 +559,16 @@ OEMCryptoResult ODK_ParseLicense( * hardware timer. * * Returns: - * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there - * were other incorrect values. An error should be returned to the CDM - * layer. + * ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there were + * other incorrect values. An error should be returned to the CDM layer. * ODK_SET_TIMER: Success. The timer should be reset to the specified timer * value. * ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is * allowed. * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. * ODK_UNSUPPORTED_API + * ODK_STALE_RENEWAL: This renewal is not the most recently signed. It is + * rejected. * OEMCrypto_ERROR_INVALID_NONCE * * Version: @@ -551,14 +589,21 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * The function ODK_ParseProvisioning will parse the message and verify the * nonce values match those in the license. * - * The function ODK_ParseProvisioning will parse the message and set each - * substring pointer to point to a location in the message body. The message - * body is the buffer starting at message + core_message_length with size - * message_length - core_message_length. - * * If the message does not parse correctly, ODK_ParseProvisioning will return * an error that OEMCrypto should return to the CDM layer above. * + * If the API in the message is larger than 16, then ODK_UNSUPPORTED_API is + * returned. + * + * ODK_ParseProvisioning shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. Otherwise + * it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * The function ODK_ParseProvisioning will verify that each substring points + * to a location in the message body. The message body is the buffer starting + * at message + core_message_length with size message_length - + * core_message_length. + * * Parameters: * [in] message: pointer to the message buffer. * [in] message_length: length of the entire message buffer. @@ -572,9 +617,8 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * * Returns: * OEMCrypto_SUCCESS - * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there - * were other incorrect values. An error should be returned to the CDM - * layer. + * ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there were + * other incorrect values. An error should be returned to the CDM layer. * ODK_UNSUPPORTED_API * OEMCrypto_ERROR_INVALID_NONCE * diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_assert.h b/libwvdrmengine/oemcrypto/odk/include/odk_assert.h deleted file mode 100644 index e1a21fd0..00000000 --- a/libwvdrmengine/oemcrypto/odk/include/odk_assert.h +++ /dev/null @@ -1,27 +0,0 @@ - -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_ASSERT_H_ -#define ODK_ASSERT_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#if (__STDC_VERSION__ >= 201112L) -# include -# define odk_static_assert static_assert -#else -# define odk_static_assert(msg, e) \ - enum { odk_static_assert = 1 / (!!((msg) && (e))) }; -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* ODK_ASSERT_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_overflow.h b/libwvdrmengine/oemcrypto/odk/include/odk_overflow.h deleted file mode 100644 index 32aebe76..00000000 --- a/libwvdrmengine/oemcrypto/odk/include/odk_overflow.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_OVERFLOW_H_ -#define ODK_OVERFLOW_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef __has_builtin -# define __has_builtin(x) 0 -#endif - -#if (defined(__GNUC__) && __GNUC__ >= 5) || \ - __has_builtin(__builtin_add_overflow) -# define odk_sub_overflow_u64 __builtin_sub_overflow -# define odk_add_overflow_u64 __builtin_add_overflow -# define odk_add_overflow_ux __builtin_add_overflow -#else -int odk_sub_overflow_u64(uint64_t a, uint64_t b, uint64_t* c); -int odk_add_overflow_u64(uint64_t a, uint64_t b, uint64_t* c); -int odk_add_overflow_ux(size_t a, size_t b, size_t* c); -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* ODK_OVERFLOW_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_serialize.h b/libwvdrmengine/oemcrypto/odk/include/odk_serialize.h deleted file mode 100644 index c9488d71..00000000 --- a/libwvdrmengine/oemcrypto/odk/include/odk_serialize.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -/* - * This code is auto-generated, do not edit - */ -#ifndef ODKITEE_SERIALIZER_H_ -#define ODKITEE_SERIALIZER_H_ - -#include "odk_structs_priv.h" -#include "serialization_base.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* odk pack */ -void Pack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense const* obj); -void Pack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage const* obj); -void Pack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage const* obj); - -/* odk unpack */ -void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj); -void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj); -void Unpack_ODK_ProvisioningResponse(Message* msg, - ODK_ProvisioningResponse* obj); - -/* kdo pack */ -void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj); -void Pack_ODK_ProvisioningResponse(Message* msg, - ODK_ProvisioningResponse const* obj); - -/* kdo unpack */ -void Unpack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense* obj); -void Unpack_ODK_ProvisioningMessage(Message* msg, ODK_ProvisioningMessage* obj); - -#ifdef __cplusplus -} // extern "C" -#endif -#endif /* ODKITEE_SERIALIZER_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_structs.h b/libwvdrmengine/oemcrypto/odk/include/odk_structs.h index 16b4de6a..b24f7d9d 100644 --- a/libwvdrmengine/oemcrypto/odk/include/odk_structs.h +++ b/libwvdrmengine/oemcrypto/odk/include/odk_structs.h @@ -8,63 +8,103 @@ #include #include "OEMCryptoCENCCommon.h" +#include "odk_target.h" -#define ODK_MAX_NUM_KEYS 32 +/* The version of this library. */ +#define ODK_MAJOR_VERSION 16 +#define ODK_MINOR_VERSION 2 + +/* Some useful constants. */ #define ODK_DEVICE_ID_LEN_MAX 64 #define ODK_SHA256_HASH_SIZE 32 /* - * ODK_TimerLimits is filled out by the function ODK_ParseLicense. + * ODK_TimerLimits Structure * - * The fields in this structure are defined in the core license response - * message. This structure should be kept as part of the session and used - * when calling the ODK timer functions described in the document "License - * Duration and Renewal" distributed as part of the OEMCrypto v16 design. + * Description: + * Timer limits are specified in a license and are used to determine when + * playback is allowed. See the document "License Duration and Renewal" for a + * discussion on the time restrictions that may be placed on a license. The + * fields in this structure are directly related to the fields in the core + * license message. The fields are set when OEMCrypto calls the function + * ODK_ParseLicense or ODK_InitializeV15Values. + * + * Fields: + * soft_enforce_rental_duration: A boolean controlling the soft or hard + * enforcement of rental duration. + * soft_enforce_playback_duration: A boolean controlling the soft or hard + * enforcement of playback duration. + * earliest_playback_start_seconds: The earliest time that the first playback + * is allowed. Measured in seconds since the license request was signed. For + * most use cases, this is zero. + * rental_duration_seconds: Window of time for the allowed first playback. + * Measured in seconds since the earliest playback start. If + * soft_enforce_rental_duration is true, this applies only to the first + * playback. If soft_enforce_rental_duration is false, then this restricts + * any playback. A value of zero means no limit. + * total_playback_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. If + * soft_enforce_playback_duration is true, this applies only to the start of + * playback for any session. If soft_enforce_playback_duration is false, then + * this restricts any playback. A value of zero means no limit. + * initial_renewal_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. This value is only + * used to start the renewal timer. After a renewal message is loaded, the + * timer will be reset. A value of zero means no limit. + * + * Version: + * This struct changed in API version 16.2. */ typedef struct { - uint32_t /*boolean*/ soft_expiry; - uint64_t earliest_playback_start_seconds; /* since license signed. */ - uint64_t latest_playback_start_seconds; /* since license signed. */ - uint64_t initial_playback_duration_seconds; /* since playback start. */ - uint64_t renewal_playback_duration_seconds; /* since renewal signed. */ - uint64_t license_duration_seconds; /* since license signed. */ + bool soft_enforce_rental_duration; + bool soft_enforce_playback_duration; + uint64_t earliest_playback_start_seconds; + uint64_t rental_duration_seconds; + uint64_t total_playback_duration_seconds; + uint64_t initial_renewal_duration_seconds; } ODK_TimerLimits; /* - * ODK_ParsedLicense holds fields from the core license response. - */ -typedef struct { - OEMCrypto_Substring enc_mac_keys_iv; - OEMCrypto_Substring enc_mac_keys; - OEMCrypto_Substring pst; - OEMCrypto_Substring srm_restriction_data; - uint32_t /* OEMCrypto_LicenseType */ license_type; - uint32_t nonce_required; - ODK_TimerLimits timer_limits; - uint8_t request_hash[ODK_SHA256_HASH_SIZE]; - uint32_t key_array_length; /* num_keys */ - OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; -} ODK_ParsedLicense; - -/* - * ODK_ParsedProvisioning holds fields from the core provisioning response. - */ -typedef struct { - uint32_t key_type; - OEMCrypto_Substring enc_private_key; - OEMCrypto_Substring enc_private_key_iv; - OEMCrypto_Substring encrypted_message_key; /* Used for Prov 3.0 */ -} ODK_ParsedProvisioning; - -/* - * ODK_ClockValues keeps information about a session's current clock values - * and timers. + * ODK_ClockValues Structure * - * Most of the fields in this structure are saved in the usage entry for each - * session. This structure should be initialized when a usage entry is - * created or loaded, and should be used to save a usage entry. It is - * updated using ODK functions listed in the document "License Duration and - * Renewal". The time values are based on OEMCrypto’s system clock. + * Description: + * Clock values are modified when decryption occurs or when a renewal is + * processed. They are used to track the current status of the license -- + * i.e. has playback started? When does the timer expire? See the section + * "Complete ODK API" of the document "Widevine Core Message Serialization" + * for a complete list of all fields in this structure. Most of these values + * shall be saved with the usage entry. + * + * All times are in seconds. Most of the fields in this structure are saved + * in the usage entry. This structure should be initialized when a usage + * entry is created or loaded, and should be used to save a usage entry. It + * is updated using the ODK functions listed below. The time values are based + * on OEMCrypto's system clock, as described in the document "License + * Duration and Renewal". + * + * Fields: + * time_of_license_signed: Time that the license request was signed, based on + * OEMCrypto's system clock. This value shall be stored and reloaded with + * usage entry as time_of_license_received. + * time_of_first_decrypt: Time of the first decrypt or call select key, based + * on OEMCrypto's system clock. This is 0 if the license has not been used to + * decrypt any data. This value shall be stored and reloaded with usage entry. + * time_of_last_decrypt: Time of the most recent decrypt call, based on + * OEMCrypto's system clock. This value shall be stored and reloaded with + * usage entry. + * time_of_renewal_request: Time of the most recent renewal request, based on + * OEMCrypto's system clock. This is used to verify that a renewal is not + * stale. + * time_when_timer_expires: Time that the current timer expires, based on + * OEMCrypto's system clock. If the timer is active, this is used by the ODK + * library to determine if it has expired. + * timer_status: Used internally by the ODK library to indicate the current + * timer status. + * status: The license or usage entry status. This value shall be stored and + * reloaded with usage entry. + * + * Version: + * This struct changed in API version 16.2. */ typedef struct { uint64_t time_of_license_signed; @@ -77,20 +117,99 @@ typedef struct { } ODK_ClockValues; /* - * ODK_NonceValues are used to match a license or provisioning request to a - * license or provisioning response. For this reason, the api_version might be - * lower than that supported by OEMCrypto. The api_version matches the version - * of the license. Similarly the nonce and session_id match the session that - * generated the license request. For an offline license, these might not match - * the session that is loading the license. We use the nonce to prevent a - * license from being replayed. By also including a session_id in the license - * request and license response, we prevent an attack using the birthday paradox - * to generate nonce collisions on a single device. + * ODK_NonceValues Structure + * + * Description: + * Nonce values are used to match a license or provisioning request to a + * license or provisioning response. They are also used to match a renewal + * request and response to a license. For this reason, the api_version might + * be lower than that supported by OEMCrypto. The api_version matches the + * version of the license. Similarly the nonce and session_id match the + * session that generated the license request. For an offline license, these + * might not match the session that is loading the license. We use the nonce + * to prevent a license from being replayed. By also including a session_id + * in the license request and license response, we prevent an attack using + * the birthday paradox to generate nonce collisions on a single device. + * + * Fields: + * api_major_version: the API version of the license. This is initialized to + * the API version of the ODK library, but may be lower. + * api_minor_version: the minor version of the ODK library. This is used by + * the server to verify that device is not using an obsolete version of the + * ODK library. + * nonce: a randomly generated number used to prevent replay attacks. + * session_id: the session id of the session which signed the license or + * provisioning request. It is used to prevent replay attacks from one + * session to another. + * + * Version: + * This struct changed in API version 16.2. */ typedef struct { - uint32_t api_version; + uint16_t api_minor_version; + uint16_t api_major_version; uint32_t nonce; uint32_t session_id; } ODK_NonceValues; +/* + * ODK_ParsedLicense Structure + * + * Description: + * The parsed license structure contains information from the license + * message. The function ODK_ParseLicense will fill in the fields of this + * message. All substrings are contained within the message body. + * + * Fields: + * enc_mac_keys_iv: IV for decrypting new mac_key. Size is 128 bits. + * enc_mac_keys: encrypted mac_keys for generating new mac_keys. Size is 512 + * bits. + * pst: the Provider Session Token. + * srm_restriction_data: optional data specifying the minimum SRM version. + * license_type: specifies if the license contains content keys or + * entitlement keys. + * nonce_required: indicates if the license requires a nonce. + * timer_limits: time limits of the for the license. + * key_array_length: number of keys present. + * key_array: set of keys to be installed. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_Substring enc_mac_keys_iv; + OEMCrypto_Substring enc_mac_keys; + OEMCrypto_Substring pst; + OEMCrypto_Substring srm_restriction_data; + OEMCrypto_LicenseType license_type; + bool nonce_required; + ODK_TimerLimits timer_limits; + uint32_t key_array_length; + OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; +} ODK_ParsedLicense; + +/* + * ODK_ParsedProvisioning Structure + * + * Description: + * The parsed provisioning structure contains information from the license + * message. The function ODK_ParseProvisioning will fill in the fields of + * this message. All substrings are contained within the message body. + * + * Fields: + * key_type: indicates if this key is an RSA or ECC private key. + * enc_private_key: encrypted private key for the DRM certificate. + * enc_private_key_iv: IV for decrypting new private key. Size is 128 bits. + * encrypted_message_key: used for provisioning 3.0 to derive keys. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_PrivateKeyType key_type; + OEMCrypto_Substring enc_private_key; + OEMCrypto_Substring enc_private_key_iv; + OEMCrypto_Substring encrypted_message_key; /* Used for Prov 3.0 */ +} ODK_ParsedProvisioning; + #endif /* WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h b/libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h deleted file mode 100644 index 8b3ee35a..00000000 --- a/libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_STRUCTS_PRIV_H_ -#define ODK_STRUCTS_PRIV_H_ - -#include -#include "OEMCryptoCENCCommon.h" -#include "odk_structs.h" - -typedef enum { - ODK_License_Request_Type = 1, - ODK_License_Response_Type = 2, - ODK_Renewal_Request_Type = 3, - ODK_Renewal_Response_Type = 4, - ODK_Provisioning_Request_Type = 5, - ODK_Provisioning_Response_Type = 6, -} ODK_MessageType; - -typedef struct { - uint32_t message_type; - uint32_t message_length; - ODK_NonceValues nonce_values; -} ODK_CoreMessage; - -typedef struct { - ODK_CoreMessage core_message; -} ODK_PreparedLicense; - -typedef struct { - ODK_CoreMessage core_message; - uint64_t playback_time; -} ODK_RenewalMessage; - -typedef struct { - ODK_CoreMessage core_message; - uint32_t device_id_length; - uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; -} ODK_ProvisioningMessage; - -typedef struct { - ODK_CoreMessage core_message; - ODK_ParsedLicense* parsed_license; -} ODK_LicenseResponse; - -typedef struct { - ODK_ProvisioningMessage core_provisioning; - ODK_ParsedProvisioning* parsed_provisioning; -} ODK_ProvisioningResponse; - -#endif // ODK_STRUCTS_PRIV_H_ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_target.h b/libwvdrmengine/oemcrypto/odk/include/odk_target.h new file mode 100644 index 00000000..92252103 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/odk_target.h @@ -0,0 +1,13 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file is distributed */ +/* under the Widevine Master License Agreement. */ + +/* Partners are expected to edit this file to support target specific code */ +/* and limits. */ + +#ifndef WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ + +/* Maximum number of keys can be modified to suit target's resource tier. */ +#define ODK_MAX_NUM_KEYS 32 + +#endif /* WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/serialization_base.h b/libwvdrmengine/oemcrypto/odk/include/serialization_base.h deleted file mode 100644 index cc1a3d12..00000000 --- a/libwvdrmengine/oemcrypto/odk/include/serialization_base.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODKITEE_SERIALIZATION_BASE_H_ -#define ODKITEE_SERIALIZATION_BASE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#include "OEMCryptoCENCCommon.h" - -#define SIZE_OF_MESSAGE_STRUCT 64 - -/* - * Description: - * Point |msg| to stack-array |blk|. - * |blk| is guaranteed large enough to hold a |Message| struct. - * |blk| cannot be used in the same scope as a variable name. - * |msg| points to valid memory in the same scope |AllocateMessage| is used. - * Parameters: - * msg: pointer to pointer to |Message| struct - * blk: variable name for stack-array - */ -#define AllocateMessage(msg, blk) \ - uint8_t blk[SIZE_OF_MESSAGE_STRUCT]; \ - *(msg) = (Message*)(blk); - -typedef struct _Message Message; - -bool ValidMessage(Message* message); - -void Pack_uint32_t(Message* message, const uint32_t* value); -void Pack_uint64_t(Message* message, const uint64_t* value); -void PackArray(Message* message, const uint8_t* base, size_t size); -void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj); - -void Unpack_uint32_t(Message* message, uint32_t* value); -void Unpack_uint64_t(Message* message, uint64_t* value); -void UnpackArray(Message* message, uint8_t* base, size_t size); /* copy out */ -void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj); - -typedef enum { - MESSAGE_STATUS_OK, - MESSAGE_STATUS_UNKNOWN_ERROR, - MESSAGE_STATUS_OVERFLOW_ERROR, - MESSAGE_STATUS_UNDERFLOW_ERROR, - MESSAGE_STATUS_PARSE_ERROR, - MESSAGE_STATUS_NULL_POINTER_ERROR, - MESSAGE_STATUS_API_VALUE_ERROR -} MessageStatus; - -/* - * Create a message from a buffer. The message structure consumes the first - * sizeof(Message) bytes of the buffer. The caller is responsible for ensuring - * that the buffer remains allocated for the lifetime of the message. - */ -Message* CreateMessage(uint8_t* buffer, size_t buffer_size); - -/* - * Initialize a message structure to reference a separate buffer. The caller - * is responsible for ensuring that the buffer remains allocated for the - * lifetime of the message. - */ -void InitMessage(Message* message, uint8_t* buffer, size_t capacity); - -/* - * Reset an existing the message to an empty state - */ -void ResetMessage(Message* message); -uint8_t* GetBase(Message* message); -size_t GetCapacity(Message* message); -size_t GetSize(Message* message); -void SetSize(Message* message, size_t size); -MessageStatus GetStatus(Message* message); -void SetStatus(Message* message, MessageStatus status); -size_t GetOffset(Message* message); - -size_t SizeOfMessageStruct(); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // ODKITEE_SERIALIZATION_BASE_H_ diff --git a/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h b/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h deleted file mode 100644 index 20bee97b..00000000 --- a/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -// clang-format off -/********************************************************************* - * oec_util.h - * - * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) - * - * For Widevine Modular DRM, there are six message types between a server and - * a client device: license request and response, provisioning request and - * response, and renewal request and response. - * - * In OEMCrypto v15 and earlier, messages from the server were parsed by the - * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of - * pointers to protected data within the message. However, the pointers - * themselves were not signed by the server. - * - * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these - * messages have been identified in the document "Widevine Core Message - * Serialization". These fields are called the core of the message. Core - * message fields are (de)serialized using the ODK, a C library provided by - * Widevine. OEMCrypto will parse and verify the core of the message with - * help from the ODK. - * - * The KDO library is the counterpart of ODK used in the CDM & Widevine - * servers. For each message type generated by the ODK, KDO provides a - * corresponding parser. For each message type to be parsed by the ODK, - * KDO provides a corresponding writer. - * - * Table: ODK vs KDO (s: serialize; d: deserialize) - * +----------------------------------------+------------------------------------+ - * | ODK | KDO | - * +---+------------------------------------+---+--------------------------------+ - * | s | ODK_PrepareCoreLicenseRequest | d | ParseLicenseRequest | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_PrepareCoreRenewalRequest | | ParseRenewalRequest | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_PrepareCoreProvisioningRequest | | ParseProvisioningRequest | - * +---+------------------------------------+---+--------------------------------+ - * | d | ODK_ParseLicense | s | CreateCoreLicenseResponse | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_ParseRenewal | | CreateCoreRenewalResponse | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_ParseProvisioning | | CreateCoreProvisioningResponse | - * +---+------------------------------------+---+--------------------------------+ - * - *********************************************************************/ -// clang-format on - -#ifndef OEC_UTIL_H_ -#define OEC_UTIL_H_ - -#include -#include - -#include "odk_structs.h" - -using namespace std; - -namespace oec_util { - -// @ input/output structs - -/** - * Output structure for ParseLicenseRequest - * Input structure for CreateCoreLicenseResponse - */ -struct ODK_LicenseRequest { - uint32_t api_version; - uint32_t nonce; - uint32_t session_id; -}; - -/** - * Output structure for ParseRenewalRequest - * Input structure for CreateCoreRenewalResponse - */ -struct ODK_RenewalRequest { - uint32_t api_version; - uint32_t nonce; - uint32_t session_id; - uint64_t playback_time; -}; - -/** - * Output structure for ParseProvisioningRequest - * Input structure for CreateCoreProvisioningResponse - */ -struct ODK_ProvisioningRequest { - uint32_t api_version; - uint32_t nonce; - uint32_t session_id; - string device_id; -}; - -// @ public parse request (deserializer) functions - -/** - * Counterpart (deserializer) of ODK_PrepareCoreLicenseRequest (serializer) - * - * Parameters: - * [in] oemcrypto_core_message - * [out] core_license_request - */ -bool ParseLicenseRequest(const string& oemcrypto_core_message, - ODK_LicenseRequest* core_license_request); - -/** - * Counterpart (deserializer) of ODK_PrepareCoreRenewalRequest (serializer) - * - * Parameters: - * [in] oemcrypto_core_message - * [out] core_renewal_request - */ -bool ParseRenewalRequest(const string& oemcrypto_core_message, - ODK_RenewalRequest* core_renewal_request); - -/** - * Counterpart (deserializer) of ODK_PrepareCoreProvisioningRequest (serializer) - * - * Parameters: - * [in] oemcrypto_core_message - * [out] core_provisioning_request - */ -bool ParseProvisioningRequest( - const string& oemcrypto_core_message, - ODK_ProvisioningRequest* core_provisioning_request); - -// @ public create response (serializer) functions - -/** - * Counterpart (serializer) of ODK_ParseLicense (deserializer) - * struct-input variant - * - * Parameters: - * [in] parsed_lic - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, - const ODK_LicenseRequest& core_request, - string* oemcrypto_core_message); - -/** - * Counterpart (serializer) of ODK_ParseRenewal (deserializer) - * - * Parameters: - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, - string* oemcrypto_core_message); - -/** - * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) - * struct-input variant - * - * Parameters: - * [in] parsed_prov - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, - const ODK_ProvisioningRequest& core_request, - string* oemcrypto_core_message); -} // namespace oec_util - -#endif // OEC_UTIL_H_ diff --git a/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h b/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h deleted file mode 100644 index f9d8017a..00000000 --- a/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -/********************************************************************* - * oec_util_proto.h - * - * These functions are an extension of those found in oec_util.h. The - * difference is that these use the license and provisioning messages - * in protobuf format to create the core message. - *********************************************************************/ - -#ifndef OEC_UTIL_PROTO_H_ -#define OEC_UTIL_PROTO_H_ - -#include -#include - -#include "license_protocol.pb.h" -#include "oec_util.h" - -using namespace std; -using video_widevine::License; -using video_widevine::License_KeyContainer; - -namespace oec_util { - -// @ public create response (serializer) functions - -/** - * Counterpart (serializer) of ODK_ParseLicense (deserializer) - * - * Parameters: - * [in] license - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreLicenseResponse(const video_widevine::License& license, - const ODK_LicenseRequest& core_request, - string* oemcrypto_core_message); - -/** - * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) - * - * Parameters: - * [in] provisioning_response - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreProvisioningResponse( - const video_widevine::ProvisioningResponse& provisioning_response, - const ODK_ProvisioningRequest& core_request, - string* oemcrypto_core_message); - -} // namespace oec_util - -#endif // OEC_UTIL_PROTO_H_ diff --git a/libwvdrmengine/oemcrypto/odk/src/core_message_deserialize.cpp b/libwvdrmengine/oemcrypto/odk/src/core_message_deserialize.cpp index b3d0059c..f2e909b5 100644 --- a/libwvdrmengine/oemcrypto/odk/src/core_message_deserialize.cpp +++ b/libwvdrmengine/oemcrypto/odk/src/core_message_deserialize.cpp @@ -19,8 +19,8 @@ namespace oemcrypto_core_message { namespace deserialize { namespace { -const int EARLIEST_OEMCRYPTO_VERSION_WITH_ODK = 16; -const int LATEST_OEMCRYPTO_VERSION = 16; +constexpr int EARLIEST_OEMCRYPTO_VERSION_WITH_ODK = 16; +constexpr int LATEST_OEMCRYPTO_VERSION = 16; /** * Template for parsing requests @@ -53,29 +53,43 @@ bool ParseRequest(uint32_t message_type, } const auto& core_message = prepared->core_message; - core_request->api_version = core_message.nonce_values.api_version; + core_request->api_major_version = core_message.nonce_values.api_major_version; + core_request->api_minor_version = core_message.nonce_values.api_minor_version; core_request->nonce = core_message.nonce_values.nonce; core_request->session_id = core_message.nonce_values.session_id; + // Verify that the minor version matches the released version for the given + // major version. + if ((core_request->api_major_version < EARLIEST_OEMCRYPTO_VERSION_WITH_ODK) || + (core_request->api_major_version > LATEST_OEMCRYPTO_VERSION)) { + // Non existing and future versions are not supported. + return false; + } else if (core_request->api_major_version == 16) { + // For version 16, we demand a minor version of at least 2. + if (core_request->api_major_version < 2) return false; + } else { + // Other versions do not (yet) have a restriction on minor number. + } return core_message.message_type == message_type && core_message.message_length == GetOffset(msg) && - core_request->api_version >= EARLIEST_OEMCRYPTO_VERSION_WITH_ODK && - core_request->api_version <= LATEST_OEMCRYPTO_VERSION; + core_request->api_major_version >= + EARLIEST_OEMCRYPTO_VERSION_WITH_ODK && + core_request->api_major_version <= LATEST_OEMCRYPTO_VERSION; } } // namespace bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, ODK_LicenseRequest* core_license_request) { - const auto unpacker = Unpack_ODK_PreparedLicense; - ODK_PreparedLicense prepared_license = {}; + const auto unpacker = Unpack_ODK_PreparedLicenseRequest; + ODK_PreparedLicenseRequest prepared_license = {}; return ParseRequest(ODK_License_Request_Type, oemcrypto_core_message, core_license_request, &prepared_license, unpacker); } bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, ODK_RenewalRequest* core_renewal_request) { - const auto unpacker = Unpack_ODK_RenewalMessage; - ODK_RenewalMessage prepared_renewal = {}; + const auto unpacker = Unpack_ODK_PreparedRenewalRequest; + ODK_PreparedRenewalRequest prepared_renewal = {}; if (!ParseRequest(ODK_Renewal_Request_Type, oemcrypto_core_message, core_renewal_request, &prepared_renewal, unpacker)) { return false; @@ -87,8 +101,8 @@ bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, bool CoreProvisioningRequestFromMessage( const std::string& oemcrypto_core_message, ODK_ProvisioningRequest* core_provisioning_request) { - const auto unpacker = Unpack_ODK_ProvisioningMessage; - ODK_ProvisioningMessage prepared_provision = {}; + const auto unpacker = Unpack_ODK_PreparedProvisioningRequest; + ODK_PreparedProvisioningRequest prepared_provision = {}; if (!ParseRequest(ODK_Provisioning_Request_Type, oemcrypto_core_message, core_provisioning_request, &prepared_provision, unpacker)) { return false; diff --git a/libwvdrmengine/oemcrypto/odk/src/core_message_serialize.cpp b/libwvdrmengine/oemcrypto/odk/src/core_message_serialize.cpp index 1ff5a592..1ce827e4 100644 --- a/libwvdrmengine/oemcrypto/odk/src/core_message_serialize.cpp +++ b/libwvdrmengine/oemcrypto/odk/src/core_message_serialize.cpp @@ -4,7 +4,6 @@ #include "core_message_serialize.h" -#include #include #include #include @@ -38,11 +37,12 @@ bool CreateResponse(uint32_t message_type, const S& core_request, auto* header = reinterpret_cast(&response); header->message_type = message_type; - header->nonce_values.api_version = core_request.api_version; + header->nonce_values.api_major_version = core_request.api_major_version; + header->nonce_values.api_minor_version = core_request.api_minor_version; header->nonce_values.nonce = core_request.nonce; header->nonce_values.session_id = core_request.session_id; - const size_t BUF_CAPACITY = 2048; + static constexpr size_t BUF_CAPACITY = 2048; std::vector buf(BUF_CAPACITY, 0); Message* msg = nullptr; AllocateMessage(&msg, message_block); @@ -63,16 +63,14 @@ bool CreateResponse(uint32_t message_type, const S& core_request, bool CopyDeviceId(const ODK_ProvisioningRequest& src, ODK_ProvisioningResponse* dest) { - auto& core_provisioning = dest->core_provisioning; + auto& request = dest->request; const std::string& device_id = src.device_id; - core_provisioning.device_id_length = device_id.size(); - if (core_provisioning.device_id_length > - sizeof(core_provisioning.device_id)) { + if (request.device_id_length > sizeof(request.device_id)) { return false; } - memset(core_provisioning.device_id, 0, sizeof(core_provisioning.device_id)); - memcpy(core_provisioning.device_id, device_id.data(), - core_provisioning.device_id_length); + request.device_id_length = device_id.size(); + memset(request.device_id, 0, sizeof(request.device_id)); + memcpy(request.device_id, device_id.data(), request.device_id_length); return true; } @@ -80,21 +78,28 @@ bool CopyDeviceId(const ODK_ProvisioningRequest& src, bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, std::string* oemcrypto_core_message) { ODK_LicenseResponse license_response{ - {}, const_cast(&parsed_lic)}; + {}, const_cast(&parsed_lic), {0}}; + if (core_request_sha256.size() != sizeof(license_response.request_hash)) + return false; + memcpy(license_response.request_hash, core_request_sha256.data(), + sizeof(license_response.request_hash)); return CreateResponse(ODK_License_Response_Type, core_request, oemcrypto_core_message, license_response, Pack_ODK_LicenseResponse); } bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, std::string* oemcrypto_core_message) { - ODK_RenewalMessage renewal{{}, core_request.playback_time_seconds}; - renewal.playback_time = core_request.playback_time_seconds; + ODK_RenewalResponse renewal_response{{}, core_request.playback_time_seconds}; + renewal_response.request.playback_time = core_request.playback_time_seconds; + renewal_response.renewal_duration_seconds = renewal_duration_seconds; return CreateResponse(ODK_Renewal_Response_Type, core_request, - oemcrypto_core_message, renewal, - Pack_ODK_RenewalMessage); + oemcrypto_core_message, renewal_response, + Pack_ODK_RenewalResponse); } bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, diff --git a/libwvdrmengine/oemcrypto/odk/src/core_message_serialize_proto.cpp b/libwvdrmengine/oemcrypto/odk/src/core_message_serialize_proto.cpp index 8520cd52..25f1887f 100644 --- a/libwvdrmengine/oemcrypto/odk/src/core_message_serialize_proto.cpp +++ b/libwvdrmengine/oemcrypto/odk/src/core_message_serialize_proto.cpp @@ -67,6 +67,7 @@ OEMCrypto_KeyObject KeyContainerToOecKey( bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, const ODK_LicenseRequest& core_request, const std::string& core_request_sha256, + const bool nonce_required, std::string* oemcrypto_core_message) { video_widevine::License lic; if (!lic.ParseFromString(serialized_license)) { @@ -74,25 +75,33 @@ bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, } ODK_ParsedLicense parsed_lic{}; - if (core_request_sha256.size() != ODK_SHA256_HASH_SIZE) { - return false; - } - std::memcpy(parsed_lic.request_hash, core_request_sha256.data(), - ODK_SHA256_HASH_SIZE); + bool any_content = false; + bool any_entitlement = false; for (int i = 0; i < lic.key_size(); ++i) { const auto& k = lic.key(i); switch (k.type()) { case video_widevine::License_KeyContainer::SIGNING: { + if (!k.has_key()) { + continue; + } parsed_lic.enc_mac_keys_iv = GetOecSubstring(serialized_license, k.iv()); - // Strip off PKCS#5 padding - const size_t MAC_KEY_SIZE = 32; - std::string mac_keys(k.key(), 2 * MAC_KEY_SIZE); + std::string mac_keys(k.key(), k.key().size()); parsed_lic.enc_mac_keys = GetOecSubstring(serialized_license, mac_keys); break; } case video_widevine::License_KeyContainer::CONTENT: { + any_content = true; + if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { + return false; + } + uint32_t& n = parsed_lic.key_array_length; + parsed_lic.key_array[n++] = KeyContainerToOecKey(serialized_license, k); + break; + } + case video_widevine::License_KeyContainer::ENTITLEMENT: { + any_entitlement = true; if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { return false; } @@ -105,7 +114,16 @@ bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, } } } - + if (any_content && any_entitlement) { + // TODO(b/147513335): this should be logged -- both type of keys. + return false; + } + if (!any_content && !any_entitlement) { + // TODO(b/147513335): this should be logged -- no keys? + return false; + } + parsed_lic.license_type = + any_content ? OEMCrypto_ContentLicense : OEMCrypto_EntitlementLicense; const auto& lid = lic.id(); if (lid.has_provider_session_token()) { parsed_lic.pst = @@ -117,22 +135,25 @@ bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, GetOecSubstring(serialized_license, lic.srm_requirement()); } - parsed_lic.license_type = lid.type(); - // todo(robertshih): nonce_required + parsed_lic.nonce_required = nonce_required; const auto& policy = lic.policy(); ODK_TimerLimits& timer_limits = parsed_lic.timer_limits; - timer_limits.soft_expiry = policy.soft_enforce_playback_duration(); + // TODO(b/148241181): add field to protobuf. + // timer_limits.soft_enforce_rental_duration = + // policy.soft_enforce_rental_duration(); + timer_limits.soft_enforce_rental_duration = true; + timer_limits.soft_enforce_playback_duration = + policy.soft_enforce_playback_duration(); timer_limits.earliest_playback_start_seconds = 0; - timer_limits.latest_playback_start_seconds = - policy.license_duration_seconds(); - timer_limits.initial_playback_duration_seconds = + timer_limits.rental_duration_seconds = policy.rental_duration_seconds(); + timer_limits.total_playback_duration_seconds = policy.playback_duration_seconds(); - timer_limits.renewal_playback_duration_seconds = - policy.playback_duration_seconds(); - timer_limits.license_duration_seconds = policy.license_duration_seconds(); + timer_limits.initial_renewal_duration_seconds = + policy.renewal_delay_seconds() + + policy.renewal_recovery_duration_seconds(); return CreateCoreLicenseResponse(parsed_lic, core_request, - oemcrypto_core_message); + core_request_sha256, oemcrypto_core_message); } bool CreateCoreProvisioningResponseFromProto( @@ -145,7 +166,8 @@ bool CreateCoreProvisioningResponseFromProto( return false; } - parsed_prov.key_type = 0; // todo(robertshih): ECC or RSA + parsed_prov.key_type = + OEMCrypto_RSA_Private_Key; // TODO(b/148404408): ECC or RSA if (prov.has_device_rsa_key()) { parsed_prov.enc_private_key = GetOecSubstring(serialized_provisioning_resp, prov.device_rsa_key()); diff --git a/libwvdrmengine/oemcrypto/odk/src/odk.c b/libwvdrmengine/oemcrypto/odk/src/odk.c index 696f5464..1ce0d3bc 100644 --- a/libwvdrmengine/oemcrypto/odk/src/odk.c +++ b/libwvdrmengine/oemcrypto/odk/src/odk.c @@ -12,12 +12,12 @@ #include "odk_serialize.h" #include "odk_structs.h" #include "odk_structs_priv.h" +#include "odk_util.h" #include "serialization_base.h" #define ODK_LICENSE_REQUEST_SIZE 20 #define ODK_RENEWAL_REQUEST_SIZE 28 #define ODK_PROVISIONING_REQUEST_SIZE 88 -#define OEC_API_VERSION 16 /* @ private odk functions */ @@ -26,8 +26,8 @@ static OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length, uint32_t message_type, const ODK_NonceValues* nonce_values, ODK_CoreMessage* core_message) { - if (!nonce_values || !core_message_length || !core_message || - *core_message_length > buffer_length) { + if (nonce_values == NULL || core_message_length == NULL || + core_message == NULL || *core_message_length > buffer_length) { return ODK_ERROR_CORE_MESSAGE; } @@ -43,17 +43,20 @@ static OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length, switch (message_type) { case ODK_License_Request_Type: { core_message->message_length = ODK_LICENSE_REQUEST_SIZE; - Pack_ODK_PreparedLicense(msg, (ODK_PreparedLicense*)core_message); + Pack_ODK_PreparedLicenseRequest( + msg, (ODK_PreparedLicenseRequest*)core_message); break; } case ODK_Renewal_Request_Type: { core_message->message_length = ODK_RENEWAL_REQUEST_SIZE; - Pack_ODK_RenewalMessage(msg, (ODK_RenewalMessage*)core_message); + Pack_ODK_PreparedRenewalRequest( + msg, (ODK_PreparedRenewalRequest*)core_message); break; } case ODK_Provisioning_Request_Type: { core_message->message_length = ODK_PROVISIONING_REQUEST_SIZE; - Pack_ODK_ProvisioningMessage(msg, (ODK_ProvisioningMessage*)core_message); + Pack_ODK_PreparedProvisioningRequest( + msg, (ODK_PreparedProvisioningRequest*)core_message); break; } default: { @@ -93,7 +96,7 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, break; } case ODK_Renewal_Response_Type: { - Unpack_ODK_RenewalMessage(msg, (ODK_RenewalMessage*)core_message); + Unpack_ODK_RenewalResponse(msg, (ODK_RenewalResponse*)core_message); break; } case ODK_Provisioning_Response_Type: { @@ -114,7 +117,10 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, if (nonce_values) { /* always verify nonce_values for Renewal and Provisioning responses */ - if (nonce_values->api_version != core_message->nonce_values.api_version || + if (nonce_values->api_major_version != + core_message->nonce_values.api_major_version || + nonce_values->api_minor_version != + core_message->nonce_values.api_minor_version || nonce_values->nonce != core_message->nonce_values.nonce || nonce_values->session_id != core_message->nonce_values.session_id) { return OEMCrypto_ERROR_INVALID_NONCE; @@ -131,7 +137,7 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, OEMCryptoResult ODK_PrepareCoreLicenseRequest( uint8_t* message, size_t message_length, size_t* core_message_length, const ODK_NonceValues* nonce_values) { - ODK_PreparedLicense license_request = { + ODK_PreparedLicenseRequest license_request = { {0}, }; return ODK_PrepareRequest(message, message_length, core_message_length, @@ -139,13 +145,17 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest( &license_request.core_message); } -OEMCryptoResult ODK_PrepareCoreRenewalRequest( - uint8_t* message, size_t message_length, size_t* core_message_size, - const ODK_NonceValues* nonce_values, ODK_ClockValues* clock_values, - uint64_t system_time_seconds) { - ODK_RenewalMessage renewal_request = { - {0}, - }; +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + if (nonce_values == NULL || clock_values == NULL) + return ODK_ERROR_CORE_MESSAGE; + ODK_PreparedRenewalRequest renewal_request = {{0}, 0}; + /* First, we compute the time this request was made relative to the playback + * clock. */ if (clock_values->time_of_first_decrypt == 0) { /* It is OK to preemptively request a renewal before playback starts. * We'll treat this as asking for a renewal at playback time 0. */ @@ -158,7 +168,9 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest( return ODK_ERROR_CORE_MESSAGE; } } - /* Save time for this request so that we can verify the response. */ + /* Save time for this request so that we can verify the response. This makes + * all earlier requests invalid. If preparing this request fails, then all + * requests will be bad. */ clock_values->time_of_renewal_request = renewal_request.playback_time; return ODK_PrepareRequest(message, message_length, core_message_size, ODK_Renewal_Request_Type, nonce_values, @@ -169,7 +181,9 @@ OEMCryptoResult ODK_PrepareCoreProvisioningRequest( uint8_t* message, size_t message_length, size_t* core_message_length, const ODK_NonceValues* nonce_values, const uint8_t* device_id, size_t device_id_length) { - ODK_ProvisioningMessage provisioning_request = { + ODK_PreparedProvisioningRequest provisioning_request = { + {0}, + 0, {0}, }; if (device_id_length > sizeof(provisioning_request.device_id)) { @@ -192,55 +206,61 @@ OEMCryptoResult ODK_ParseLicense( const uint8_t* request_hash, ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, ODK_ParsedLicense* parsed_license) { - if (!message || !request_hash || !timer_limits || !clock_values || - !nonce_values || !parsed_license) { + if (message == NULL || request_hash == NULL || timer_limits == NULL || + clock_values == NULL || nonce_values == NULL || parsed_license == NULL) { return ODK_ERROR_CORE_MESSAGE; } - ODK_LicenseResponse license_response = {{0}, parsed_license}; - OEMCryptoResult err = ODK_ParseResponse( + ODK_LicenseResponse license_response = {{{0}}, parsed_license, {0}}; + const OEMCryptoResult err = ODK_ParseResponse( message, message_length, core_message_length, ODK_License_Response_Type, - NULL, &license_response.core_message); + NULL, &license_response.request.core_message); if (err != OEMCrypto_SUCCESS) { return err; } /* This function should not be used for legacy licenses. */ - if (license_response.core_message.nonce_values.api_version != - OEC_API_VERSION) { + if (license_response.request.core_message.nonce_values.api_major_version != + ODK_MAJOR_VERSION) { return ODK_UNSUPPORTED_API; } + /* If the license has a provider session token (pst), then OEMCrypto should + * have a usage entry loaded. The opposite is also an error. */ + if ((usage_entry_present && parsed_license->pst.length == 0) || + (!usage_entry_present && parsed_license->pst.length > 0)) { + return ODK_ERROR_CORE_MESSAGE; + } + if (parsed_license->nonce_required) { if (initial_license_load) { if (nonce_values->nonce != - license_response.core_message.nonce_values.nonce || + license_response.request.core_message.nonce_values.nonce || nonce_values->session_id != - license_response.core_message.nonce_values.session_id) { + license_response.request.core_message.nonce_values.session_id) { return OEMCrypto_ERROR_INVALID_NONCE; } } else { /* !initial_license_load */ - nonce_values->nonce = license_response.core_message.nonce_values.nonce; + nonce_values->nonce = + license_response.request.core_message.nonce_values.nonce; nonce_values->session_id = - license_response.core_message.nonce_values.session_id; + license_response.request.core_message.nonce_values.session_id; } } /* For v16, in order to be backwards compatible with a v15 license server, * OEMCrypto stores a hash of the core license request and only signs the * message body. Here, when we process the license response, we verify that * the server has the same hash of the core request. */ - if (initial_license_load && memcmp(request_hash, parsed_license->request_hash, - ODK_SHA256_HASH_SIZE)) { - return ODK_ERROR_CORE_MESSAGE; - } - /* If the license has a provider session token (pst), then OEMCrypto should - * have a usage entry loaded. */ - if (usage_entry_present && parsed_license->pst.length == 0) { + if (initial_license_load && + crypto_memcmp(request_hash, license_response.request_hash, + ODK_SHA256_HASH_SIZE)) { return ODK_ERROR_CORE_MESSAGE; } *timer_limits = parsed_license->timer_limits; - return err; + /* And update the clock values state. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + return OEMCrypto_SUCCESS; } OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, @@ -250,16 +270,18 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, uint64_t* timer_value) { - if (!message || !nonce_values || !timer_limits || !clock_values) { + if (message == NULL || nonce_values == NULL || timer_limits == NULL || + clock_values == NULL) { return ODK_ERROR_CORE_MESSAGE; } - ODK_RenewalMessage renewal_response = { - {0}, + ODK_RenewalResponse renewal_response = { + {{0}, 0}, + 0, }; OEMCryptoResult err = ODK_ParseResponse( message, message_length, core_message_length, ODK_Renewal_Response_Type, - nonce_values, &renewal_response.core_message); + nonce_values, &renewal_response.request.core_message); if (err) { return err; @@ -269,69 +291,51 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * Doc: License Duration and Renewal (Changes for OEMCrypto v16) * Section: Renewal Message */ - /* If a renewal request is lost in transit, we should throw it out and create * a new one. We use the timestamp to make sure we have the latest request. */ - if (clock_values->time_of_renewal_request < renewal_response.playback_time) { + if (clock_values->time_of_renewal_request < + renewal_response.request.playback_time) { return ODK_STALE_RENEWAL; } - - /* The timer value should be set to the renewal duration. */ - if (timer_value) { - *timer_value = timer_limits->renewal_playback_duration_seconds; - } - - if (timer_limits->renewal_playback_duration_seconds == 0) { - clock_values->time_when_timer_expires = 0; - clock_values->timer_status = ODK_DISABLE_TIMER; - return ODK_DISABLE_TIMER; - } - if (odk_add_overflow_u64(system_time, - timer_limits->renewal_playback_duration_seconds, - &clock_values->time_when_timer_expires)) { - return ODK_ERROR_CORE_MESSAGE; - } - clock_values->timer_status = ODK_SET_TIMER; - return ODK_SET_TIMER; + return ODK_ComputeRenewalDuration(timer_limits, clock_values, system_time, + renewal_response.renewal_duration_seconds, + timer_value); } OEMCryptoResult ODK_ParseProvisioning( const uint8_t* message, size_t message_length, size_t core_message_length, const ODK_NonceValues* nonce_values, const uint8_t* device_id, size_t device_id_length, ODK_ParsedProvisioning* parsed_response) { - if (!message || !nonce_values || !device_id || !parsed_response) { + if (message == NULL || nonce_values == NULL || device_id == NULL || + parsed_response == NULL) { return ODK_ERROR_CORE_MESSAGE; } - ODK_ProvisioningResponse provisioning_response = {{ - {0}, - }, + ODK_ProvisioningResponse provisioning_response = {{{0}, 0, {0}}, parsed_response}; if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { return ODK_ERROR_CORE_MESSAGE; } - const OEMCryptoResult err = + OEMCryptoResult err = ODK_ParseResponse(message, message_length, core_message_length, ODK_Provisioning_Response_Type, nonce_values, - &provisioning_response.core_provisioning.core_message); + &provisioning_response.request.core_message); if (err) { return err; } - if (memcmp(device_id, provisioning_response.core_provisioning.device_id, + if (memcmp(device_id, provisioning_response.request.device_id, device_id_length)) { return ODK_ERROR_CORE_MESSAGE; } uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {0}; /* check bytes beyond device_id_length are 0 */ - if (memcmp( - zero, - provisioning_response.core_provisioning.device_id + device_id_length, - ODK_DEVICE_ID_LEN_MAX - device_id_length)) { + if (memcmp(zero, provisioning_response.request.device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length)) { return ODK_ERROR_CORE_MESSAGE; } diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_overflow.h b/libwvdrmengine/oemcrypto/odk/src/odk_overflow.h index 23cc4405..3fcb32cc 100644 --- a/libwvdrmengine/oemcrypto/odk/src/odk_overflow.h +++ b/libwvdrmengine/oemcrypto/odk/src/odk_overflow.h @@ -5,6 +5,9 @@ #ifndef WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ #define WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ +#include +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c b/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c index 5f77aee1..e0500505 100644 --- a/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c +++ b/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c @@ -14,7 +14,8 @@ /* @@ private serialize */ static void Pack_ODK_NonceValues(Message* msg, ODK_NonceValues const* obj) { - Pack_uint32_t(msg, &obj->api_version); + Pack_uint16_t(msg, &obj->api_minor_version); + Pack_uint16_t(msg, &obj->api_major_version); Pack_uint32_t(msg, &obj->nonce); Pack_uint32_t(msg, &obj->session_id); } @@ -35,12 +36,12 @@ static void Pack_OEMCrypto_KeyObject(Message* msg, } static void Pack_ODK_TimerLimits(Message* msg, ODK_TimerLimits const* obj) { - Pack_uint32_t(msg, &obj->soft_expiry); + Pack_bool(msg, &obj->soft_enforce_rental_duration); + Pack_bool(msg, &obj->soft_enforce_playback_duration); Pack_uint64_t(msg, &obj->earliest_playback_start_seconds); - Pack_uint64_t(msg, &obj->latest_playback_start_seconds); - Pack_uint64_t(msg, &obj->initial_playback_duration_seconds); - Pack_uint64_t(msg, &obj->renewal_playback_duration_seconds); - Pack_uint64_t(msg, &obj->license_duration_seconds); + Pack_uint64_t(msg, &obj->rental_duration_seconds); + Pack_uint64_t(msg, &obj->total_playback_duration_seconds); + Pack_uint64_t(msg, &obj->initial_renewal_duration_seconds); } static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { @@ -53,10 +54,9 @@ static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { Pack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); Pack_OEMCrypto_Substring(msg, &obj->pst); Pack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); - Pack_uint32_t(msg, &obj->license_type); - Pack_uint32_t(msg, &obj->nonce_required); + Pack_enum(msg, obj->license_type); + Pack_bool(msg, &obj->nonce_required); Pack_ODK_TimerLimits(msg, &obj->timer_limits); - PackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); Pack_uint32_t(msg, &obj->key_array_length); size_t i; for (i = 0; i < (size_t)obj->key_array_length; i++) { @@ -66,7 +66,7 @@ static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { static void Pack_ODK_ParsedProvisioning(Message* msg, ODK_ParsedProvisioning const* obj) { - Pack_uint32_t(msg, &obj->key_type); + Pack_enum(msg, obj->key_type); Pack_OEMCrypto_Substring(msg, &obj->enc_private_key); Pack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); Pack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); @@ -74,17 +74,19 @@ static void Pack_ODK_ParsedProvisioning(Message* msg, /* @@ odk serialize */ -void Pack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense const* obj) { +void Pack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest const* obj) { Pack_ODK_CoreMessage(msg, &obj->core_message); } -void Pack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage const* obj) { +void Pack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest const* obj) { Pack_ODK_CoreMessage(msg, &obj->core_message); Pack_uint64_t(msg, &obj->playback_time); } -void Pack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage const* obj) { +void Pack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest const* obj) { Pack_ODK_CoreMessage(msg, &obj->core_message); Pack_uint32_t(msg, &obj->device_id_length); PackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); @@ -93,13 +95,19 @@ void Pack_ODK_ProvisioningMessage(Message* msg, /* @@ kdo serialize */ void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj) { - Pack_ODK_CoreMessage(msg, &obj->core_message); + Pack_ODK_PreparedLicenseRequest(msg, &obj->request); Pack_ODK_ParsedLicense(msg, (const ODK_ParsedLicense*)obj->parsed_license); + PackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); +} + +void Pack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse const* obj) { + Pack_ODK_PreparedRenewalRequest(msg, &obj->request); + Pack_uint64_t(msg, &obj->renewal_duration_seconds); } void Pack_ODK_ProvisioningResponse(Message* msg, ODK_ProvisioningResponse const* obj) { - Pack_ODK_ProvisioningMessage(msg, &obj->core_provisioning); + Pack_ODK_PreparedProvisioningRequest(msg, &obj->request); Pack_ODK_ParsedProvisioning( msg, (const ODK_ParsedProvisioning*)obj->parsed_provisioning); } @@ -109,7 +117,8 @@ void Pack_ODK_ProvisioningResponse(Message* msg, /* @@ private deserialize */ static void Unpack_ODK_NonceValues(Message* msg, ODK_NonceValues* obj) { - Unpack_uint32_t(msg, &obj->api_version); + Unpack_uint16_t(msg, &obj->api_minor_version); + Unpack_uint16_t(msg, &obj->api_major_version); Unpack_uint32_t(msg, &obj->nonce); Unpack_uint32_t(msg, &obj->session_id); } @@ -129,12 +138,12 @@ static void Unpack_OEMCrypto_KeyObject(Message* msg, OEMCrypto_KeyObject* obj) { } static void Unpack_ODK_TimerLimits(Message* msg, ODK_TimerLimits* obj) { - Unpack_uint32_t(msg, &obj->soft_expiry); + Unpack_bool(msg, &obj->soft_enforce_rental_duration); + Unpack_bool(msg, &obj->soft_enforce_playback_duration); Unpack_uint64_t(msg, &obj->earliest_playback_start_seconds); - Unpack_uint64_t(msg, &obj->latest_playback_start_seconds); - Unpack_uint64_t(msg, &obj->initial_playback_duration_seconds); - Unpack_uint64_t(msg, &obj->renewal_playback_duration_seconds); - Unpack_uint64_t(msg, &obj->license_duration_seconds); + Unpack_uint64_t(msg, &obj->rental_duration_seconds); + Unpack_uint64_t(msg, &obj->total_playback_duration_seconds); + Unpack_uint64_t(msg, &obj->initial_renewal_duration_seconds); } static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) { @@ -142,10 +151,9 @@ static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) { Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); Unpack_OEMCrypto_Substring(msg, &obj->pst); Unpack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); - Unpack_uint32_t(msg, &obj->license_type); - Unpack_uint32_t(msg, &obj->nonce_required); + obj->license_type = Unpack_enum(msg); + Unpack_bool(msg, &obj->nonce_required); Unpack_ODK_TimerLimits(msg, &obj->timer_limits); - UnpackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); Unpack_uint32_t(msg, &obj->key_array_length); if (obj->key_array_length > ODK_MAX_NUM_KEYS) { SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); @@ -159,7 +167,7 @@ static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) { static void Unpack_ODK_ParsedProvisioning(Message* msg, ODK_ParsedProvisioning* obj) { - Unpack_uint32_t(msg, &obj->key_type); + obj->key_type = Unpack_enum(msg); Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key); Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); Unpack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); @@ -167,12 +175,19 @@ static void Unpack_ODK_ParsedProvisioning(Message* msg, /* @ kdo deserialize */ -void Unpack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense* obj) { +void Unpack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest* obj) { Unpack_ODK_CoreMessage(msg, &obj->core_message); } -void Unpack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage* obj) { +void Unpack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_uint64_t(msg, &obj->playback_time); +} + +void Unpack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest* obj) { Unpack_ODK_CoreMessage(msg, &obj->core_message); Unpack_uint32_t(msg, &obj->device_id_length); UnpackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); @@ -181,17 +196,18 @@ void Unpack_ODK_ProvisioningMessage(Message* msg, /* @@ odk deserialize */ void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj) { - Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_ODK_PreparedLicenseRequest(msg, &obj->request); Unpack_ODK_ParsedLicense(msg, obj->parsed_license); + UnpackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); } -void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj) { - Unpack_ODK_CoreMessage(msg, &obj->core_message); - Unpack_uint64_t(msg, &obj->playback_time); +void Unpack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse* obj) { + Unpack_ODK_PreparedRenewalRequest(msg, &obj->request); + Unpack_uint64_t(msg, &obj->renewal_duration_seconds); } void Unpack_ODK_ProvisioningResponse(Message* msg, ODK_ProvisioningResponse* obj) { - Unpack_ODK_ProvisioningMessage(msg, &obj->core_provisioning); + Unpack_ODK_PreparedProvisioningRequest(msg, &obj->request); Unpack_ODK_ParsedProvisioning(msg, obj->parsed_provisioning); } diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_serialize.h b/libwvdrmengine/oemcrypto/odk/src/odk_serialize.h index 1b570188..f35f1782 100644 --- a/libwvdrmengine/oemcrypto/odk/src/odk_serialize.h +++ b/libwvdrmengine/oemcrypto/odk/src/odk_serialize.h @@ -16,25 +16,32 @@ extern "C" { #endif /* odk pack */ -void Pack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense const* obj); -void Pack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage const* obj); -void Pack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage const* obj); +void Pack_ODK_PreparedLicenseRequest(Message* msg, + const ODK_PreparedLicenseRequest* obj); +void Pack_ODK_PreparedRenewalRequest(Message* msg, + const ODK_PreparedRenewalRequest* obj); +void Pack_ODK_PreparedProvisioningRequest( + Message* msg, const ODK_PreparedProvisioningRequest* obj); /* odk unpack */ void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj); -void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj); +void Unpack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse* obj); void Unpack_ODK_ProvisioningResponse(Message* msg, ODK_ProvisioningResponse* obj); /* kdo pack */ -void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj); +void Pack_ODK_LicenseResponse(Message* msg, const ODK_LicenseResponse* obj); +void Pack_ODK_RenewalResponse(Message* msg, const ODK_RenewalResponse* obj); void Pack_ODK_ProvisioningResponse(Message* msg, - ODK_ProvisioningResponse const* obj); + const ODK_ProvisioningResponse* obj); /* kdo unpack */ -void Unpack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense* obj); -void Unpack_ODK_ProvisioningMessage(Message* msg, ODK_ProvisioningMessage* obj); +void Unpack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest* obj); +void Unpack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest* obj); +void Unpack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest* obj); #ifdef __cplusplus } /* extern "C" */ diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_structs_priv.h b/libwvdrmengine/oemcrypto/odk/src/odk_structs_priv.h index d21fbe21..00e31bf5 100644 --- a/libwvdrmengine/oemcrypto/odk/src/odk_structs_priv.h +++ b/libwvdrmengine/oemcrypto/odk/src/odk_structs_priv.h @@ -10,6 +10,10 @@ #include "OEMCryptoCENCCommon.h" #include "odk_structs.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { ODK_License_Request_Type = 1, ODK_License_Response_Type = 2, @@ -27,27 +31,61 @@ typedef struct { typedef struct { ODK_CoreMessage core_message; -} ODK_PreparedLicense; +} ODK_PreparedLicenseRequest; typedef struct { ODK_CoreMessage core_message; uint64_t playback_time; -} ODK_RenewalMessage; +} ODK_PreparedRenewalRequest; typedef struct { ODK_CoreMessage core_message; uint32_t device_id_length; uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; -} ODK_ProvisioningMessage; +} ODK_PreparedProvisioningRequest; typedef struct { - ODK_CoreMessage core_message; + ODK_PreparedLicenseRequest request; ODK_ParsedLicense* parsed_license; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; } ODK_LicenseResponse; typedef struct { - ODK_ProvisioningMessage core_provisioning; + ODK_PreparedRenewalRequest request; + uint64_t renewal_duration_seconds; +} ODK_RenewalResponse; + +typedef struct { + ODK_PreparedProvisioningRequest request; ODK_ParsedProvisioning* parsed_provisioning; } ODK_ProvisioningResponse; +/* These are the possible timer status values. */ +#define ODK_CLOCK_TIMER_STATUS_UNDEFINED 0 /* Should not happen. */ +/* When the structure has been initialized, but no license is loaded. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED 1 +/* After the license is loaded, before a successful decrypt. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED 2 +/* After the license is loaded, if a renewal has also been loaded. */ +#define ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED 3 +/* The first decrypt has occurred and the timer is active. */ +#define ODK_CLOCK_TIMER_STATUS_ACTIVE 4 +/* The first decrypt has occurred and the timer is unlimited. */ +#define ODK_CLOCK_TIMER_STATUS_UNLIMITED 5 +/* The timer has transitioned from active to expired. */ +#define ODK_CLOCK_TIMER_STATUS_EXPIRED 6 +/* The license has been marked as inactive. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE 7 + +/* A helper function for computing timer limits when a renewal is loaded. */ +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value); + +#ifdef __cplusplus +} +#endif + #endif /* WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_timer.c b/libwvdrmengine/oemcrypto/odk/src/odk_timer.c index a28547d4..8bef3d9a 100644 --- a/libwvdrmengine/oemcrypto/odk/src/odk_timer.c +++ b/libwvdrmengine/oemcrypto/odk/src/odk_timer.c @@ -6,41 +6,279 @@ #include #include "odk.h" +#include "odk_overflow.h" +#include "odk_structs_priv.h" +/* Private function. Checks to see if the license is active. Returns + * ODK_TIMER_EXPIRED if the license is valid but inactive. Returns + * OEMCrypto_SUCCESS if the license is active. Returns + * OEMCrypto_ERROR_UNKNOWN_FAILURE on other errors. This also updates the + * timer_status if appropriate. */ +static OEMCryptoResult ODK_LicenseActive(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values) { + /* Check some basic errors. */ + if (clock_values == NULL || timer_limits == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if the license has not been loaded yet. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_UNDEFINED || + clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (clock_values->status > kActive) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + return ODK_TIMER_EXPIRED; + } + return OEMCrypto_SUCCESS; +} + +/* Private function. Sets the timer_value to be the min(timer_value, new_value), + * with the convention that 0 means infinite. The convention that 0 means + * infinite is used for all Widevine license and duration values. */ +static void ComputeMinimum(uint64_t* timer_value, uint64_t new_value) { + if (timer_value == NULL) return; + if (new_value > 0) { + if (*timer_value == 0 || *timer_value > new_value) { + *timer_value = new_value; + } + } +} + +/* Private function. Check to see if the rental window restricts playback. If + * the rental enforcement is hard, or if this is the first playback, then we + * verify that system_time_seconds is within the rental window. If the + * enforcement is soft and we have already started playback, then there is no + * restriction. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_TIMER_ACTIVE if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no there should be no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckRentalWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* If playback has already started, and rental duration enforcement is soft, + * then there is no restriction. */ + if (clock_values->time_of_first_decrypt > 0 && + timer_limits->soft_enforce_rental_duration) { + return ODK_DISABLE_TIMER; + } + + /* rental_clock = time since license signed. */ + uint64_t rental_clock = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_signed, + &rental_clock)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if it is before license is valid. This is an unusual case. First + * playback may still work if it occurs after the rental window opens. */ + if (rental_clock < timer_limits->earliest_playback_start_seconds) { + return ODK_TIMER_EXPIRED; + } + /* If the rental duration is 0, there is no limit. */ + if (timer_limits->rental_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + /* End of rental window, based on rental clock (not system time). */ + uint64_t end_of_rental_window = 0; + if (odk_add_overflow_u64(timer_limits->earliest_playback_start_seconds, + timer_limits->rental_duration_seconds, + &end_of_rental_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_rental_window <= rental_clock) { + return ODK_TIMER_EXPIRED; + } + /* At this point system_time is within the rental window. */ + if (timer_limits->soft_enforce_rental_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_rental_window, rental_clock, &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Private function. Check to see if the playback window restricts + * playback. This should only be called if playback has started, so that + * clock_values->time_of_first_decrypt is nonzero. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_SET_TIMER if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckPlaybackWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + /* if the playback duration is 0, there is no limit. */ + if (timer_limits->total_playback_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + uint64_t end_of_playback_window = 0; + if (odk_add_overflow_u64(timer_limits->total_playback_duration_seconds, + clock_values->time_of_first_decrypt, + &end_of_playback_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_playback_window <= system_time_seconds) { + return ODK_TIMER_EXPIRED; + } + /* At this point, system_time is within the total playback window. */ + if (timer_limits->soft_enforce_playback_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_playback_window, system_time_seconds, + &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Update the timer status. If playback has already started, we use the given + * status. However, if playback has not yet started, then we expect a call to + * ODK_AttemptFirstPlayback in the future, and we need to signal to it that we + * have already computed the timer limit. */ +static void ODK_UpdateTimerStatusForRenewal(ODK_ClockValues* clock_values, + uint32_t new_status) { + if (clock_values == NULL) return; /* should not happen. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED) { + /* Signal that the timer is already set. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED; + } else { + clock_values->timer_status = new_status; + } +} + +/* Private function, but accessed from odk.c so cannot be static. This checks to + * see if a renewal message should restart the playback timer and sets the value + * appropriately. */ +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value) { + if (timer_limits == NULL || clock_values == NULL) + return OEMCrypto_ERROR_INVALID_CONTEXT; /* should not happen. */ + /* If this is before the license was signed, something is odd. Return an + * error. */ + if (system_time_seconds < clock_values->time_of_license_signed) + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + + const OEMCryptoResult license_status = + ODK_LicenseActive(timer_limits, clock_values); + /* If the license is not active, then we cannot renew the license. */ + if (license_status != OEMCrypto_SUCCESS) return license_status; + + /* We start with the new renewal duration as the new timer limit. */ + uint64_t new_timer_value = new_renewal_duration; + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + const OEMCryptoResult rental_status = ODK_CheckRentalWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + /* If the rental status forbids playback, then we're done. */ + if ((rental_status != ODK_DISABLE_TIMER) && (rental_status != ODK_SET_TIMER)) + return rental_status; + + /* If playback has already started and it has hard enforcement, then check + * total playback window. */ + if (clock_values->time_of_first_decrypt > 0 && + !timer_limits->soft_enforce_playback_duration) { + /* This might decrease new_timer_value. */ + const OEMCryptoResult playback_status = ODK_CheckPlaybackWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + /* If the timer limits forbid playback in the playback window, then we're + * done. */ + if ((playback_status != ODK_DISABLE_TIMER) && + (playback_status != ODK_SET_TIMER)) + return playback_status; + } + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { + clock_values->time_when_timer_expires = 0; + ODK_UpdateTimerStatusForRenewal(clock_values, + ODK_CLOCK_TIMER_STATUS_UNLIMITED); + return ODK_DISABLE_TIMER; + } + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value != NULL) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + ODK_UpdateTimerStatusForRenewal(clock_values, ODK_CLOCK_TIMER_STATUS_ACTIVE); + return ODK_SET_TIMER; +} + +/************************************************************************/ +/************************************************************************/ +/* Public functions, declared in odk.h. */ + +/* This is called when OEMCrypto opens a new session. */ OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, - uint32_t api_version, + uint32_t api_major_version, uint32_t session_id) { if (clock_values == NULL || clock_values == NULL || nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; - timer_limits->soft_expiry = false; + /* Check that the API version passed in from OEMCrypto matches the version of + * this ODK library. */ + if (api_major_version != ODK_MAJOR_VERSION) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + timer_limits->soft_enforce_rental_duration = false; + timer_limits->soft_enforce_playback_duration = false; timer_limits->earliest_playback_start_seconds = 0; - timer_limits->latest_playback_start_seconds = 0; - timer_limits->initial_playback_duration_seconds = 0; - timer_limits->renewal_playback_duration_seconds = 0; - timer_limits->license_duration_seconds = 0; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = 0; - clock_values->time_of_license_signed = 0; - clock_values->time_of_first_decrypt = 0; - clock_values->time_of_last_decrypt = 0; - clock_values->time_when_timer_expires = 0; - clock_values->timer_status = 0; - clock_values->status = kUnused; + ODK_InitializeClockValues(clock_values, 0); - nonce_values->api_version = api_version; + nonce_values->api_major_version = ODK_MAJOR_VERSION; + nonce_values->api_minor_version = ODK_MINOR_VERSION; nonce_values->nonce = 0; nonce_values->session_id = session_id; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto generates a new nonce in + * OEMCrypto_GenerateNonce. */ OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, uint32_t nonce) { + if (nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + /* Setting the nonce should only happen once per session. */ + if (nonce_values->nonce != 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } nonce_values->nonce = nonce; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto signs a license. */ OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, uint64_t system_time_seconds) { if (clock_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; @@ -48,12 +286,12 @@ OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, clock_values->time_of_first_decrypt = 0; clock_values->time_of_last_decrypt = 0; clock_values->time_when_timer_expires = 0; - /* TODO(b/142415188): document this. */ - clock_values->timer_status = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; clock_values->status = kUnused; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto reloads a usage entry. */ OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, uint64_t time_of_license_signed, uint64_t time_of_first_decrypt, @@ -65,114 +303,84 @@ OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, clock_values->time_of_first_decrypt = time_of_first_decrypt; clock_values->time_of_last_decrypt = time_of_last_decrypt; clock_values->time_when_timer_expires = 0; - clock_values->timer_status = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; clock_values->status = status; return OEMCrypto_SUCCESS; } /* This is called on the first playback for a session. */ -uint32_t ODK_AttemptFirstPlayback(uint64_t system_time_seconds, - const ODK_TimerLimits* timer_limits, - ODK_ClockValues* clock_values, - uint64_t* timer_value) { +OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { if (clock_values == NULL || timer_limits == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; /* All times are relative to when the license was signed. */ - const uint64_t rental_time = - system_time_seconds - clock_values->time_of_license_signed; + uint64_t rental_time = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_signed, + &rental_time)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } if (rental_time < timer_limits->earliest_playback_start_seconds) { clock_values->timer_status = ODK_TIMER_EXPIRED; return ODK_TIMER_EXPIRED; } - /* If the clock status is already marked as inactive, then playback is - * not allowed. */ - /* TODO(b/142415188): add helper function. */ - if (clock_values->status > kActive) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - /* If this license is still inactive (never used) then we just look at the - * rental window. This is the first playback for the license, not just this - * session. */ - if (clock_values->status == kUnused) { - /* If the rental clock has expired, the license has expired. */ - if (rental_time > timer_limits->latest_playback_start_seconds && - timer_limits->latest_playback_start_seconds > 0) { - clock_values->timer_status = ODK_TIMER_EXPIRED; + /* If the license is inactive or not loaded, then playback is not allowed. */ + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) return status; + + /* We start with the initial renewal duration as the timer limit. */ + uint64_t new_timer_value = timer_limits->initial_renewal_duration_seconds; + /* However, if a renewal was loaded before this first playback, use the + * previously computed limit. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED) { + if (clock_values->time_when_timer_expires <= system_time_seconds) { return ODK_TIMER_EXPIRED; } - /* The timer should be limited by the playback duration. */ - uint64_t time_left = timer_limits->initial_playback_duration_seconds; - /* If there is a license duration, it also limits the timer. Remeber, a - * limit of 0 means no limit, or infinite. */ - if (timer_limits->license_duration_seconds > 0) { - if (timer_limits->license_duration_seconds < rental_time) { - /* If the license duration has expired. This is unusual, because this - * can only happen if the license duration is less than the rental - * window. */ - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - if (timer_limits->license_duration_seconds - rental_time < time_left || - time_left == 0) { - time_left = timer_limits->license_duration_seconds - rental_time; - } + if (odk_sub_overflow_u64(clock_values->time_when_timer_expires, + system_time_seconds, &new_timer_value)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; } - /* This is a new license, and we can start playback. */ - clock_values->status = kActive; + } + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + status = ODK_CheckRentalWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) return status; + + /* If playback has not already started, then this is the first playback. */ + if (clock_values->time_of_first_decrypt == 0) { clock_values->time_of_first_decrypt = system_time_seconds; - clock_values->time_of_last_decrypt = system_time_seconds; - if (time_left == 0 || timer_limits->soft_expiry) { /* Unlimited. */ - clock_values->time_when_timer_expires = 0; - clock_values->timer_status = ODK_DISABLE_TIMER; - return ODK_DISABLE_TIMER; - } - /* Set timer to limit playback. */ - if (timer_value) *timer_value = time_left; - clock_values->time_when_timer_expires = system_time_seconds + time_left; - clock_values->timer_status = ODK_SET_TIMER; - return ODK_SET_TIMER; + clock_values->status = kActive; } - /* Otherwise, this is the second loading of a persistent license. In this - * case, we ignore the rental window. */ - const uint64_t time_since_first_decrypt = - system_time_seconds - clock_values->time_of_first_decrypt; - uint64_t time_left = 0; - /* If there is an initial playback duration, the we use that as a limit. - * This ignores any license renewals. If renewals are allowed, then the last - * one can be reloaded to reset the timer. */ - if (timer_limits->initial_playback_duration_seconds > 0) { - if (timer_limits->initial_playback_duration_seconds <= - time_since_first_decrypt) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - time_left = timer_limits->initial_playback_duration_seconds - - time_since_first_decrypt; - } - /* If there is a license duration, it also limits the timer. */ - if (timer_limits->license_duration_seconds > 0) { - if (timer_limits->license_duration_seconds < rental_time) { - /* The license duration has expired. */ - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - if (timer_limits->license_duration_seconds - rental_time < time_left || - time_left == 0) { - time_left = timer_limits->license_duration_seconds - rental_time; - } - } - /* We can restart playback for this license. Update last playback time. */ + + /* Similar to the rental window, we check the playback window + * restrictions. This might decrease new_timer_value. */ + status = ODK_CheckPlaybackWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) return status; + + /* We know we are allowed to decrypt. The rest computes the timer duration. */ clock_values->time_of_last_decrypt = system_time_seconds; - if (time_left == 0 || timer_limits->soft_expiry) { /* Unlimited. */ + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { clock_values->time_when_timer_expires = 0; - clock_values->timer_status = ODK_DISABLE_TIMER; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_UNLIMITED; return ODK_DISABLE_TIMER; } - /* Set timer. */ - if (timer_value) *timer_value = time_left; - clock_values->time_when_timer_expires = system_time_seconds + time_left; - clock_values->timer_status = ODK_SET_TIMER; + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_ACTIVE; return ODK_SET_TIMER; } @@ -181,27 +389,29 @@ uint32_t ODK_AttemptFirstPlayback(uint64_t system_time_seconds, OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values) { - if (clock_values == NULL || timer_limits == NULL) - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - if (clock_values->timer_status == ODK_TIMER_EXPIRED) { - return ODK_TIMER_EXPIRED; - } - /* If the clock status is already marked as inactive, then playback is - * not allowed. */ - /* TODO(b/142415188): add helper function. */ - if (clock_values->status > kActive) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - if (clock_values->time_when_timer_expires > 0 && - system_time_seconds > clock_values->time_when_timer_expires) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) return status; + switch (clock_values->timer_status) { + case ODK_CLOCK_TIMER_STATUS_UNLIMITED: + break; + case ODK_CLOCK_TIMER_STATUS_ACTIVE: + /* Note: we allow playback at the time when the timer expires, but not + * after. This is not important for business cases, but it makes it + * easier to write tests. */ + if (clock_values->time_when_timer_expires > 0 && + system_time_seconds > clock_values->time_when_timer_expires) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_EXPIRED; + return ODK_TIMER_EXPIRED; + } + break; + default: /* Expired, error state, or never started. */ + return ODK_TIMER_EXPIRED; } clock_values->time_of_last_decrypt = system_time_seconds; return OEMCrypto_SUCCESS; } +/* This is called from OEMCrypto_DeactivateUsageEntry. */ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values) { if (clock_values == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; if (clock_values->status == kUnused) { @@ -209,9 +419,12 @@ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values) { } else if (clock_values->status == kActive) { clock_values->status = kInactiveUsed; } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto loads a legacy v15 license, from + * OEMCrypto_LoadKeys. */ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, @@ -219,67 +432,41 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, uint64_t system_time_seconds) { if (clock_values == NULL || clock_values == NULL || nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; - timer_limits->soft_expiry = false; + timer_limits->soft_enforce_playback_duration = false; + timer_limits->soft_enforce_rental_duration = false; timer_limits->earliest_playback_start_seconds = 0; - timer_limits->latest_playback_start_seconds = 0; - timer_limits->initial_playback_duration_seconds = key_duration; - timer_limits->renewal_playback_duration_seconds = key_duration; - timer_limits->license_duration_seconds = 0; - nonce_values->api_version = 15; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = key_duration; + + nonce_values->api_major_version = 15; + nonce_values->api_minor_version = 0; if (key_duration > 0) { clock_values->time_when_timer_expires = system_time_seconds + key_duration; } else { clock_values->time_when_timer_expires = 0; } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto loads a legacy license renewal in + * OEMCrypto_RefreshKeys. */ OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, const ODK_NonceValues* nonce_values, uint64_t system_time_seconds, + uint32_t new_key_duration, uint64_t* timer_value) { if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; - if (nonce_values->api_version != 15) return OEMCrypto_ERROR_INVALID_NONCE; + if (nonce_values->api_major_version != 15) + return OEMCrypto_ERROR_INVALID_NONCE; if (clock_values->status > kActive) { clock_values->timer_status = ODK_TIMER_EXPIRED; return ODK_TIMER_EXPIRED; } - /* If this is before the license was signed, something is odd. Return an - * error. */ - if (system_time_seconds < clock_values->time_of_license_signed) - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - - /* All times are relative to when the license was signed. */ - const uint64_t rental_time = - system_time_seconds - clock_values->time_of_license_signed; - - /* The timer should be limited by the renewal playback duration. This is - * similar to code in AttemptFirstPlayback, above, except we use the - * renewal_playback_duration here, and we do not change clock_values->status. - */ - uint64_t time_left = timer_limits->renewal_playback_duration_seconds; - /* If there is a license duration, it also limits the timer. Remember, a - * limit of 0 means no limit, or infinite. */ - if (timer_limits->license_duration_seconds > 0) { - if (timer_limits->license_duration_seconds < rental_time) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - if (timer_limits->license_duration_seconds - rental_time < time_left || - time_left == 0) { - time_left = timer_limits->license_duration_seconds - rental_time; - } - } - if (time_left == 0 || timer_limits->soft_expiry) { /* Unlimited. */ - clock_values->time_when_timer_expires = 0; - clock_values->timer_status = ODK_DISABLE_TIMER; - return ODK_DISABLE_TIMER; - } - /* Set timer to limit playback. */ - if (timer_value) *timer_value = time_left; - clock_values->time_when_timer_expires = system_time_seconds + time_left; - clock_values->timer_status = ODK_SET_TIMER; - return ODK_SET_TIMER; + return ODK_ComputeRenewalDuration(timer_limits, clock_values, + system_time_seconds, new_key_duration, + timer_value); } diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_util.c b/libwvdrmengine/oemcrypto/odk/src/odk_util.c new file mode 100644 index 00000000..7a85b1a4 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_util.c @@ -0,0 +1,25 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +#include "odk_util.h" + +int crypto_memcmp(const void* in_a, const void* in_b, size_t len) { + if (len == 0) { + return 0; + } + + /* Only valid pointers are allowed. */ + if (in_a == NULL || in_b == NULL) { + return -1; + } + + const uint8_t* a = in_a; + const uint8_t* b = in_b; + uint8_t x = 0; + + for (size_t i = 0; i < len; i++) { + x |= a[i] ^ b[i]; + } + return x; +} diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_util.h b/libwvdrmengine/oemcrypto/odk/src/odk_util.h new file mode 100644 index 00000000..42d492b1 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_util.h @@ -0,0 +1,24 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +#ifndef WIDEVINE_ODK_SRC_ODK_UTIL_H_ +#define WIDEVINE_ODK_SRC_ODK_UTIL_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* crypto_memcmp returns zero iff the |len| bytes at |a| and |b| are equal. It + * takes an amount of time dependent on |len|, but independent of the contents + * of |a| and |b|. Unlike memcmp, it cannot be used to order elements as the + * return value when a != b is undefined, other than being non-zero. */ +int crypto_memcmp(const void* a, const void* b, size_t len); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* WIDEVINE_ODK_SRC_ODK_UTIL_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/src/serialization_base.c b/libwvdrmengine/oemcrypto/odk/src/serialization_base.c index 3aff38bb..505d6486 100644 --- a/libwvdrmengine/oemcrypto/odk/src/serialization_base.c +++ b/libwvdrmengine/oemcrypto/odk/src/serialization_base.c @@ -51,6 +51,26 @@ static void PackBytes(Message* message, const uint8_t* ptr, size_t count) { } } +void Pack_enum(Message* message, int value) { + uint32_t v32 = value; + Pack_uint32_t(message, &v32); +} + +void Pack_bool(Message* message, const bool* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + data[3] = *value ? 1 : 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint16_t(Message* message, const uint16_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[2] = {0}; + data[0] = *value >> 8; + data[1] = *value >> 0; + PackBytes(message, data, sizeof(data)); +} + void Pack_uint32_t(Message* message, const uint32_t* value) { if (!ValidMessage(message)) return; uint8_t data[4] = {0}; @@ -90,6 +110,27 @@ static void UnpackBytes(Message* message, uint8_t* ptr, size_t count) { } } +int Unpack_enum(Message* message) { + uint32_t v32; + Unpack_uint32_t(message, &v32); + return v32; +} + +void Unpack_bool(Message* message, bool* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = (0 != data[3]); +} + +void Unpack_uint16_t(Message* message, uint16_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[2] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = data[0]; + *value = *value << 8 | data[1]; +} + void Unpack_uint32_t(Message* message, uint32_t* value) { if (!ValidMessage(message)) return; uint8_t data[4] = {0}; @@ -195,7 +236,8 @@ size_t GetSize(Message* message) { void SetSize(Message* message, size_t size) { if (message == NULL) return; if (size > message->capacity) message->status = MESSAGE_STATUS_OVERFLOW_ERROR; - message->size = size; + else + message->size = size; } MessageStatus GetStatus(Message* message) { return message->status; } diff --git a/libwvdrmengine/oemcrypto/odk/src/serialization_base.h b/libwvdrmengine/oemcrypto/odk/src/serialization_base.h index e969cb30..109bd496 100644 --- a/libwvdrmengine/oemcrypto/odk/src/serialization_base.h +++ b/libwvdrmengine/oemcrypto/odk/src/serialization_base.h @@ -34,11 +34,17 @@ typedef struct _Message Message; bool ValidMessage(Message* message); +void Pack_enum(Message* message, int value); +void Pack_bool(Message* message, const bool* value); +void Pack_uint16_t(Message* message, const uint16_t* value); void Pack_uint32_t(Message* message, const uint32_t* value); void Pack_uint64_t(Message* message, const uint64_t* value); void PackArray(Message* message, const uint8_t* base, size_t size); void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj); +int Unpack_enum(Message* message); +void Unpack_bool(Message* message, bool* value); +void Unpack_uint16_t(Message* message, uint16_t* value); void Unpack_uint32_t(Message* message, uint32_t* value); void Unpack_uint64_t(Message* message, uint64_t* value); void UnpackArray(Message* message, uint8_t* address, diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp b/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp index 8d0244a8..75dd7093 100644 --- a/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp +++ b/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp @@ -115,7 +115,7 @@ static bool kdo_fun_LicenseResponse(const ODK_ParseLicense_Args* args, static OEMCryptoResult odk_fun_RenewalResponse( const uint8_t* buf, size_t len, uint32_t api_version, uint32_t nonce, uint32_t session_id, ODK_ParseRenewal_Args* a, - ODK_RenewalMessage& renewal_msg) { + ODK_PreparedRenewalRequest& renewal_msg) { uint64_t timer_value = 0; OEMCryptoResult err = ODK_ParseRenewal(buf, len, api_version, nonce, session_id, a->system_time, @@ -125,15 +125,16 @@ static OEMCryptoResult odk_fun_RenewalResponse( AllocateMessage(&msg, message_block); InitMessage(msg, const_cast(buf), len); SetSize(msg, len); - Unpack_ODK_RenewalMessage(msg, &renewal_msg); + Unpack_ODK_PreparedRenewalRequest(msg, &renewal_msg); assert(ValidMessage(msg)); } return err; } -static bool kdo_fun_RenewalResponse(const ODK_ParseRenewal_Args* args, - const ODK_RenewalMessage& renewal_msg, - std::string* oemcrypto_core_message) { +static bool kdo_fun_RenewalResponse( + const ODK_ParseRenewal_Args* args, + const ODK_PreparedRenewalRequest& renewal_msg, + std::string* oemcrypto_core_message) { const auto& common = args->common; ODK_RenewalRequest core_request{common.api_version, common.nonce, common.session_id, renewal_msg.playback_time}; @@ -217,7 +218,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { odk_kdo( odk_fun_LicenseResponse, kdo_fun_LicenseResponse)); verify_roundtrip(data, size, - odk_kdo( + odk_kdo( odk_fun_RenewalResponse, kdo_fun_RenewalResponse)); verify_roundtrip( data, size, diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp b/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp index 77c4bfc3..016b8bfa 100644 --- a/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp +++ b/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp @@ -4,6 +4,8 @@ #include "odk.h" +#include // TODO(b/147944591): use this one? Or odk_endian.h? + #include #include #include @@ -37,6 +39,7 @@ using oemcrypto_core_message::serialize::CreateCoreProvisioningResponse; using oemcrypto_core_message::serialize::CreateCoreRenewalResponse; enum ODK_FieldType { + ODK_UINT16, ODK_UINT32, ODK_UINT64, ODK_SUBSTRING, @@ -59,6 +62,8 @@ struct ODK_Field { size_t ODK_FieldLength(ODK_FieldType type) { switch (type) { + case ODK_UINT16: + return sizeof(uint16_t); case ODK_UINT32: return sizeof(uint32_t); case ODK_UINT64: @@ -87,6 +92,11 @@ OEMCryptoResult ODK_WriteSingleField(uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } switch (field->type) { + case ODK_UINT16: { + uint16_t u16 = htobe16(*static_cast(field->value)); + memcpy(buf, &u16, sizeof(u16)); + break; + } case ODK_UINT32: { uint32_t u32 = htobe32(*static_cast(field->value)); memcpy(buf, &u32, sizeof(u32)); @@ -125,6 +135,12 @@ OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } switch (field->type) { + case ODK_UINT16: { + memcpy(field->value, buf, sizeof(uint16_t)); + uint16_t* u16p = static_cast(field->value); + *u16p = be16toh(*u16p); + break; + } case ODK_UINT32: { memcpy(field->value, buf, sizeof(uint32_t)); uint32_t* u32p = static_cast(field->value); @@ -166,6 +182,14 @@ OEMCryptoResult ODK_DumpSingleField(const uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } switch (field->type) { + case ODK_UINT16: { + uint16_t val; + memcpy(&val, buf, sizeof(uint16_t)); + val = be16toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } case ODK_UINT32: { uint32_t val; memcpy(&val, buf, sizeof(uint32_t)); @@ -230,14 +254,18 @@ OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } uint8_t* const buf_off = buf + off; - if (mode == ODK_WRITE) { - ODK_WriteSingleField(buf_off, &fields[i]); - } else if (mode == ODK_READ) { - ODK_ReadSingleField(buf_off, &fields[i]); - } else if (mode == ODK_DUMP) { - ODK_DumpSingleField(buf_off, &fields[i]); - } else { - return ODK_ERROR_CORE_MESSAGE; + switch (mode) { + case ODK_WRITE: + ODK_WriteSingleField(buf_off, &fields[i]); + break; + case ODK_READ: + ODK_ReadSingleField(buf_off, &fields[i]); + break; + case ODK_DUMP: + ODK_DumpSingleField(buf_off, &fields[i]); + break; + default: + return ODK_ERROR_CORE_MESSAGE; } off = off2; } @@ -280,14 +308,17 @@ void ValidateRequest(uint32_t message_type, const std::vector& extra_fields, const F& odk_prepare_func, const G& kdo_parse_func) { uint32_t message_size = 0; - uint32_t api_version = 16; + uint16_t api_major_version = ODK_MAJOR_VERSION; + uint16_t api_minor_version = ODK_MINOR_VERSION; uint32_t nonce = 0xdeadbeef; uint32_t session_id = 0xcafebabe; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; std::vector total_fields = { {ODK_UINT32, &message_type, "message_type"}, {ODK_UINT32, &message_size, "message_size"}, - {ODK_UINT32, &api_version, "api_version"}, + {ODK_UINT16, &api_minor_version, "api_minor_version"}, + {ODK_UINT16, &api_major_version, "api_major_version"}, {ODK_UINT32, &nonce, "nonce"}, {ODK_UINT32, &session_id, "session_id"}, }; @@ -317,7 +348,8 @@ void ValidateRequest(uint32_t message_type, std::string oemcrypto_core_message(reinterpret_cast(buf), message_size); EXPECT_TRUE(kdo_parse_func(oemcrypto_core_message, &t)); - nonce_values.api_version = t.api_version; + nonce_values.api_minor_version = t.api_minor_version; + nonce_values.api_major_version = t.api_major_version; nonce_values.nonce = t.nonce; nonce_values.session_id = t.session_id; EXPECT_EQ(OEMCrypto_SUCCESS, @@ -340,13 +372,15 @@ void ValidateResponse(uint32_t message_type, const std::vector& extra_fields, const F& odk_parse_func, const G& kdo_prepare_func) { uint32_t message_size = 0; - uint32_t api_version = 16; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = ODK_MAJOR_VERSION; uint32_t nonce = 0xdeadbeef; uint32_t session_id = 0xcafebabe; std::vector total_fields = { {ODK_UINT32, &message_type, "message_type"}, {ODK_UINT32, &message_size, "message_size"}, - {ODK_UINT32, &api_version, "api_version"}, + {ODK_UINT16, &api_minor_version, "api_minor_version"}, + {ODK_UINT16, &api_major_version, "api_major_version"}, {ODK_UINT32, &nonce, "nonce"}, {ODK_UINT32, &session_id, "session_id"}, }; @@ -367,7 +401,8 @@ void ValidateResponse(uint32_t message_type, size_t bytes_read = 0, bytes_written = 0; T t = {}; - t.api_version = api_version; + t.api_minor_version = api_minor_version; + t.api_major_version = api_major_version; t.nonce = nonce; t.session_id = session_id; @@ -383,7 +418,8 @@ void ValidateResponse(uint32_t message_type, bytes_written - bytes_read == header_size); // parse buf with odk - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{ODK_MINOR_VERSION, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_SUCCESS, odk_parse_func(buf, bytes_written, &nonce_values)); @@ -465,16 +501,16 @@ TEST(OdkTest, LicenseRequest) { } TEST(OdkTest, RenewalRequest) { - const uint64_t system_time_seconds = 0xBADDCAFE000FF1CE; + constexpr uint64_t system_time_seconds = 0xBADDCAFE000FF1CE; uint64_t playback_time = 0xCAFE00000000; const uint64_t playback_start = system_time_seconds - playback_time; - std::vector extra_fields = { + const std::vector extra_fields = { {ODK_UINT64, &playback_time, "playback_time"}, }; ODK_ClockValues clock_values = {0}; clock_values.time_of_first_decrypt = playback_start; auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, - const ODK_NonceValues* nonce_values) { + ODK_NonceValues* nonce_values) { return ODK_PrepareCoreRenewalRequest(buf, SIZE_MAX, size, nonce_values, &clock_values, system_time_seconds); }; @@ -528,20 +564,17 @@ TEST(OdkTest, LicenseResponse) { .enc_mac_keys = {.offset = 2, .length = 3}, .pst = {.offset = 4, .length = 5}, .srm_restriction_data = {.offset = 6, .length = 7}, - .license_type = 8, - .nonce_required = 0xDEADC0DE, + .license_type = OEMCrypto_EntitlementLicense, + .nonce_required = true, .timer_limits = { - .soft_expiry = 9, + .soft_enforce_rental_duration = true, + .soft_enforce_playback_duration = false, .earliest_playback_start_seconds = 10, - .latest_playback_start_seconds = 11, - .initial_playback_duration_seconds = 12, - .renewal_playback_duration_seconds = 13, - .license_duration_seconds = 14, + .rental_duration_seconds = 11, + .total_playback_duration_seconds = 12, + .initial_renewal_duration_seconds = 13, }, - .request_hash = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, .key_array_length = 3, .key_array = { @@ -569,6 +602,11 @@ TEST(OdkTest, LicenseResponse) { }, }; + const uint8_t request_hash[ODK_SHA256_HASH_SIZE] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + uint8_t request_hash_read[ODK_SHA256_HASH_SIZE]; + memcpy(request_hash_read, request_hash, sizeof(request_hash)); std::vector extra_fields = { {ODK_SUBSTRING, &parsed_license.enc_mac_keys_iv, ".enc_mac_keys_iv"}, {ODK_SUBSTRING, &parsed_license.enc_mac_keys, ".enc_mac_keys"}, @@ -577,20 +615,19 @@ TEST(OdkTest, LicenseResponse) { ".srm_restriction_data"}, {ODK_UINT32, &parsed_license.license_type, ".license_type"}, {ODK_UINT32, &parsed_license.nonce_required, ".nonce_required"}, - {ODK_UINT32, &parsed_license.timer_limits.soft_expiry, ".soft_expiry"}, + {ODK_UINT32, &parsed_license.timer_limits.soft_enforce_rental_duration, + ".soft_enforce_rental_duration"}, + {ODK_UINT32, &parsed_license.timer_limits.soft_enforce_playback_duration, + ".soft_enforce_playback_duration"}, {ODK_UINT64, &parsed_license.timer_limits.earliest_playback_start_seconds, ".earliest_playback_start_seconds"}, - {ODK_UINT64, &parsed_license.timer_limits.latest_playback_start_seconds, - ".latest_playback_start_seconds"}, + {ODK_UINT64, &parsed_license.timer_limits.rental_duration_seconds, + ".rental_duration_seconds"}, + {ODK_UINT64, &parsed_license.timer_limits.total_playback_duration_seconds, + ".total_playback_duration_seconds"}, {ODK_UINT64, - &parsed_license.timer_limits.initial_playback_duration_seconds, - ".initial_playback_duration_seconds"}, - {ODK_UINT64, - &parsed_license.timer_limits.renewal_playback_duration_seconds, - ".renewal_playback_duration_seconds"}, - {ODK_UINT64, &parsed_license.timer_limits.license_duration_seconds, - ".license_duration_seconds"}, - {ODK_HASH, &parsed_license.request_hash, ".request_hash"}, + &parsed_license.timer_limits.initial_renewal_duration_seconds, + ".initial_renewal_duration_seconds"}, {ODK_UINT32, &parsed_license.key_array_length, ".key_array_length"}, {ODK_SUBSTRING, &parsed_license.key_array[0].key_id, ".key_id"}, {ODK_SUBSTRING, &parsed_license.key_array[0].key_data_iv, ".key_data_iv"}, @@ -610,21 +647,24 @@ TEST(OdkTest, LicenseResponse) { {ODK_SUBSTRING, &parsed_license.key_array[2].key_control_iv, ".key_control_iv"}, {ODK_SUBSTRING, &parsed_license.key_array[2].key_control, ".key_control"}, + {ODK_HASH, request_hash_read, ".request_hash"}, }; - - uint8_t request_hash[ODK_SHA256_HASH_SIZE] = {}; - memcpy(request_hash, parsed_license.request_hash, ODK_SHA256_HASH_SIZE); + const std::string request_hash_string( + reinterpret_cast(request_hash), sizeof(request_hash)); auto odk_parse_func = [&](const uint8_t* buf, size_t size, ODK_NonceValues* nonce_values) { ODK_TimerLimits timer_limits; ODK_ClockValues clock_values; - return ODK_ParseLicense(buf, size + 128, size, true, false, request_hash, - &timer_limits, &clock_values, nonce_values, - &parsed_license); + constexpr bool initial_license_load = true; + constexpr bool usage_entry_present = true; + return ODK_ParseLicense(buf, size + 128, size, initial_license_load, + usage_entry_present, request_hash, &timer_limits, + &clock_values, nonce_values, &parsed_license); }; auto kdo_prepare_func = [&](const ODK_LicenseRequest& core_request, std::string* oemcrypto_core_message) { return CreateCoreLicenseResponse(parsed_license, core_request, + request_hash_string, oemcrypto_core_message); }; ValidateResponse(ODK_License_Response_Type, extra_fields, @@ -636,26 +676,29 @@ TEST(OdkTest, RenewalResponse) { uint64_t playback_clock = 11; uint64_t playback_timer = 12; uint64_t message_playback_clock = 10; + constexpr uint64_t renewal_duration = 130; + uint64_t var_renewal_duration = renewal_duration; std::vector extra_fields = { {ODK_UINT64, &message_playback_clock, "message_playback_clock"}, + {ODK_UINT64, &var_renewal_duration, "renewal_duration"}, }; ODK_TimerLimits timer_limits = { - .soft_expiry = 0, + .soft_enforce_rental_duration = false, + .soft_enforce_playback_duration = false, .earliest_playback_start_seconds = 0, - .latest_playback_start_seconds = 100, - .initial_playback_duration_seconds = 10, - .renewal_playback_duration_seconds = 20, - .license_duration_seconds = 100, + .rental_duration_seconds = 1000, + .total_playback_duration_seconds = 2000, + .initial_renewal_duration_seconds = 30, }; ODK_ClockValues clock_values = { - .time_of_license_signed = 0, + .time_of_license_signed = system_time - playback_clock - 42, .time_of_first_decrypt = system_time - playback_clock, .time_of_last_decrypt = 0, .time_of_renewal_request = message_playback_clock, .time_when_timer_expires = system_time + playback_timer, - .timer_status = 0, + .timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED, .status = kUnused, }; @@ -666,7 +709,7 @@ TEST(OdkTest, RenewalResponse) { &timer_limits, &clock_values, &playback_timer); EXPECT_EQ(ODK_SET_TIMER, err); - EXPECT_EQ(timer_limits.renewal_playback_duration_seconds, playback_timer); + EXPECT_EQ(renewal_duration, playback_timer); EXPECT_EQ(clock_values.time_when_timer_expires, system_time + playback_timer); @@ -678,7 +721,8 @@ TEST(OdkTest, RenewalResponse) { auto kdo_prepare_func = [&](ODK_RenewalRequest& core_request, std::string* oemcrypto_core_message) { core_request.playback_time_seconds = message_playback_clock; - return CreateCoreRenewalResponse(core_request, oemcrypto_core_message); + return CreateCoreRenewalResponse(core_request, renewal_duration, + oemcrypto_core_message); }; ValidateResponse(ODK_Renewal_Response_Type, extra_fields, odk_parse_func, kdo_prepare_func); @@ -732,10 +776,12 @@ TEST(OdkSizeTest, LicenseRequest) { uint8_t* message = nullptr; size_t message_length = 0; size_t core_message_length = 0; - uint32_t api_version = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; uint32_t nonce = 0; uint32_t session_id = 0; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, ODK_PrepareCoreLicenseRequest(message, message_length, &core_message_length, &nonce_values)); @@ -748,13 +794,15 @@ TEST(OdkSizeTest, RenewalRequest) { uint8_t* message = nullptr; size_t message_length = 0; size_t core_message_length = 0; - uint32_t api_version = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; uint32_t nonce = 0; uint32_t session_id = 0; ODK_ClockValues clock_values = {}; clock_values.time_of_first_decrypt = 10; uint64_t system_time_seconds = 15; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, ODK_PrepareCoreRenewalRequest(message, message_length, &core_message_length, &nonce_values, @@ -768,11 +816,13 @@ TEST(OdkSizeTest, ProvisioningRequest) { uint8_t* message = nullptr; size_t message_length = 0; size_t core_message_length = 0; - uint32_t api_version = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; uint32_t nonce = 0; uint32_t session_id = 0; uint32_t device_id_length = 0; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, ODK_PrepareCoreProvisioningRequest( message, message_length, &core_message_length, &nonce_values, diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_test.h b/libwvdrmengine/oemcrypto/odk/test/odk_test.h deleted file mode 100644 index a8114a10..00000000 --- a/libwvdrmengine/oemcrypto/odk/test/odk_test.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_TEST_H_ -#define ODK_TEST_H_ - -#include "OEMCryptoCENCCommon.h" - -typedef enum { - ODK_License_Request_Type = 1, - ODK_License_Response_Type = 2, - ODK_Renewal_Request_Type = 3, - ODK_Renewal_Response_Type = 4, - ODK_Provisioning_Request_Type = 5, - ODK_Provisioning_Response_Type = 6, -} ODK_MessageType; - -typedef enum { - ODK_UINT32, - ODK_UINT64, - ODK_SUBSTRING, - ODK_DEVICEID, - ODK_HASH, - ODK_NUMTYPES, -} ODK_FieldType; - -typedef enum { - ODK_READ, - ODK_WRITE, -} ODK_FieldMode; - -typedef struct { - ODK_FieldType type; - void* value; -} ODK_Field; - -#define DEVICE_ID_MAX (64) - -#ifdef __cplusplus -extern "C" { -#endif - -size_t ODK_FieldLength(ODK_FieldType type); -OEMCryptoResult ODK_WriteSingleField(uint8_t* const buf, - const ODK_Field* const field); -OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf, - const ODK_Field* const field); - -OEMCryptoResult ODK_ReadFields(const uint8_t* const buf, const size_t size_in, - size_t* size_out, const size_t n, - const ODK_Field* const fields); - -OEMCryptoResult ODK_WriteFields(uint8_t* const buf, const size_t size_in, - size_t* size_out, const size_t n, - const ODK_Field* const fields); - -#ifdef __cplusplus -} -#endif - -#endif // ODK_TEST_H_ diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp b/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp index 9259220c..fae23bc0 100644 --- a/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp +++ b/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp @@ -6,17 +6,22 @@ #include "OEMCryptoCENCCommon.h" #include "gtest/gtest.h" #include "odk.h" - -using ::testing::Values; -using ::testing::WithParamInterface; +#include "odk_structs_priv.h" namespace { -constexpr uint64_t kTolerance = 1; // Allow 1 second of roundoff. -struct ServerExpiry { - bool soft_rental; - bool soft_playback; -}; +// The rental clock starts when the request is signed. If any test fails +// because a time is off by exactly 1000, that is a strong indication that we +// confused rental and system clocks. The system clock should only be used +// internally on the device, because it might not be synchronized from one +// device to another. Rental clock is used in the license message from the +// server. +constexpr uint64_t kRentalClockStart = 1000u; + +// The renewal grace period is used by the server and the CDM layer to allow +// time between when the renewal is requested and when playback is cutoff if the +// renewal is not loaded. +constexpr uint64_t kGracePeriod = 5u; TEST(OdkTimerBasicTest, NullTest) { // Assert that nullptr does not cause a core dump. @@ -33,10 +38,11 @@ TEST(OdkTimerBasicTest, Init) { uint64_t time = 42; ODK_InitializeClockValues(&clock_values, time); EXPECT_EQ(clock_values.time_of_license_signed, time); - EXPECT_EQ(clock_values.time_of_first_decrypt, 0); - EXPECT_EQ(clock_values.time_of_last_decrypt, 0); - EXPECT_EQ(clock_values.time_when_timer_expires, 0); - EXPECT_EQ(clock_values.timer_status, 0); + EXPECT_EQ(clock_values.time_of_first_decrypt, 0u); + EXPECT_EQ(clock_values.time_of_last_decrypt, 0u); + EXPECT_EQ(clock_values.time_when_timer_expires, 0u); + EXPECT_EQ(clock_values.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); EXPECT_EQ(clock_values.status, kUnused); } @@ -55,866 +61,1173 @@ TEST(OdkTimerBasicTest, Reload) { EXPECT_EQ(clock_values.time_of_first_decrypt, first_decrypt); EXPECT_EQ(clock_values.time_of_last_decrypt, last_decrypt); EXPECT_EQ(clock_values.time_when_timer_expires, 0u); - EXPECT_EQ(clock_values.timer_status, 0u); + EXPECT_EQ(clock_values.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); EXPECT_EQ(clock_values.status, status); } -// ************************************************************************ -// Test that we can only start playback within the rental window. This -// simulates requesting a license at rental_clock_start_, and a rental window of -// 50-150s relative to license request, or 100-200s absolute. -class OdkTimerRentalWindow : public ::testing::Test { +// All of the following test cases are derived from this base class. It +// simulates loading a license, attempting playback, and reloading a license. +class ODKTimerTest : public ::testing::Test { public: - OdkTimerRentalWindow() { - rental_clock_start_ = 10000u; - rental_window_start_ = 50u; - rental_window_duration_ = 100u; + ODKTimerTest() { + // These are reasonable initial values for most tests. This is an unlimited + // license. Most of the tests below will add some restrictions by changing + // these values, and then verify that the ODK library behaves correctly. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = true; + timer_limits_.earliest_playback_start_seconds = 0u; + // A duration of 0 means infinite. + timer_limits_.rental_duration_seconds = 0u; + timer_limits_.total_playback_duration_seconds = 0u; + timer_limits_.initial_renewal_duration_seconds = 0u; + + // This is when we will attempt the first valid playback. + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); } protected: void SetUp() override { ::testing::Test::SetUp(); - // Start rental clocks at rental_clock_start_. - ODK_InitializeClockValues(&clock_values_, rental_clock_start_); - // Simple license values: - timer_limits_.soft_expiry = false; - timer_limits_.earliest_playback_start_seconds = rental_window_start_; - timer_limits_.latest_playback_start_seconds = - rental_window_start_ + rental_window_duration_; - timer_limits_.initial_playback_duration_seconds = 0; - timer_limits_.renewal_playback_duration_seconds = 0; - timer_limits_.license_duration_seconds = 0; + // Start rental clock at kRentalClockStart. This happens when the license + // request is signed. + ODK_InitializeClockValues(&clock_values_, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); } - // Simulate reloading a license in a new session. An offline license should - // have several of the clock_value's fields saved into the usage table. When - // it is reloaded those values should be reloaded. From these fields, the - // ODK function can tell if this is the first playback for the license, or - // just the first playback for this session. The key fields that should be - // saved are the status, and the times for license signed, and first and - // last playback times. - void ReloadClock(uint64_t system_time) { + // Simulate loading or reloading a license in a new session. An offline + // license should have several of the clock_value's fields saved into the + // usage table. When it is reloaded those values should be reloaded. From + // these fields, the ODK function can tell if this is the first playback for + // the license, or just the first playback for this session. The key fields + // that should be saved are the status, and the times for license signed, and + // first and last playback times. + void ReloadLicense(uint64_t system_time) { ODK_ClockValues old_clock_values = clock_values_; // First clear out the old clock values. - ODK_InitializeClockValues(&clock_values_, 0u); + memset(&clock_values_, 0, sizeof(clock_values_)); + // When the session is opened, the clock values are initialized. + ODK_InitializeClockValues(&clock_values_, 0); + // When the usage entry is reloaded, the clock values are reloaded. ODK_ReloadClockValues(&clock_values_, old_clock_values.time_of_license_signed, old_clock_values.time_of_first_decrypt, old_clock_values.time_of_last_decrypt, old_clock_values.status, system_time); + EXPECT_EQ(clock_values_.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); + // These shall not change: + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_first_decrypt, + old_clock_values.time_of_first_decrypt); + EXPECT_EQ(clock_values_.time_of_last_decrypt, + old_clock_values.time_of_last_decrypt); + // ODK_ParseLicense sets the new timer state. + clock_values_.timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; } - uint64_t system_time(uint64_t rental_clock) { - return rental_clock_start_ + rental_clock; + // Simulate loading or reloading a license, then verify that we are allowed + // playback from |start| to |stop|. If |cutoff| is not 0, then expect the + // timer to expire at that time. If |cutoff| is 0, expect the timer is not + // set. If you refer to the diagrams in "License Duration and Renewal", this + // tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void LoadAndAllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ReloadLicense(start); + AllowPlayback(start, stop, cutoff); + } + + // Verify that we are allowed playback from |start| to |stop|. If |cutoff| is + // not 0, then expect the timer to expire at that time. If |cutoff| is 0, + // expect the timer is not set. If you refer to the diagrams in "License + // Duration and Renewal", this tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void AllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + uint64_t timer_value; + const OEMCryptoResult result = ODK_AttemptFirstPlayback( + start, &timer_limits_, &clock_values_, &timer_value); + // After first playback, the license is active. + EXPECT_EQ(clock_values_.status, kActive); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + EXPECT_EQ(timer_value, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + CheckClockValues(start); + const uint64_t mid = (start + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(mid); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(stop); + } + + // Simulate loading or reloading a license, then attempt to play from |start| + // to |cutoff|. Verify that we are allowed playback from |start| to |cutoff|, + // but playback is not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. When + // nonzero |start|, and |cutoff| are all system times. + void LoadAndTerminatePlayback(uint64_t start, uint64_t cutoff) { + ReloadLicense(start); + TerminatePlayback(start, cutoff, cutoff + 10); + } + + // Attempt to play from |start| to |stop|. Verify that we are allowed playback + // from |start| to |cutoff|, but playback not allowed after |cutoff|. If you + // refer to the diagrams in "License Duration and Renewal", this tests a cyan + // bar with a black X. This assumes that |cutoff| is before |stop|. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void TerminatePlayback(uint64_t start, uint64_t cutoff, uint64_t stop) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + AllowPlayback(start, cutoff, cutoff); + EXPECT_EQ( + ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + const uint64_t mid = (cutoff + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + // times do not change if playback was not allowed. + CheckClockValues(cutoff); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + } + + // Verify that we are not allowed playback at the |start| time. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan line + // followed by a black X. The parameter |start| is system time. + void ForbidPlayback(uint64_t start) { + ReloadLicense(start); + ODK_ClockValues old_clock_values = clock_values_; + uint64_t timer_value; + EXPECT_EQ(ODK_AttemptFirstPlayback(start, &timer_limits_, &clock_values_, + &timer_value), + ODK_TIMER_EXPIRED); + // These should not have changed. In particular, if the license was unused + // before, it should reamin unused. + EXPECT_EQ(clock_values_.time_of_license_signed, + old_clock_values.time_of_license_signed); + EXPECT_EQ(clock_values_.time_of_first_decrypt, + old_clock_values.time_of_first_decrypt); + EXPECT_EQ(clock_values_.time_of_last_decrypt, + old_clock_values.time_of_last_decrypt); + EXPECT_EQ(clock_values_.status, old_clock_values.status); + } + + // Verify that the clock values are correct. + void CheckClockValues(uint64_t time_of_last_decrypt) { + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_first_decrypt, start_of_playback_); + EXPECT_EQ(clock_values_.time_of_last_decrypt, time_of_last_decrypt); + EXPECT_EQ(clock_values_.status, kActive); + } + + // Convert from rental time to system time. By "system time", we mean + // OEMCrypto's montonic clock. The spec does not specify a starting time for + // this clock. + uint64_t GetSystemTime(uint64_t rental_clock) { + return kRentalClockStart + rental_clock; + } + + // The end of the playback window. (using system clock) + // This is not useful if the playback duration is 0. + uint64_t EndOfPlaybackWindow() { + return start_of_playback_ + timer_limits_.total_playback_duration_seconds; + } + + // The end of the rental window. (using system clock) + // This is not useful if the rental duration is 0. + uint64_t EndOfRentalWindow() { + return GetSystemTime(timer_limits_.earliest_playback_start_seconds) + + timer_limits_.rental_duration_seconds; } ODK_TimerLimits timer_limits_; ODK_ClockValues clock_values_; - - // The rental clock starts when the request is signed. - uint64_t rental_clock_start_; - // start of rental window in seconds since rental clock start. - uint64_t rental_window_start_; - // The "width" of window. - uint64_t rental_window_duration_; + // The start of playback. This is set to the planned start at the beginning of + // the test. (using system clock) + uint64_t start_of_playback_; }; -TEST_F(OdkTimerRentalWindow, EarlyTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, &timer_value)); - const uint64_t good_start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(good_start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); +TEST_F(ODKTimerTest, SimplePlayback) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); } -TEST_F(OdkTimerRentalWindow, NullTimer) { - // If OEMCrypto passes in a nullpointer, then the ODK should allow this. +// This tests that we are not allowed to start playback before the rental window +// opens. +TEST_F(ODKTimerTest, EarlyTest) { + timer_limits_.earliest_playback_start_seconds = 100u; + // This is earlier than we are allowed. + const uint64_t bad_start_time = + GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); + // An attempt to start playback before the timer starts is an error. + // We use the TIMER_EXPIRED error to mean both early or late. + ForbidPlayback(bad_start_time); + // And times were not updated: + EXPECT_EQ(clock_values_.status, kUnused); + // This is when we will attempt the first valid playback. + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// This runs the same test as above, but explicitly gives the ODK library a null +// pointer for the timer value. A null pointer is allowed for OEMCrypto +// implementations that do not manage their own timer. +TEST_F(ODKTimerTest, NullTimer) { + timer_limits_.earliest_playback_start_seconds = 100u; + // This is the earlier, invalid start time. + const uint64_t bad_start_time = + GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); + timer_limits_.rental_duration_seconds = 300; + timer_limits_.soft_enforce_rental_duration = false; + ReloadLicense(GetSystemTime(bad_start_time)); + + // If OEMCrypto passes in a null pointer, then the ODK should allow this. uint64_t* timer_value_pointer = nullptr; - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, timer_value_pointer)); - const uint64_t good_start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(good_start_time, &timer_limits_, - &clock_values_, timer_value_pointer)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); + EXPECT_EQ(ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, + &clock_values_, timer_value_pointer), + ODK_TIMER_EXPIRED); + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + EXPECT_EQ(ODK_AttemptFirstPlayback(start_of_playback_, &timer_limits_, + &clock_values_, timer_value_pointer), + ODK_SET_TIMER); + CheckClockValues(start_of_playback_); } -// Verify that an attempt to start playback outside the rental window fails. -TEST_F(OdkTimerRentalWindow, LateTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t start_time = system_time(201); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); -} +/*****************************************************************************/ +// Note on Use Case tests. The test classes below correspond to the use cases +// in the doucment "License Duration and Renewal.". Each diagram in that +// document has a test class below to verify the use case is supported. +// +// In the document, we use realistic rental times in hours or days. In these +// tests, we will use round numbers so that it is easier to read. For example, +// instead of a seven day rental duration, we will use a 700 rental duration. +/*****************************************************************************/ -// ************************************************************************ -// The Hard Rental use case is a strict time limit on playback. -class OdkTimerHardTest : public OdkTimerRentalWindow { - protected: - void SetUp() override { - OdkTimerRentalWindow::SetUp(); - timer_limits_.soft_expiry = false; // Hard expiry. - // License duration is 200. The license starts when it was signed at 50, - // so the license is valid from 50-250. The rental window is 100-200 -- as - // inherited from the ODKRentalWindow class. - timer_limits_.license_duration_seconds = 200u; - } -}; - -TEST_F(OdkTimerHardTest, EarlyTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); -} - -TEST_F(OdkTimerHardTest, NormalTest) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. We are allowed to continue - // playback after the rental window expires as long as the first decrypt is - // within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - // Try to play after the cutoff time is not valid. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); -} - -// This verifies that the rental window only affects the first load for the -// license, not the first load for the session. -TEST_F(OdkTimerHardTest, Reload) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(75, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - - // We can restart playback before the cutoff time. - const uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - - // This restart is outside the rental window. We are allowed to - // restart playback after the rental window expires as long as the first - // decrypt for the license is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time); - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); - - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. That means we are allowed - // to continue playback after the rental window expires as long as the first - // decrypt is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - // Try to play after the cutoff time is not valid. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); -} - -TEST_F(OdkTimerHardTest, ReloadLate) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - - // We can not restart playback after the cutoff time. - const uint64_t late_restart_time = cutoff_time + 10; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); -} - -// ************************************************************************ -// The Soft Rental use case is a strict time limit on playback. -class OdkTimerSoftTest : public OdkTimerRentalWindow { - protected: - void SetUp() override { - OdkTimerRentalWindow::SetUp(); - timer_limits_.soft_expiry = true; // Soft expiry. - // License duration is 200. The license starts when it was signed at 50, - // so the license is valid from 50-250. The rental window is 100-200 -- as - // inherited from the ODKRentalWindow class. - timer_limits_.license_duration_seconds = 200u; - } -}; - -TEST_F(OdkTimerSoftTest, EarlyTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, &timer_value)); - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); -} - -TEST_F(OdkTimerSoftTest, NormalTest) { - uint64_t timer_value = 0; - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. We are allowed to continue - // playback after the rental window expires as long as the first decrypt is - // within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - // Try to play after the cutoff time is still valid for soft expiry. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); -} - -// This verifies that the rental window only affects the first load for the -// license, not the first load for the session. -TEST_F(OdkTimerSoftTest, Reload) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); - - // We can restart playback before the cutoff time. - const uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - - // This restart is outside the rental window. We are allowed to - // restart playback after the rental window expires as long as the first - // decrypt for the license is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time); - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); - - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. That means we are allowed - // to continue playback after the rental window expires as long as the first - // decrypt is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); - // Try to play after the cutoff time is still valid. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); -} - -TEST_F(OdkTimerSoftTest, ReloadLate) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); - - // We can not restart playback after the cutoff time. - const uint64_t late_restart_time = cutoff_time + 10; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); -} -// ************************************************************************ - -// ************************************************************************ -// From the server's point of view, there are two flags that decide soft or -// hard limits: the rental duration, and the playback duration. From -// OEMCrypto's point of view, there is only playback duration. A soft or hard -// rental duration is translated into different rental and license durations. -// The four test classes below all have a 700 rental window and a 200 playback -// duration. We'll use the server description, and then set the OEMCrypto -// restraints in the test SetUp() function. Note, it's easier to use the word -// "day" but really the rental window is 700 seconds, not 7 days. These tests -// have some coverage overlap with the ones above, but it is better to have -// these all grouped together to make sure we cover all of the server team's -// needs. -// ************************************************************************ -class Odk7DayTest : public OdkTimerRentalWindow, - public WithParamInterface { +/*****************************************************************************/ +// Streaming is the simplest use case. The user has three hours to watch the +// movie from the time of the rental. (See above for note on Use Case tests) +class ODKUseCase_Streaming : public ODKTimerTest { public: - Odk7DayTest() { - rental_window_duration_ = 700; - rental_window_start_ = 100u; + ODKUseCase_Streaming() { + // Rental duration = 3 hours hard. (use 300 for readability) + // Playback duration = 0 (unlimited) + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 300; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// Playback within rental duration. +TEST_F(ODKUseCase_Streaming, Case1) { + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow(), + EndOfRentalWindow()); +} + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case2) { + // Allow playback within the rental window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within rental duration, last one exceeds rental +// duration. +TEST_F(ODKUseCase_Streaming, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, + EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfRentalWindow()); +} + +// Playback within rental duration, restart exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Initial playback exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case5) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Streaming Quick Start. The user must start watching within 30 seconds, and +// then has three hours to finish. (See above for note on Use Case tests) +class ODKUseCase_StreamingQuickStart : public ODKTimerTest { + public: + ODKUseCase_StreamingQuickStart() { + // Rental duration = 30 seconds, soft. + // Playback duration = 3 hours (use 300 for readability) + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 30; + timer_limits_.total_playback_duration_seconds = 300; + timer_limits_.soft_enforce_playback_duration = false; + + // A valid start of playback time. + start_of_playback_ = + GetSystemTime(timer_limits_.rental_duration_seconds - 10); + } +}; + +// Playback starts within rental duration, continues within playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case1) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_LE(EndOfRentalWindow(), EndOfPlaybackWindow()); + // Allow playback within the playback window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfPlaybackWindow()); +} + +// Initial playback exceeds rental duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case4) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// smaller of the two time limits. The first four cases start on day +// three. (See above for note on Use Case tests) +class ODKUseCase_SevenHardTwoHard_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenHardTwoHard_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenHardTwoHard_Start6 + : public ODKUseCase_SevenHardTwoHard_Start3 { + public: + ODKUseCase_SevenHardTwoHard_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one is terminated +// at the end of the rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfRentalWindow()); + // Allow playback that starts within rental window, but terminate at end of + // rental window. + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// rental duration time limits. The first four cases start on day three. (See +// above for note on Use Case tests) +class ODKUseCase_SevenHardTwoSoft_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenHardTwoSoft_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfRentalWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and a little after. + // Timer expires at end of rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, + EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, + EndOfRentalWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenHardTwoSoft_Start6 + : public ODKUseCase_SevenHardTwoSoft_Start3 { + public: + ODKUseCase_SevenHardTwoSoft_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// playback duration. The first four cases start on day three. (See above for +// note on Use Case tests) +class ODKUseCase_SevenSoftTwoHard_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenSoftTwoHard_Start3() { + // Rental duration = 700, hard + // Playback duration = 300, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenSoftTwoHard_Start6 + : public ODKUseCase_SevenSoftTwoHard_Start3 { + public: + ODKUseCase_SevenSoftTwoHard_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback to continue beyond the rental window, but not beyond the + // playback window. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfPlaybackWindow()); +} + +// Restart exceeds rental duration, playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow()); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is not cutoff, +// but restarts are not allowed after playback duration. The first four cases +// start on day three. (See above for note on Use Case tests) +class ODKUseCase_SevenSoftTwoSoft_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenSoftTwoSoft_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and beyond. No timer limit. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, 0); + LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, 0); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenSoftTwoSoft_Start6 + : public ODKUseCase_SevenSoftTwoSoft_Start3 { + public: + ODKUseCase_SevenSoftTwoSoft_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow() + 25); + // Allow playback past the rental window, but within the playback window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow() + 25, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, 0); + LoadAndAllowPlayback(start_of_playback_ + 100, EndOfPlaybackWindow() + 50, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + // Allow playback to start after end of rental window, and continue after + // playback window. + LoadAndAllowPlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow() + 10, 0); + // But forbid restart after playback window. + ForbidPlayback(EndOfPlaybackWindow() + 20); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case8) { + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 100, 0); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +class RenewalTest : public ODKTimerTest { + protected: + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. + void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + uint64_t timer_value; + RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, + &timer_value); + } + + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. + // This does the work of the function above, except it allows the caller to + // explicitly set the timer_value_pointer. The timer_value_pointer can be a + // nullptr. + void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds, + uint64_t* timer_value_pointer) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + // We'll fake instantaneous renewal requests. Flight time not important. + clock_values_.time_of_renewal_request = start; + OEMCryptoResult result = ODK_ComputeRenewalDuration( + &timer_limits_, &clock_values_, start, renewal_duration_seconds, + timer_value_pointer); + // After first playback, the license is active. + EXPECT_EQ(clock_values_.status, kActive); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + if (timer_value_pointer != nullptr) + EXPECT_EQ(*timer_value_pointer, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + EXPECT_EQ(ODK_UpdateLastPlaybackTime(start, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(start); + const uint64_t mid = (start + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(mid); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(stop); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // after |cutoff|. Verify that we are allowed playback from |start| to + // |cutoff|, but playback not allowed after |cutoff|. If you refer to the + // diagrams in "License Duration and Renewal", this tests a cyan bar with a + // black X. + void RenewAndTerminate(uint64_t start, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + RenewAndTerminate(start, cutoff, cutoff + 100, renewal_duration_seconds); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // |stop|. Verify that we are allowed playback from |start| to |cutoff|, but + // playback not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. This + // assumes that |cutoff| is between |start| and |stop|. + void RenewAndTerminate(uint64_t start, uint64_t cutoff, uint64_t stop, + uint64_t renewal_duration_seconds) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + RenewAndContinue(start, cutoff, cutoff, renewal_duration_seconds); + EXPECT_EQ( + ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + const uint64_t mid = (cutoff + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues( + cutoff); // times do not change if playback was not allowed. + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + } + + // Verify that a renewal can be processed and playback may start from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. This is different from the previous functions, + // because the renewal is loaded before the first playback. + void RenewAndStart(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + // We'll fake instantaneous renewal requests. Flight time not important. + clock_values_.time_of_renewal_request = start; + uint64_t timer_value; + OEMCryptoResult result = + ODK_ComputeRenewalDuration(&timer_limits_, &clock_values_, start, + renewal_duration_seconds, &timer_value); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + EXPECT_EQ(timer_value, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + AllowPlayback(start, stop, cutoff); + } +}; + +// License with Renewal, limited by playback duration. (See above for note on +// Use Case tests) These tests are parameterized. If the parameter is 0, we +// limit the playback duration. If the parameter is 1, we limit the rental +// duration. The behavior is basically the same. +class ODKUseCase_LicenseWithRenewal + : public RenewalTest, + public ::testing::WithParamInterface { + public: + ODKUseCase_LicenseWithRenewal() { + // Either Playback or rental duration = 2 days hard + if (GetParam() == 0) { + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 2000; + } else { + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 2000; + } + timer_limits_.initial_renewal_duration_seconds = 50; + } + + void SetUp() override { + RenewalTest::SetUp(); + renewal_interval_ = + timer_limits_.initial_renewal_duration_seconds - kGracePeriod; + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, next_renewal, + next_renewal + kGracePeriod); + } + + uint64_t playback_end_restriction() { + if (GetParam() == 0) { + return EndOfRentalWindow(); + } else { + return EndOfPlaybackWindow(); + } } protected: - void SetUp() override { - OdkTimerRentalWindow::SetUp(); - server_expiry_ = GetParam(); - const uint64_t playback_duration = 200; - // Beginning of rental window. it is unusual to start it in the future, - // but it is a supported use case, so we'll test it here. - timer_limits_.earliest_playback_start_seconds = rental_window_start_; - // The rental window is 700 long. - timer_limits_.latest_playback_start_seconds = - timer_limits_.earliest_playback_start_seconds + rental_window_duration_; - // Playback duration is 200 starting from first playback. - timer_limits_.initial_playback_duration_seconds = playback_duration; - if (server_expiry_.soft_rental) { - // The license duration limits any restart. For soft rental window, the - // server will set this to the rental end + playback duration. - // License duration is in seconds since license signed. - timer_limits_.license_duration_seconds = - timer_limits_.latest_playback_start_seconds + playback_duration; - } else { - // The license duration limits any restart. For hard rental window, the - // server will set this to the rental end. - // License duration is in seconds since license signed. - timer_limits_.license_duration_seconds = - timer_limits_.latest_playback_start_seconds; - } - timer_limits_.soft_expiry = server_expiry_.soft_playback; - } - ServerExpiry server_expiry_; + // How long to wait before we load the next renewal. i.e. cutoff - + // kGracePeriod. This is because the renewal_duration includes the grace + // period. + uint64_t renewal_interval_; }; -TEST_P(Odk7DayTest, StartDay3) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t three_days = 300u; - const uint64_t start_time = system_time(rental_window_start_ + three_days); - const uint64_t cutoff_time = - start_time + timer_limits_.initial_playback_duration_seconds; - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - } - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 50; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); +// Playback within rental duration and renewal duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case1) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } } -TEST_P(Odk7DayTest, StartDay3Reload) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t three_days = 300u; - const uint64_t start_time = system_time(rental_window_start_ + three_days); - const uint64_t cutoff_time = - start_time + timer_limits_.initial_playback_duration_seconds; - EXPECT_NE(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - // ----------------------------------------------------------------------- - // Try to reload and play before the cutoff time is valid. - uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); +// Playback within rental duration, last renewal_interval_ exceeds renewal +// duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case2) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } + // We attempt to continue playing beyond the renewal renewal_interval_. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); +// Playback interrupted by late renewal. +TEST_P(ODKUseCase_LicenseWithRenewal, Case3) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } + // We attempt to continue playing beyond the renewal renewal_interval_. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // stop: when timer expires. + timer_limits_.initial_renewal_duration_seconds); - // Try to play after the cutoff time is valid for soft expiry, but not hard. - const uint64_t late_play_time = cutoff_time + 1; - if (server_expiry_.soft_playback) { - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should change because we were allowed to decrypt. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + const uint64_t late_renewal = next_renewal + kGracePeriod + 10; + next_renewal = late_renewal + renewal_interval_; + RenewAndContinue(late_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} + +// Playback & restart within playback duration and renewal duration. Note: this +// simulates reloading a persistent license, and then reloading the last +// renewal. +TEST_P(ODKUseCase_LicenseWithRenewal, Case4) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 2; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } - // First decrypt should NOT change for either soft or hard. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // ----------------------------------------------------------------------- - // Try to reload after the cutoff time is not valid for soft or hard - // playback expiry. - const uint64_t late_restart_time = cutoff_time + 1; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should not change. - if (server_expiry_.soft_playback) { - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + // Reload license after timer expired. + uint64_t reload_time = next_renewal + kGracePeriod + 100; + next_renewal = reload_time + renewal_interval_; + ReloadLicense(reload_time); + RenewAndStart(reload_time, next_renewal, next_renewal + kGracePeriod, + timer_limits_.initial_renewal_duration_seconds); + for (int i = 0; i < 2; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } } -TEST_P(Odk7DayTest, StartDay6Reload) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t six_days = 600u; - const uint64_t start_time = system_time(rental_window_start_ + six_days); - const uint64_t cutoff_time = - server_expiry_.soft_rental - ? - // If the rental expiry is soft, we can continue playing and - // reloading for the full playback duration. - start_time + timer_limits_.initial_playback_duration_seconds - // If the rental expiry is hard, we can reload only within the - // rental window. - : system_time(timer_limits_.latest_playback_start_seconds); - // Let's double check that math: - if (server_expiry_.soft_rental) { - // If that was not clear, the cutoff = 100 start of window + 600 start time - // + 200 playback duration... - EXPECT_EQ(system_time(900u), cutoff_time); - } else { - // ...and for hard rental, the cutoff = 100 start of window + 700 rental - // duration. - EXPECT_EQ(system_time(800u), cutoff_time); - } - - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - } - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - // Try to play before the cutoff time is valid. - uint64_t valid_play_time = cutoff_time - 50; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - } - - // ----------------------------------------------------------------------- - // Try to reload and play before the cutoff time is valid. - uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); - } - - // Try to play before the cutoff time is valid. - valid_play_time = cutoff_time - 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - } - - // Try to play after the cutoff time is valid for soft expiry, but not hard. - const uint64_t late_play_time = cutoff_time + 1; - if (server_expiry_.soft_playback) { - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should change because we were allowed to decrypt. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - } - // First decrypt should NOT change for either soft or hard. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // ----------------------------------------------------------------------- - // Try to reload after the cutoff time is not valid for soft or hard - // playback expiry. - const uint64_t late_restart_time = cutoff_time + 2; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should not change. - if (server_expiry_.soft_playback) { - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - } +// Playback within renewal duration, but exceeds playback duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case5) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + do { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } while (next_renewal + renewal_interval_ + kGracePeriod < + playback_end_restriction()); + // Attemt playing beyond the playback window. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + playback_end_restriction(), // stop: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } -// This test explicitly shows the difference between hard and soft rental -// expiry. This is not an OEMCrypto concept, but it is used on the serer, and -// this test verifies the logic is handled correctly when we translate it into -// OEMCrypto concepts. -TEST_P(Odk7DayTest, StartDay6ReloadDay7) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t six_days = 600u; - const uint64_t start_time = system_time(rental_window_start_ + six_days); - EXPECT_NE(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); +// Renewal duration increases over time. +TEST_P(ODKUseCase_LicenseWithRenewal, Case6) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + do { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); + // Renewal_Interval_ increases with each renewal. + renewal_interval_ += 100; + } while (next_renewal + renewal_interval_ + kGracePeriod < + playback_end_restriction()); + // Attemt playing beyond the playback window: + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + playback_end_restriction(), // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); +} - // Reload time is 1 second after end of rental window. - const uint64_t restart_time = - system_time(timer_limits_.latest_playback_start_seconds + 1); - ReloadClock(restart_time); - if (server_expiry_.soft_rental) { - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(restart_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - const uint64_t cutoff_time = - start_time + timer_limits_.initial_playback_duration_seconds; - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, - kTolerance); - EXPECT_NEAR(cutoff_time - restart_time, timer_value, kTolerance); - } - } else { - // For hard rental expiry, reloading after the rental window fails. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(restart_time, &timer_limits_, - &clock_values_, &timer_value)); +// Increasing renewal duration, playback exceeds last renewal. +// This is a mix between case 2 and case 6. +TEST_P(ODKUseCase_LicenseWithRenewal, Case7) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); + // Renewal_Interval_ increases with each renewal. + renewal_interval_ += 100; } + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); } -TEST_P(Odk7DayTest, StartDay8) { - uint64_t timer_value = 0; - // Starting playback after the rental window should not work. - const uint64_t eight_days = 800u; - const uint64_t start_time = system_time(rental_window_start_ + eight_days); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); - EXPECT_EQ(0u, clock_values_.time_of_first_decrypt); - EXPECT_EQ(0u, clock_values_.time_of_last_decrypt); - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime( - start_time, &timer_limits_, &clock_values_)); - EXPECT_EQ(kUnused, clock_values_.status); - EXPECT_EQ(0u, clock_values_.time_of_first_decrypt); - EXPECT_EQ(0u, clock_values_.time_of_last_decrypt); +// This is just like Case1, except we use a null pointer for the timer values in +// RenewAndContinue. It is not shown in the use case document. +TEST_P(ODKUseCase_LicenseWithRenewal, NullPointerTest) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + const uint64_t start = next_renewal; + const uint64_t stop = start + renewal_interval_; + const uint64_t cutoff = stop + kGracePeriod; + const uint64_t renewal_duration_seconds = + timer_limits_.initial_renewal_duration_seconds; + uint64_t* timer_value_pointer = nullptr; + RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, + timer_value_pointer); } -INSTANTIATE_TEST_CASE_P(OdkSoftHard, Odk7DayTest, - Values(ServerExpiry({true, true}), - ServerExpiry({true, false}), - ServerExpiry({false, true}), - ServerExpiry({false, false}))); +INSTANTIATE_TEST_CASE_P(RestrictRenewal, ODKUseCase_LicenseWithRenewal, + ::testing::Values(0, 1)); +// Limited Duration License. (See above for notes on Use Case tests). The user +// has 15 minutes to begin watching the movie. If a renewal is not received, +// playback is terminated after 30 seconds. If a renewal is received, playback +// may continue for two hours from playback start. +class ODKUseCase_LimitedDurationLicense : public RenewalTest { + public: + ODKUseCase_LimitedDurationLicense() { + renewal_delay_ = 30u; + time_of_renewal_ = start_of_playback_ + renewal_delay_; + + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 150; // 15 minutes. + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 2000; // Two hours. + timer_limits_.initial_renewal_duration_seconds = + renewal_delay_ + kGracePeriod; + } + uint64_t renewal_delay_; + uint64_t time_of_renewal_; +}; + +// Playback started within rental window and continues. +TEST_F(ODKUseCase_LimitedDurationLicense, Case1) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + const uint64_t play_for_one_hour = GetSystemTime(1000); + RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. + play_for_one_hour, // stop: expect play allowed. + EndOfPlaybackWindow(), // cutoff: when timer expires. + renewal_duration); +} + +// Playback started after rental duration. +TEST_F(ODKUseCase_LimitedDurationLicense, Case2) { + start_of_playback_ = EndOfRentalWindow() + 1; + EXPECT_NEAR(start_of_playback_, GetSystemTime(150), 5); + ForbidPlayback(start_of_playback_); +} + +// Playback started within rental window but renewal not received. +TEST_F(ODKUseCase_LimitedDurationLicense, Case3) { + // Allow playback within the initial renewal window. + LoadAndTerminatePlayback(start_of_playback_, time_of_renewal_ + kGracePeriod); +} + +// Playback started within rental window and continues. +TEST_F(ODKUseCase_LimitedDurationLicense, Case4) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + RenewAndTerminate(time_of_renewal_, // start: when renewal is loaded. + EndOfPlaybackWindow(), // stop: when timer expires. + renewal_duration); +} + +// Playback may be restarted. +TEST_F(ODKUseCase_LimitedDurationLicense, Case5) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + const uint64_t play_for_one_hour = GetSystemTime(1000); + RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. + play_for_one_hour, // stop: expect play allowed. + EndOfPlaybackWindow(), // cutoff: when timer expires. + renewal_duration); + + uint64_t reload_time = play_for_one_hour + 100; + ReloadLicense(reload_time); + RenewAndStart(reload_time, EndOfPlaybackWindow(), EndOfPlaybackWindow(), + renewal_duration); + // But not one second more. + EXPECT_EQ(ODK_UpdateLastPlaybackTime(EndOfPlaybackWindow() + 1, + &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(EndOfPlaybackWindow()); +} + +// Verify that the backwards compatible function, ODK_InitializeV15Values, can +// set up the clock values and timer limits. +TEST_F(RenewalTest, V15Test) { + const uint32_t key_duration = 25; + ODK_NonceValues nonce_values; + const uint64_t license_loaded = GetSystemTime(10); + EXPECT_EQ( + ODK_InitializeV15Values(&timer_limits_, &clock_values_, &nonce_values, + key_duration, license_loaded), + OEMCrypto_SUCCESS); + const uint64_t later_on = GetSystemTime(200); + TerminatePlayback(license_loaded, license_loaded + key_duration, later_on); + const uint32_t new_key_duration = 100; + RenewAndTerminate(later_on, later_on + new_key_duration, new_key_duration); +} } // namespace diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.cpp index b313fb44..4b401ab6 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.cpp @@ -42,11 +42,15 @@ CryptoEngine::~CryptoEngine() { } bool CryptoEngine::Initialize() { + std::string file_path = GetUsageTimeFileFullPath(); + LoadOfflineTimeInfo(file_path); usage_table_.reset(MakeUsageTable()); return true; } void CryptoEngine::Terminate() { + std::string file_path = GetUsageTimeFileFullPath(); + SaveOfflineTimeInfo(file_path); std::unique_lock lock(session_table_lock_); ActiveSessions::iterator it; for (it = sessions_.begin(); it != sessions_.end(); ++it) { @@ -102,44 +106,53 @@ int64_t CryptoEngine::OnlineTime() { } int64_t CryptoEngine::RollbackCorrectedOfflineTime() { - struct TimeInfo { - // The max time recorded through this function call. - int64_t previous_time; - // If the wall time is rollbacked to before the previous_time, this member - // is updated to reflect the offset. - int64_t rollback_offset; - // Pad the struct so that TimeInfo is a multiple of 16. - uint8_t padding[16 - (2 * sizeof(time_t)) % 16]; - }; + // Add any time offsets in the past to the current time. + int64_t current_time = OnlineTime() + offline_time_info_.rollback_offset; + // Write time info to disk if kTimeInfoUpdateWindowInSeconds has elapsed since + // last write. + if (current_time - offline_time_info_.previous_time > + kTimeInfoUpdateWindowInSeconds) { + std::string file_path = GetUsageTimeFileFullPath(); + SaveOfflineTimeInfo(file_path); + } + return current_time; +} - std::vector encrypted_buffer(sizeof(TimeInfo)); - std::vector clear_buffer(sizeof(TimeInfo)); - TimeInfo time_info; - memset(&time_info, 0, sizeof(time_info)); - // Use the device key for encrypt/decrypt. - const std::vector& key = DeviceRootKey(); - - std::unique_ptr file; - std::string path; - // Note: this path is OK for a real implementation, but using security level 1 - // would be better. +std::string CryptoEngine::GetUsageTimeFileFullPath() const { + std::string file_path; + // Note: file path is OK for a real implementation, but using security + // level 1 would be better. // TODO(fredgc, jfore): Address how this property is presented to the ref. - // For now, the path is empty. + // For now, the file path is empty. /*if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3, - &path)) { - LOGE("RollbackCorrectedOfflineTime: Unable to get base path"); + &file_path)) { + LOGE("RollbackCorrectedOfflineTime: Unable to get base path"); }*/ - std::string filename = path + "StoredUsageTime.dat"; + return file_path + kStoredUsageTimeFileName; +} +bool CryptoEngine::LoadOfflineTimeInfo(const std::string& file_path) { + memset(&offline_time_info_, 0, sizeof(TimeInfo)); wvcdm::FileSystem* file_system = file_system_.get(); - if (file_system->Exists(filename)) { - // Load time info from previous call to this function. - file = file_system->Open(filename, wvcdm::FileSystem::kReadOnly); + if (file_system->Exists(file_path)) { + std::vector encrypted_buffer(sizeof(TimeInfo)); + std::vector clear_buffer(sizeof(TimeInfo)); + + KeyboxError error_code = ValidateKeybox(); + if (error_code != NO_ERROR) { + LOGE("Keybox is invalid: %d", error_code); + return false; + } + // Use the device key for encrypt/decrypt. + const std::vector& key = DeviceRootKey(); + std::unique_ptr file = + file_system->Open(file_path, wvcdm::FileSystem::kReadOnly); if (!file) { LOGE("RollbackCorrectedOfflineTime: File open failed: %s", - filename.c_str()); - return OnlineTime(); + file_path.c_str()); + return false; } + // Load time info from previous call. file->Read(reinterpret_cast(&encrypted_buffer[0]), sizeof(TimeInfo)); // Decrypt the encrypted TimeInfo buffer. AES_KEY aes_key; @@ -147,42 +160,65 @@ int64_t CryptoEngine::RollbackCorrectedOfflineTime() { std::vector iv(wvoec::KEY_IV_SIZE, 0); AES_cbc_encrypt(&encrypted_buffer[0], &clear_buffer[0], sizeof(TimeInfo), &aes_key, iv.data(), AES_DECRYPT); - memcpy(&time_info, &clear_buffer[0], sizeof(TimeInfo)); - } + memcpy(&offline_time_info_, &clear_buffer[0], sizeof(TimeInfo)); - int64_t current_time; - // Add any time offsets in the past to the current time. - current_time = OnlineTime() + time_info.rollback_offset; - if (time_info.previous_time > current_time) { - // Time has been rolled back. - // Update the rollback offset. - time_info.rollback_offset += time_info.previous_time - current_time; - // Keep current time at previous recorded time. - current_time = time_info.previous_time; + // Detect offline time rollback after loading from disk. + // Add any time offsets in the past to the current time. + int64_t current_time = OnlineTime() + offline_time_info_.rollback_offset; + if (offline_time_info_.previous_time > current_time) { + // Current time is earlier than the previously saved time. Time has been + // rolled back. Update the rollback offset. + offline_time_info_.rollback_offset += + offline_time_info_.previous_time - current_time; + // Keep current time at previous recorded time. + current_time = offline_time_info_.previous_time; + } + // The new previous_time will either stay the same or move forward. + offline_time_info_.previous_time = current_time; } + return true; +} + +bool CryptoEngine::SaveOfflineTimeInfo(const std::string& file_path) { + // Add any time offsets in the past to the current time. If there was an + // earlier offline rollback, the rollback offset will be updated in + // LoadOfflineTimeInfo(). It guarantees that the current time to be saved + // will never go back. + int64_t current_time = OnlineTime() + offline_time_info_.rollback_offset; // The new previous_time will either stay the same or move forward. - time_info.previous_time = current_time; + if (current_time > offline_time_info_.previous_time) + offline_time_info_.previous_time = current_time; + + KeyboxError error_code = ValidateKeybox(); + if (error_code != NO_ERROR) { + LOGE("Keybox is invalid: %d", error_code); + return false; + } + // Use the device key for encrypt/decrypt. + const std::vector& key = DeviceRootKey(); + std::vector encrypted_buffer(sizeof(TimeInfo)); + std::vector clear_buffer(sizeof(TimeInfo)); // Copy updated data and encrypt the buffer. - memcpy(&clear_buffer[0], &time_info, sizeof(TimeInfo)); + memcpy(&clear_buffer[0], &offline_time_info_, sizeof(TimeInfo)); AES_KEY aes_key; AES_set_encrypt_key(&key[0], 128, &aes_key); std::vector iv(wvoec::KEY_IV_SIZE, 0); AES_cbc_encrypt(&clear_buffer[0], &encrypted_buffer[0], sizeof(TimeInfo), &aes_key, iv.data(), AES_ENCRYPT); + std::unique_ptr file; + wvcdm::FileSystem* file_system = file_system_.get(); // Write the encrypted buffer to disk. file = file_system->Open( - filename, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate); + file_path, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate); if (!file) { LOGE("RollbackCorrectedOfflineTime: File open failed: %s", - filename.c_str()); - return OnlineTime(); + file_path.c_str()); + return false; } file->Write(reinterpret_cast(&encrypted_buffer[0]), sizeof(TimeInfo)); - - // Return time with offset. - return current_time; + return true; } bool CryptoEngine::NonceCollision(uint32_t nonce) { diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.h b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.h index d111b734..dd39e16f 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.h +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.h @@ -29,10 +29,23 @@ namespace wvoec_ref { typedef std::map ActiveSessions; +static const std::string kStoredUsageTimeFileName = "StoredUsageTime.dat"; + +typedef struct { + // The max time recorded + int64_t previous_time; + // If the wall time is rollbacked to before the previous_time, this member + // is updated to reflect the offset. + int64_t rollback_offset; + // Pad the struct so that TimeInfo is a multiple of 16. + uint8_t padding[16 - (2 * sizeof(time_t)) % 16]; +} TimeInfo; + class CryptoEngine { public: static const uint32_t kApiVersion = 16; static const uint32_t kMinorApiVersion = 0; + static const int64_t kTimeInfoUpdateWindowInSeconds = 300; // This is like a factory method, except we choose which version to use at // compile time. It is defined in several source files. The build system @@ -217,6 +230,10 @@ class CryptoEngine { // System clock with antirollback protection, measuring time in seconds. int64_t RollbackCorrectedOfflineTime(); + bool LoadOfflineTimeInfo(const std::string& file_path); + bool SaveOfflineTimeInfo(const std::string& file_path); + std::string GetUsageTimeFileFullPath() const; + explicit CryptoEngine(std::unique_ptr&& file_system); virtual SessionContext* MakeSession(SessionId sid); virtual UsageTable* MakeUsageTable(); @@ -226,6 +243,7 @@ class CryptoEngine { std::mutex session_table_lock_; std::unique_ptr file_system_; std::unique_ptr usage_table_; + TimeInfo offline_time_info_; CORE_DISALLOW_COPY_AND_ASSIGN(CryptoEngine); }; diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_ref.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_ref.cpp index 05e6f26a..cfede60b 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_ref.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_ref.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -356,8 +357,9 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadKeys( "range check iv]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } else { - if (memcmp(message + enc_mac_keys.offset - wvoec::KEY_IV_SIZE, - message + enc_mac_keys_iv.offset, wvoec::KEY_IV_SIZE) == 0) { + if (CRYPTO_memcmp(message + enc_mac_keys.offset - wvoec::KEY_IV_SIZE, + message + enc_mac_keys_iv.offset, + wvoec::KEY_IV_SIZE) == 0) { LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT - " "suspicious iv]"); return OEMCrypto_ERROR_INVALID_CONTEXT; @@ -465,14 +467,17 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RefreshKeys( return OEMCrypto_ERROR_INVALID_CONTEXT; } - // Range check - for (size_t i = 0; i < num_keys; i++) { - if (!RangeCheck(message_length, key_array[i].key_id, true) || - !RangeCheck(message_length, key_array[i].key_control, false) || - !RangeCheck(message_length, key_array[i].key_control_iv, true)) { - LOGE("Range Check %zu", i); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + // We only use the first key object to update the entire license. Since we + // know num_keys > 0 after the last if statement, we can assume index is not + // out of bounds. + constexpr size_t kIndex = 0; + + // Range check. + if (!RangeCheck(message_length, key_array[kIndex].key_id, true) || + !RangeCheck(message_length, key_array[kIndex].key_control, false) || + !RangeCheck(message_length, key_array[kIndex].key_control_iv, true)) { + LOGE("Range Check %zu", kIndex); + return OEMCrypto_ERROR_INVALID_CONTEXT; } // Validate message signature @@ -487,37 +492,31 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RefreshKeys( std::vector key_id; std::vector key_control; std::vector key_control_iv; - for (size_t i = 0; i < num_keys; i++) { - if (key_array[i].key_id.length != 0) { - key_id.assign( - message + key_array[i].key_id.offset, - message + key_array[i].key_id.offset + key_array[i].key_id.length); - key_control.assign( - message + key_array[i].key_control.offset, - message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE); - if (key_array[i].key_control_iv.length == 0) { - key_control_iv.clear(); - } else { - key_control_iv.assign( - message + key_array[i].key_control_iv.offset, - message + key_array[i].key_control_iv.offset + wvoec::KEY_IV_SIZE); - } - } else { - // key_id could be null if special control key type - // key_control is not encrypted in this case - key_id.clear(); + if (key_array[kIndex].key_id.length != 0) { + key_id.assign(message + key_array[kIndex].key_id.offset, + message + key_array[kIndex].key_id.offset + + key_array[kIndex].key_id.length); + key_control.assign(message + key_array[kIndex].key_control.offset, + message + key_array[kIndex].key_control.offset + + wvoec::KEY_CONTROL_SIZE); + if (key_array[kIndex].key_control_iv.length == 0) { key_control_iv.clear(); - key_control.assign( - message + key_array[i].key_control.offset, - message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE); - } - - status = session_ctx->RefreshKey(key_id, key_control, key_control_iv); - if (status != OEMCrypto_SUCCESS) { - LOGE("error %d in key %zu", status, i); - break; + } else { + key_control_iv.assign(message + key_array[kIndex].key_control_iv.offset, + message + key_array[kIndex].key_control_iv.offset + + wvoec::KEY_IV_SIZE); } + } else { + // key_id could be null if special control key type + // key_control is not encrypted in this case + key_id.clear(); + key_control_iv.clear(); + key_control.assign(message + key_array[kIndex].key_control.offset, + message + key_array[kIndex].key_control.offset + + wvoec::KEY_CONTROL_SIZE); } + + status = session_ctx->RefreshKey(key_id, key_control, key_control_iv); if (status != OEMCrypto_SUCCESS) { return status; } diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp index ece666b1..4e74cf6e 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -429,7 +430,7 @@ OEMCryptoResult SessionContext::PrepAndSignRenewalRequest( } // If we are talking to an old license server, then we only sign the message // body. - if (nonce_values_.api_version < 16) { + if (nonce_values_.api_major_version < 16) { const uint8_t* message_body = message + *core_message_length; const size_t message_body_length = message_length - *core_message_length; return GenerateSignature(message_body, message_body_length, signature, @@ -643,7 +644,7 @@ bool SessionContext::ValidateMessage(const uint8_t* given_message, LOGE("ValidateMessage: Could not compute signature"); return false; } - if (memcmp(given_signature, computed_signature, signature_length)) { + if (CRYPTO_memcmp(given_signature, computed_signature, signature_length)) { LOGE("Invalid signature given: %s", wvcdm::HexEncode(given_signature, signature_length).c_str()); LOGE("Invalid signature computed: %s", @@ -1099,10 +1100,40 @@ OEMCryptoResult SessionContext::RefreshKey( if (session_keys_ == nullptr) { return OEMCrypto_ERROR_INVALID_CONTEXT; } + if (key_control.empty()) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + std::vector decrypted_key_control; + if (key_id.empty()) { + // Key control is not encrypted if key id is NULL + decrypted_key_control = key_control; + } else { + Key* content_key = session_keys_->Find(key_id); + if (nullptr == content_key) { + LOGE("Key ID not found."); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + const std::vector content_key_value = content_key->value(); + // Decrypt encrypted key control block + if (key_control_iv.empty()) { + decrypted_key_control = key_control; + } else { + if (!DecryptMessage(content_key_value, key_control_iv, key_control, + &decrypted_key_control, 128 /* key size */)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } + } + KeyControlBlock key_control_block(decrypted_key_control); + if (!key_control_block.valid()) { + LOGE("Parse key control error."); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + uint32_t new_key_duration = key_control_block.duration(); uint64_t* timer_value = nullptr; const OEMCryptoResult result = ODK_RefreshV15Values(&timer_limits_, &clock_values_, &nonce_values_, - ce_->SystemTime(), timer_value); + ce_->SystemTime(), new_key_duration, timer_value); if (result == ODK_SET_TIMER || result == ODK_DISABLE_TIMER) return OEMCrypto_SUCCESS; if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED; @@ -1375,7 +1406,8 @@ OEMCryptoResult SessionContext::Generic_Verify(const uint8_t* in_buffer, uint8_t computed_signature[SHA256_DIGEST_LENGTH]; if (HMAC(EVP_sha256(), &key[0], key.size(), in_buffer, buffer_length, computed_signature, &md_len)) { - if (0 == memcmp(signature, computed_signature, SHA256_DIGEST_LENGTH)) { + if (0 == + CRYPTO_memcmp(signature, computed_signature, SHA256_DIGEST_LENGTH)) { return OEMCrypto_SUCCESS; } else { return OEMCrypto_ERROR_SIGNATURE_FAILURE; diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp index 1c68c487..abe6167a 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -79,15 +80,17 @@ bool UsageTableEntry::VerifyPST(const uint8_t* pst, size_t pst_length) { if (pst_length > kMaxPSTLength) return false; if (data_.pst_length != pst_length) return false; if (!pst || !pst_length) return false; - return 0 == memcmp(pst, data_.pst, pst_length); + return 0 == CRYPTO_memcmp(pst, data_.pst, pst_length); } bool UsageTableEntry::VerifyMacKeys(const std::vector& server, const std::vector& client) { return (server.size() == wvoec::MAC_KEY_SIZE) && (client.size() == wvoec::MAC_KEY_SIZE) && - (0 == memcmp(&server[0], data_.mac_key_server, wvoec::MAC_KEY_SIZE)) && - (0 == memcmp(&client[0], data_.mac_key_client, wvoec::MAC_KEY_SIZE)); + (0 == CRYPTO_memcmp(&server[0], data_.mac_key_server, + wvoec::MAC_KEY_SIZE)) && + (0 == + CRYPTO_memcmp(&client[0], data_.mac_key_client, wvoec::MAC_KEY_SIZE)); } bool UsageTableEntry::SetMacKeys(const std::vector& server, @@ -117,7 +120,7 @@ OEMCryptoResult UsageTableEntry::ReportUsage(const std::vector& pst, data_.pst_length); return OEMCrypto_ERROR_WRONG_PST; } - if (memcmp(&pst[0], data_.pst, data_.pst_length)) { + if (CRYPTO_memcmp(&pst[0], data_.pst, data_.pst_length)) { LOGE("ReportUsage: wrong pst %s, should be %s.", wvcdm::b2a_hex(pst).c_str(), wvcdm::HexEncode(data_.pst, data_.pst_length).c_str()); return OEMCrypto_ERROR_WRONG_PST; @@ -254,7 +257,8 @@ OEMCryptoResult UsageTableEntry::LoadData(CryptoEngine* ce, uint32_t index, LOGE("LoadUsageEntry: Could not sign entry."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (memcmp(clear->signature, encrypted->signature, SHA256_DIGEST_LENGTH)) { + if (CRYPTO_memcmp(clear->signature, encrypted->signature, + SHA256_DIGEST_LENGTH)) { LOGE("LoadUsageEntry: Signature did not match."); LOGE("LoadUsageEntry: Invalid signature given: %s", wvcdm::HexEncode(encrypted->signature, sig_length).c_str()); @@ -530,7 +534,8 @@ OEMCryptoResult UsageTable::LoadUsageTableHeader( LOGE("LoadUsageTableHeader: Could not sign entry."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (memcmp(clear->signature, encrypted->signature, SHA256_DIGEST_LENGTH)) { + if (CRYPTO_memcmp(clear->signature, encrypted->signature, + SHA256_DIGEST_LENGTH)) { LOGE("LoadUsageTableHeader: Signature did not match."); LOGE("LoadUsageTableHeader: Invalid signature given: %s", wvcdm::HexEncode(encrypted->signature, sig_length).c_str()); diff --git a/libwvdrmengine/oemcrypto/test/common.mk b/libwvdrmengine/oemcrypto/test/common.mk index 1af5e227..8a0dfd84 100644 --- a/libwvdrmengine/oemcrypto/test/common.mk +++ b/libwvdrmengine/oemcrypto/test/common.mk @@ -5,6 +5,9 @@ ifeq ($(filter mips mips64, $(TARGET_ARCH)),) LOCAL_LDFLAGS+=-Wl,--hash-style=both endif +# The unit tests can access v15 functions through the dynamic adapter: +LOCAL_CFLAGS += -DTEST_OEMCRYPTO_V15 + LOCAL_SRC_FILES:= \ oec_device_features.cpp \ oec_decrypt_fallback_chain.cpp \ @@ -43,6 +46,7 @@ LOCAL_SHARED_LIBRARIES := \ libdl \ liblog \ libmedia_omx \ + libprotobuf-cpp-lite \ libstagefright_foundation \ libutils \ libz \ diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp index dfd44750..bf9d61a8 100644 --- a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp @@ -63,7 +63,6 @@ bool CanChangeTime() { void DeviceFeatures::Initialize() { if (initialized_) return; uses_keybox = false; - uses_certificate = false; loads_certificate = false; generic_crypto = false; usage_table = false; @@ -100,10 +99,6 @@ void DeviceFeatures::Initialize() { loads_certificate = false; } printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); - uses_certificate = (OEMCrypto_ERROR_NOT_IMPLEMENTED != - OEMCrypto_GenerateRSASignature(session, buffer, 0, buffer, - &size, kSign_RSASSA_PSS)); - printf("uses_certificate = %s.\n", uses_certificate ? "true" : "false"); generic_crypto = (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_Generic_Encrypt(session, buffer, 0, buffer, @@ -145,7 +140,6 @@ void DeviceFeatures::Initialize() { printf("NO_METHOD: Cannot derive known session keys.\n"); // Note: cast_receiver left unchanged because set by user on command line. uses_keybox = false; - uses_certificate = false; loads_certificate = false; generic_crypto = false; usage_table = false; @@ -174,7 +168,6 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { std::string filter = initial_filter; // clang-format off if (!uses_keybox) FilterOut(&filter, "*KeyboxTest*"); - if (!uses_certificate) FilterOut(&filter, "OEMCrypto*Cert*"); if (!loads_certificate) FilterOut(&filter, "OEMCryptoLoadsCert*"); if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); if (!cast_receiver) FilterOut(&filter, "*CastReceiver*"); diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.h b/libwvdrmengine/oemcrypto/test/oec_device_features.h index 9d2db5a1..9cef66f2 100644 --- a/libwvdrmengine/oemcrypto/test/oec_device_features.h +++ b/libwvdrmengine/oemcrypto/test/oec_device_features.h @@ -38,7 +38,6 @@ class DeviceFeatures { enum DeriveMethod derive_key_method; bool uses_keybox; // Device uses a keybox to derive session keys. - bool uses_certificate; // Device uses a certificate to derive session keys. bool loads_certificate; // Device can load a certificate from the server. bool generic_crypto; // Device supports generic crypto. bool cast_receiver; // Device supports alternate rsa signature padding. diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp index 776e822d..6c10c32f 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -137,26 +138,6 @@ class boringssl_ptr { CORE_DISALLOW_COPY_AND_ASSIGN(boringssl_ptr); }; -OEMCrypto_Substring GetSubstring(const std::string& message, - const std::string& field, bool set_zero) { - OEMCrypto_Substring substring; - if (set_zero || field.empty() || message.empty()) { - substring.offset = 0; - substring.length = 0; - } else { - size_t pos = message.find(field); - if (pos == std::string::npos) { - LOGW("GetSubstring : Cannot find offset for %s", field.c_str()); - substring.offset = 0; - substring.length = 0; - } else { - substring.offset = pos; - substring.length = field.length(); - } - } - return substring; -} - Test_PST_Report::Test_PST_Report(const std::string& pst_in, OEMCrypto_Usage_Entry_Status status_in) : status(status_in), pst(pst_in) { @@ -169,23 +150,29 @@ void RoundTrip::SignAndVerifyRequest() { // In the real world, a message should be signed by the client and // verified by the server. This simulates that. - vector data(message_size_); - for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; - OEMCryptoResult sts; size_t gen_signature_length = 0; size_t core_message_length = 0; - sts = + constexpr size_t small_size = 42; // arbitrary. + size_t message_size = + std::max(required_message_size_, core_message_length + small_size); + vector data(message_size, 0); + for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + ASSERT_EQ( PrepAndSignRequest(session()->session_id(), data.data(), data.size(), - &core_message_length, nullptr, &gen_signature_length); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - // If this fails, either the test needs to be modified, or the core message is - // very very large. - ASSERT_LT(core_message_length, message_size_); + &core_message_length, nullptr, &gen_signature_length), + OEMCrypto_ERROR_SHORT_BUFFER); + // Make the message buffer a little bigger than the core message, or the + // required size, whichever is larger. + message_size = + std::max(required_message_size_, core_message_length + small_size); + data.resize(message_size); + for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + vector gen_signature(gen_signature_length); - sts = PrepAndSignRequest(session()->session_id(), data.data(), data.size(), - &core_message_length, gen_signature.data(), - &gen_signature_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(PrepAndSignRequest(session()->session_id(), data.data(), + data.size(), &core_message_length, + gen_signature.data(), &gen_signature_length), + OEMCrypto_SUCCESS); if (global_features.api_version >= kCoreMessagesAPI) { ASSERT_GT(data.size(), core_message_length); std::string core_message(reinterpret_cast(data.data()), @@ -247,7 +234,7 @@ void ProvisioningRoundTrip::FillAndVerifyCoreRequest( EXPECT_TRUE( oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage( core_message_string, &core_request_)); - EXPECT_EQ(global_features.api_version, core_request_.api_version); + EXPECT_EQ(global_features.api_version, core_request_.api_major_version); EXPECT_EQ(session()->nonce(), core_request_.nonce); EXPECT_EQ(session()->session_id(), core_request_.session_id); size_t device_id_length = core_request_.device_id.size(); @@ -282,7 +269,7 @@ void ProvisioningRoundTrip::CreateDefaultResponse() { } else { response_data_.enc_message_key_length = 0; } - core_response_.key_type = OEMCrypto_Supports_RSA_2048bit; + core_response_.key_type = OEMCrypto_RSA_Private_Key; core_response_.enc_private_key = FindSubstring(response_data_.rsa_key, response_data_.rsa_key_length); core_response_.enc_private_key_iv = FindSubstring( @@ -296,12 +283,20 @@ void ProvisioningRoundTrip::EncryptAndSignResponse() { &encrypted_response_data_); core_response_.enc_private_key.length = encrypted_response_data_.rsa_key_length; - ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreProvisioningResponse( - core_response_, core_request_, &serialized_core_message_)); + if (global_features.api_version >= kCoreMessagesAPI) { + ASSERT_TRUE( + oemcrypto_core_message::serialize::CreateCoreProvisioningResponse( + core_response_, core_request_, &serialized_core_message_)); + } + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); // Stripe the encrypted message. - encrypted_response_.resize(message_size_); + encrypted_response_.resize(message_size); for (size_t i = 0; i < encrypted_response_.size(); i++) { - encrypted_response_[i] = i % 0x100; + encrypted_response_[i] = i & 0xFF; } ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size()); memcpy(encrypted_response_.data(), serialized_core_message_.data(), @@ -322,19 +317,83 @@ void ProvisioningRoundTrip::EncryptAndSignResponse() { OEMCryptoResult ProvisioningRoundTrip::LoadResponse(Session* session) { EXPECT_NE(session, nullptr); size_t wrapped_key_length = 0; - const OEMCryptoResult sts = OEMCrypto_LoadProvisioning( - session->session_id(), encrypted_response_.data(), - encrypted_response_.size(), serialized_core_message_.size(), - response_signature_.data(), response_signature_.size(), nullptr, - &wrapped_key_length); + const OEMCryptoResult sts = LoadResponseNoRetry(session, &wrapped_key_length); if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts; wrapped_rsa_key_.clear(); wrapped_rsa_key_.assign(wrapped_key_length, 0); - return OEMCrypto_LoadProvisioning( - session->session_id(), encrypted_response_.data(), - encrypted_response_.size(), serialized_core_message_.size(), - response_signature_.data(), response_signature_.size(), - wrapped_rsa_key_.data(), &wrapped_key_length); + return LoadResponseNoRetry(session, &wrapped_key_length); +} + +#ifdef TEST_OEMCRYPTO_V15 +// If this platform supports v15 functions, then will test with them: +# define OEMCrypto_RewrapDeviceRSAKey_V15 OEMCrypto_RewrapDeviceRSAKey +# define OEMCrypto_RewrapDeviceRSAKey30_V15 OEMCrypto_RewrapDeviceRSAKey30 + +#else +// If this platform does not support v15 functions, we just need to stub these +// out so that the tests compile. +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30_V15( + OEMCrypto_SESSION session, const uint32_t* unaligned_nonce, + const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, + const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, + size_t* wrapped_rsa_key_length) { + LOGE("Support for v15 functions not included. Define TEST_OEMCRYPTO_V15."); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey_V15( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + const uint32_t* unaligned_nonce, const uint8_t* enc_rsa_key, + size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, + uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length) { + LOGE("Support for v15 functions not included. Define TEST_OEMCRYPTO_V15."); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} +#endif + +template +const T* ProvisioningRoundTrip::RemapPointer(const T* response_pointer) const { + const uint8_t* original_pointer = + reinterpret_cast(response_pointer); + size_t delta = + original_pointer - reinterpret_cast(&response_data_); + // Base offset should be 0 if this is a v15 message, which is the only time + // this function is called. + size_t base_offset = serialized_core_message_.size(); + const uint8_t* new_pointer = encrypted_response_.data() + delta + base_offset; + return reinterpret_cast(new_pointer); +} + +OEMCryptoResult ProvisioningRoundTrip::LoadResponseNoRetry( + Session* session, size_t* wrapped_key_length) { + EXPECT_NE(session, nullptr); + if (global_features.api_version >= kCoreMessagesAPI) { + return OEMCrypto_LoadProvisioning( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size(), + wrapped_rsa_key_.data(), wrapped_key_length); + } else if (global_features.provisioning_method == OEMCrypto_Keybox) { + return OEMCrypto_RewrapDeviceRSAKey_V15( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), response_signature_.data(), + response_signature_.size(), RemapPointer(&response_data_.nonce), + RemapPointer(response_data_.rsa_key), + encrypted_response_data_.rsa_key_length, + RemapPointer(response_data_.rsa_key_iv), wrapped_rsa_key_.data(), + wrapped_key_length); + } else { + return OEMCrypto_RewrapDeviceRSAKey30_V15( + session->session_id(), &encrypted_response_data_.nonce, + RemapPointer(response_data_.enc_message_key), + response_data_.enc_message_key_length, + RemapPointer(response_data_.rsa_key), + encrypted_response_data_.rsa_key_length, + RemapPointer(response_data_.rsa_key_iv), wrapped_rsa_key_.data(), + wrapped_key_length); + } } void ProvisioningRoundTrip::VerifyLoadFailed() { @@ -346,10 +405,17 @@ void ProvisioningRoundTrip::VerifyLoadFailed() { void LicenseRoundTrip::VerifyRequestSignature( const vector& data, const vector& generated_signature, size_t core_message_length) { - std::vector subdata(data.begin() + core_message_length, data.end()); + const std::vector subdata(data.begin() + core_message_length, + data.end()); session()->VerifyRSASignature(subdata, generated_signature.data(), generated_signature.size(), kSign_RSASSA_PSS); - SHA256(data.data(), core_message_length, core_response_.request_hash); + SHA256(data.data(), core_message_length, request_hash_); + // If the api version was not set by the test, then we record the api version + // from the request. Also, if the api was set to be higher than oemcrypto + // supports, then we lower it. This version will be used in the response. + if (api_version_ == 0) api_version_ = core_request_.api_major_version; + if (api_version_ > global_features.api_version) + api_version_ = global_features.api_version; } void LicenseRoundTrip::FillAndVerifyCoreRequest( @@ -357,12 +423,16 @@ void LicenseRoundTrip::FillAndVerifyCoreRequest( EXPECT_TRUE( oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage( core_message_string, &core_request_)); - EXPECT_EQ(global_features.api_version, core_request_.api_version); + EXPECT_EQ(global_features.api_version, core_request_.api_major_version); + // If we are testing the latest OEMCrypto version, make sure it is built with + // the latest ODK version, too: + if (global_features.api_version == ODK_MAJOR_VERSION) { + EXPECT_EQ(ODK_MINOR_VERSION, core_request_.api_minor_version); + } if (expect_request_has_correct_nonce_) { EXPECT_EQ(session()->nonce(), core_request_.nonce); } EXPECT_EQ(session()->session_id(), core_request_.session_id); - if (api_version_ == 0) api_version_ = core_request_.api_version; } void LicenseRoundTrip::CreateDefaultResponse() { @@ -371,10 +441,12 @@ void LicenseRoundTrip::CreateDefaultResponse() { memset(response_data_.padding, 0, sizeof(response_data_.padding)); EXPECT_EQ(1, GetRandBytes(response_data_.mac_keys, sizeof(response_data_.mac_keys))); - // For backwards compatibility, we use the license duration for each key's - // duration. - uint32_t duration = static_cast( - core_response_.timer_limits.license_duration_seconds); + // For backwards compatibility, we use the largest limit in timer_limits for + // each key's duration. + uint32_t key_duration = static_cast( + std::max({core_response_.timer_limits.rental_duration_seconds, + core_response_.timer_limits.total_playback_duration_seconds, + core_response_.timer_limits.initial_renewal_duration_seconds})); // The key data for an entitlement license is an AES-256 key, otherwise the // default is an AES_128 key. uint32_t default_key_size = @@ -393,7 +465,7 @@ void LicenseRoundTrip::CreateDefaultResponse() { sizeof(response_data_.keys[i].control_iv))); std::string kcVersion = "kc" + std::to_string(api_version_); memcpy(response_data_.keys[i].control.verification, kcVersion.c_str(), 4); - response_data_.keys[i].control.duration = htonl(duration); + response_data_.keys[i].control.duration = htonl(key_duration); response_data_.keys[i].control.nonce = htonl(session_->nonce()); response_data_.keys[i].control.control_bits = htonl(control_); response_data_.keys[i].cipher_mode = OEMCrypto_CipherMode_CTR; @@ -493,12 +565,20 @@ void LicenseRoundTrip::EncryptAndSignResponse() { if (api_version_ < kCoreMessagesAPI) { serialized_core_message_.resize(0); } else { + std::string request_hash_string( + reinterpret_cast(request_hash_), sizeof(request_hash_)); ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreLicenseResponse( - core_response_, core_request_, &serialized_core_message_)); + core_response_, core_request_, request_hash_string, + &serialized_core_message_)); } + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); // Stripe the encrypted message. - encrypted_response_.resize(message_size_); + encrypted_response_.resize(message_size); for (size_t i = 0; i < encrypted_response_.size(); i++) { encrypted_response_[i] = i % 0x100; } @@ -522,15 +602,19 @@ void LicenseRoundTrip::EncryptAndSignResponse() { OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) { EXPECT_NE(session, nullptr); // Some tests adjust the offset to be beyond the length of the message. Here, - // we create a duplicate of the message buffer so that these offsets do not - // point to garbage data. The goal is to make sure OEMCrypto is verifying that - // the offset points outside of the message -- we don't want OEMCrypto to look - // at what offset points to and return an error if the data is garbage. Since - // the memory after the message buffer is an exact copy of the message, we can - // increment the offset by the message size and get valid data. + // we create a duplicate of the main message buffer so that these offsets do + // not point to garbage data. The goal is to make sure OEMCrypto is verifying + // that the offset points outside of the message -- we don't want OEMCrypto to + // look at what offset points to and return an error if the data is + // garbage. Since the memory after the message buffer is an exact copy of the + // message, we can increment the offset by the message size and get valid + // data. std::vector double_message = encrypted_response_; - double_message.insert(double_message.end(), encrypted_response_.begin(), - encrypted_response_.end()); + double_message.insert( + double_message.end(), + reinterpret_cast(&encrypted_response_data_), + reinterpret_cast(&encrypted_response_data_) + + sizeof(encrypted_response_data_)); OEMCryptoResult result; if (api_version_ < kCoreMessagesAPI) { result = OEMCrypto_LoadKeys( @@ -584,11 +668,9 @@ void LicenseRoundTrip::VerifyTestKeys() { if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); ASSERT_EQ(sizeof(block), size); - // control duration and bits stored in network byte order. For printing + // Note: we do not assume that duration is stored with each key after v16. + // control bits stored in network byte order. For printing // we change to host byte order. - ASSERT_EQ(htonl_fnc(response_data_.keys[i].control.duration), - htonl_fnc(block.duration)) - << "For key " << i; ASSERT_EQ(htonl_fnc(response_data_.keys[i].control.control_bits), htonl_fnc(block.control_bits)) << "For key " << i; @@ -758,8 +840,8 @@ void RenewalRoundTrip::FillAndVerifyCoreRequest( EXPECT_TRUE( oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage( core_message_string, &core_request_)); - EXPECT_EQ(license_messages_->core_request().api_version, - core_request_.api_version); + EXPECT_EQ(license_messages_->core_request().api_major_version, + core_request_.api_major_version); if (!is_release_) { // For a license release, we do not expect the nonce to be correct. That // is because a release might be sent without loading the license first. @@ -786,12 +868,17 @@ void RenewalRoundTrip::CreateDefaultResponse() { constexpr size_t index = 0; response_data_.keys[index].key_id_length = 0; response_data_.keys[index].key_id[0] = '\0'; - std::string kcVersion = "kc" + std::to_string(core_request_.api_version); + std::string kcVersion = + "kc" + std::to_string(core_request_.api_major_version); + if (global_features.api_version < kCoreMessagesAPI) { + // For v15 or earlier devices, we use the api of the device. + kcVersion = "kc" + std::to_string(global_features.api_version); + } memcpy(response_data_.keys[index].control.verification, kcVersion.c_str(), 4); const uint32_t duration = static_cast( license_messages_->core_response() - .timer_limits.renewal_playback_duration_seconds); + .timer_limits.initial_renewal_duration_seconds); response_data_.keys[index].control.duration = htonl(duration); response_data_.keys[index].control.nonce = htonl(nonce); response_data_.keys[index].control.control_bits = htonl(control); @@ -801,12 +888,6 @@ void RenewalRoundTrip::CreateDefaultResponse() { void RenewalRoundTrip::EncryptAndSignResponse() { // Renewal messages are not encrypted. encrypted_response_data_ = response_data_; - - // Stripe the encrypted message. - encrypted_response_.resize(message_size_); - for (size_t i = 0; i < encrypted_response_.size(); i++) { - encrypted_response_[i] = i % 0x100; - } // Either create a KeyRefreshObject for a call to RefreshKeys or a core // response for a call to LoadRenewal. if (license_messages_->api_version() < kCoreMessagesAPI) { @@ -818,7 +899,17 @@ void RenewalRoundTrip::EncryptAndSignResponse() { serialized_core_message_.resize(0); } else { ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreRenewalResponse( - core_request_, &serialized_core_message_)); + core_request_, renewal_duration_seconds_, &serialized_core_message_)); + } + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); + // Stripe the encrypted message. + encrypted_response_.resize(message_size); + for (size_t i = 0; i < encrypted_response_.size(); i++) { + encrypted_response_[i] = i % 0x100; } // Concatenate the core message and the response. ASSERT_GE(kMaxCoreMessage, serialized_core_message_.size()); diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.h b/libwvdrmengine/oemcrypto/test/oec_session_util.h index 2e8a58e7..e3fa6967 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.h +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.h @@ -133,13 +133,6 @@ uint32_t htonl_fnc(uint32_t x); // Prints error string from BoringSSL void dump_boringssl_error(); -// Given a message and field, returns an OEMCrypto_Substring with the field's -// offset into the message and its length. If |set_zero| is true, both the -// offset and length will be zero. -OEMCrypto_Substring GetSubstring(const std::string& message = "", - const std::string& field = "", - bool set_zero = false); - class Session; // The prototype of the OEMCrypto function to prepare and sign a request. typedef OEMCryptoResult (*PrepAndSignRequest_t)( @@ -159,7 +152,7 @@ class RoundTrip { core_response_(), response_data_(), encrypted_response_data_(), - message_size_(sizeof(ResponseData) + kMaxCoreMessage){}; + required_message_size_(0) {} virtual ~RoundTrip() {} // Have OEMCrypto sign a request message and then verify the signature and the @@ -190,9 +183,7 @@ class RoundTrip { } // Set the size of the buffer used the encrypted license. - void set_message_size(size_t size) { message_size_ = size; } - // The size of the encrypted message. - size_t message_size() { return message_size_; } + void set_message_size(size_t size) { required_message_size_ = size; } std::vector& response_signature() { return response_signature_; } const std::string& serialized_core_message() const { return serialized_core_message_; @@ -218,7 +209,9 @@ class RoundTrip { CoreRequest core_request_; CoreResponse core_response_; ResponseData response_data_, encrypted_response_data_; - size_t message_size_; // How much of the padded message to use. + // Message buffers will be at least this big. Tests for loading and signing + // messages will increase all buffers to this size. + size_t required_message_size_; std::vector response_signature_; std::string serialized_core_message_; std::vector encrypted_response_; @@ -257,6 +250,15 @@ class ProvisioningRoundTrip // Verify the values of the core response. virtual void FillAndVerifyCoreRequest( const std::string& core_message_string) override; + // Load the response, without the retry. Called by LoadResponse. + OEMCryptoResult LoadResponseNoRetry(Session* session, + size_t* wrapped_key_length); + // This takes a pointer in the response_data_ and remaps it to the same + // pointer within the encrypted message. This is used for backwards + // compatibliity testing, so that a v15 oemcrypto will accept range checks. + template + const T* RemapPointer(const T* response_pointer) const; + uint32_t allowed_schemes_; Encryptor encryptor_; // The message key used for Prov 3.0. @@ -317,7 +319,7 @@ class LicenseRoundTrip } // Change the hash of the core request. This should cause the response to be // rejected. - void BreakRequestHash() { core_response_.request_hash[3] ^= 42; } + void BreakRequestHash() { request_hash_[3] ^= 42; } // Set the API version for the license itself. This will be used in // CreateDefaultResponse. void set_api_version(uint32_t api_version) { api_version_ = api_version; } @@ -363,6 +365,7 @@ class LicenseRoundTrip // Whether this is a content license or an entitlement license. Used in // CreateDefaultResponse. OEMCrypto_LicenseType license_type_; + uint8_t request_hash_[ODK_SHA256_HASH_SIZE]; }; class RenewalRoundTrip @@ -377,11 +380,20 @@ class RenewalRoundTrip : RoundTrip(license_messages->session()), license_messages_(license_messages), refresh_object_(), + renewal_duration_seconds_( + license_messages->core_response() + .timer_limits.initial_renewal_duration_seconds), is_release_(false) {} void CreateDefaultResponse() override; void EncryptAndSignResponse() override; OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } OEMCryptoResult LoadResponse(Session* session) override; + uint64_t renewal_duration_seconds() const { + return renewal_duration_seconds_; + } + void set_renewal_duration_seconds(uint64_t renewal_duration_seconds) { + renewal_duration_seconds_ = renewal_duration_seconds; + } void set_is_release(bool is_release) { is_release_ = is_release; } protected: @@ -393,6 +405,7 @@ class RenewalRoundTrip const std::string& core_message_string) override; LicenseRoundTrip* license_messages_; OEMCrypto_KeyRefreshObject refresh_object_; + uint64_t renewal_duration_seconds_; bool is_release_; // If this is a license release, and not a real renewal. }; diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp index 51b717ff..9e2bc7e7 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp @@ -71,7 +71,6 @@ void SessionUtil::EnsureTestKeys() { // This makes sure that the derived keys (encryption key and two mac keys) // are installed in OEMCrypto and in the test session. void SessionUtil::InstallTestRSAKey(Session* s) { - ASSERT_TRUE(global_features.uses_certificate); if (global_features.loads_certificate) { if (wrapped_rsa_key_.size() == 0) { // If we don't have a wrapped key yet, create one. diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 27b023be..9e559c5f 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -875,7 +875,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonce) { ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// Verify that a second license may be not be loaded in a session. +// Verify that a second license may not be loaded in a session. TEST_P(OEMCryptoLicenseTest, LoadKeyNoNonceTwiceAPI16) { ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); license_messages_.set_control(0); @@ -886,8 +886,8 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyNoNonceTwiceAPI16) { ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); } -// Verify that a second license may be not be loaded in a session. -TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonceTwice) { +// Verify that a second license may not be loaded in a session. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonceTwiceAPI16) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -985,7 +985,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_enc_mac_keys) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().enc_mac_keys.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -997,7 +997,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_enc_mac_keys_iv) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().enc_mac_keys_iv.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1009,7 +1009,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_id) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().key_array[0].key_id.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1021,7 +1021,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_data) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().key_array[1].key_data.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1033,7 +1033,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_data_iv) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().key_array[1].key_data_iv.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1045,7 +1045,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_control) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().key_array[2].key_control.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1057,7 +1057,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_control_iv) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().key_array[2].key_control_iv.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1071,7 +1071,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_pst) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().pst.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); // If we have a pst, then we need a usage entry. ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); @@ -1081,7 +1081,8 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_pst) { //---------------------------------------------------------------------------// // The IV should not be identical to the data right before the encrypted mac -// keys. +// keys. This requirement was added in 15.2, so it frequently fails on +// production devices. TEST_F(OEMCryptoLicenseTestAPI15, LoadKeyWithSuspiciousIV) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); @@ -1272,7 +1273,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyUnalignedMessageAPI16) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadLicense( session_.session_id(), unaligned_message, - license_messages_.message_size(), + license_messages_.encrypted_response_buffer().size(), license_messages_.serialized_core_message().size(), license_messages_.response_signature().data(), license_messages_.response_signature().size())); @@ -1293,6 +1294,10 @@ TEST_P(OEMCryptoLicenseTest, LoadLicenseAgainFailureAPI16) { TEST_P(OEMCryptoLicenseTestRangeAPI, LoadKeys) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + // Re-set the API version. The function VerifyRequestSignature sets the api to + // be a sane value. But in this test, we want to verify an unsupported version + // is rejected. + license_messages_.set_api_version(license_api_version_); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); @@ -1626,6 +1631,7 @@ TEST_F(OEMCryptoSessionTests, CheckMinimumPatchLevel) { ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + EXPECT_EQ(global_features.api_version, license_messages.api_version()); ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); } // Reject any future patch levels. @@ -1716,22 +1722,16 @@ class OEMCryptoRefreshTest : public OEMCryptoLicenseTest { // playback right away. All times are in seconds since the license was // signed. // Soft expiry false means timers are strictly enforce. - timer_limits_.soft_expiry = false; + 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_.latest_playback_start_seconds = kDuration; + timer_limits_.rental_duration_seconds = kDuration; // Once started, playback may last two seconds without a renewal. - timer_limits_.initial_playback_duration_seconds = kDuration; - // Playback may continue for four seconds after a renewal is loaded. - timer_limits_.renewal_playback_duration_seconds = 2 * kDuration; - if (license_api_version_ < kCoreMessagesAPI) { - // For legacy licenses, only license duration is enforced. - timer_limits_.license_duration_seconds = kDuration; - } else { - // Total playback is not limited. - timer_limits_.license_duration_seconds = 0; - } + timer_limits_.initial_renewal_duration_seconds = kDuration; + // Total playback is not limited. + timer_limits_.total_playback_duration_seconds = 0; } void LoadLicense() { @@ -1902,8 +1902,8 @@ TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) { TEST_P(OEMCryptoLicenseTest, Decrypt) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + 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()); @@ -1914,7 +1914,8 @@ TEST_P(OEMCryptoLicenseTest, Decrypt) { TEST_P(OEMCryptoLicenseTest, DecryptZeroDuration) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - license_messages_.core_response().timer_limits.license_duration_seconds = 0; + 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()); @@ -2179,8 +2180,8 @@ class OEMCryptoSessionTestsDecryptTests ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + 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_; @@ -2424,7 +2425,7 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, PartialBlock) { // // 1) The maximum total sample size // 2) The maximum number of subsamples multiplied by the maximum subsample size -TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSample) { +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); @@ -2589,8 +2590,8 @@ TEST_P(OEMCryptoLicenseTest, DecryptNoAnalogToClearAPI13) { TEST_P(OEMCryptoLicenseTest, KeyDuration) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + 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()); @@ -2669,14 +2670,14 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) { // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1) { +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()); provisioning_messages.core_response().enc_private_key.offset = - provisioning_messages.message_size() + 1; + provisioning_messages.encrypted_response_buffer().size() + 1; ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); provisioning_messages.VerifyLoadFailed(); @@ -2684,14 +2685,14 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1) { // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2) { +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()); provisioning_messages.core_response().enc_private_key_iv.offset = - provisioning_messages.message_size() + 1; + provisioning_messages.encrypted_response_buffer().size() + 1; ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); provisioning_messages.VerifyLoadFailed(); @@ -2699,7 +2700,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2) { // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3_API16) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -2708,7 +2709,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3) { // 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.message_size() - 5; + provisioning_messages.encrypted_response_buffer().size() - 5; ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); provisioning_messages.VerifyLoadFailed(); @@ -2716,7 +2717,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3) { // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4_API16) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -2725,7 +2726,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4) { // 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.message_size() - 5; + provisioning_messages.encrypted_response_buffer().size() - 5; ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); provisioning_messages.VerifyLoadFailed(); @@ -2733,7 +2734,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4) { // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30_API16) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -2742,7 +2743,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30) { // 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.message_size() + 1; + provisioning_messages.encrypted_response_buffer().size() + 1; ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); provisioning_messages.VerifyLoadFailed(); @@ -2764,7 +2765,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadSignatureKeyboxTest) { } // Test that RewrapDeviceRSAKey verifies the nonce is current. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce_API16) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -3199,7 +3200,7 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, DisallowForbiddenPaddingAPI09) { // The alternate padding is only required for cast receivers, but if a device // does load an alternate certificate, it should NOT use it for generating // a license request signature. -TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1) { +TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1_API16) { // Try to load an RSA key with alternative padding schemes. This signing // scheme is used by cast receivers. LoadWithAllowedSchemes(kSign_PKCS1_Block1, false); @@ -4587,8 +4588,8 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerifyLargeBuffer) { // Test Generic_Encrypt when the key duration has expired. TEST_P(OEMCryptoGenericCryptoTest, KeyDurationEncrypt) { - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); vector expected_encrypted; @@ -4622,8 +4623,8 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationEncrypt) { // Test Generic_Decrypt when the key duration has expired. TEST_P(OEMCryptoGenericCryptoTest, KeyDurationDecrypt) { - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); @@ -4656,8 +4657,8 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationDecrypt) { // Test Generic_Sign when the key duration has expired. TEST_P(OEMCryptoGenericCryptoTest, KeyDurationSign) { - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); @@ -4692,8 +4693,8 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationSign) { // Test Generic_Verify when the key duration has expired. TEST_P(OEMCryptoGenericCryptoTest, KeyDurationVerify) { - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); @@ -4732,8 +4733,8 @@ class OEMCryptoGenericCryptoKeyIdLengthTest OEMCryptoGenericCryptoTest::SetUp(); license_messages_.set_num_keys(5); license_messages_.set_control(wvoec::kControlAllowDecrypt); - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); SetUniformKeyIdLength(16); // Start with all key ids being 16 bytes. // But, we are testing that the key ids do not have to have the same length. @@ -5127,7 +5128,7 @@ TEST_P(OEMCryptoUsageTableTest, OnlineMissingEntry) { // Sessions should have at most one entry at a time. This tests different // orderings of CreateNewUsageEntry and LoadUsageEntry calls. -TEST_P(OEMCryptoUsageTableTest, CreateAndLoadMultipleEntries) { +TEST_P(OEMCryptoUsageTableTest, CreateAndLoadMultipleEntriesAPI16) { // Entry Count: we start each test with an empty header. uint32_t usage_entry_number; LicenseWithUsageEntry entry;