From 44ba42f5cc1f45746c807e7c086a6e82aee62585 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Tue, 25 May 2021 01:33:04 +0000 Subject: [PATCH 01/12] Use local provisioning server Merge from Widevine repo of http://go/wvgerrit/133703 and http://ag/14707867 In order to use a local provisioning server, we need to use a different test keybox system id that is in the dev device database instead of the production database. We also need to use a local license server that uses the dev license server. Bug: 187646550 Test: GtsMediaTestCases Change-Id: Ice89143dd26de22757375a770c6bac716fcbc057 Add Keybox OTA Provisioning functions to OEMCrypto header Merge from Widevine repo of http://go/wvgerrit/133704 and http://go/ag/14707868 Bug: 188228998 Change-Id: Iff54bc2870e87bf7239e179e1d02fbcc8df6198f Stub build changes to support OTA Keybox Merge from Widevine repo of http://go/wvgerrit/133725 and http://go/ag/14781459 This CL adds a new unit test file for testing OTA keybox reprovisioning functionality. This new test is built when running the dynamic adapter in the linux build, and in the Android build. Bug: 187646550 Change-Id: I625513840188f95e74831ef2ea399e827e837439 Add OTA Keybox functions to dynamic adapter Merge from Widevine repo of http://go/wvgerrit/125843 and http://go/ag/14781460 Bug: 187646550 Change-Id: Ief78ed10599c091690e0d7dc488ea71674c763b5 Refactor dynamic adapter keybox verification Merge from Widevine repo of http://go/wvgerrit/133727 http://go/ag/14812524 The keybox validation needs to be done separately from initializing the library so that we can support Keybox OTA Reprovisioning. If L1 loads, but the keybox is missing, the initialization should succeed. When the keybox is validated, the adapter should try to look for a keybox on the filesystem. if none is found, it should either return NEEDS PROVISIONING or an error. Bug: 187646550 Change-Id: I34a8c365a5a5ca35c379bea827c85c749964744c Update crypto session to use new OTA keybox functionality Merge from Widevine repo of http://go/wvgerrit/133728 and http://go/ag/14812525 This CL stubs out two new CryptoSession functions that call the new OEMCrypto functions for OTA Keybox Provisioning. It builds! Yay! It also adds a boolean needs_keybox_provisioning that is set to true when OEMCrypto reports that it needs a keybox. This should only happen if there is no keybox installed and oemcrypto supports provisioning. Bug: 187646550 Merged-In: Ide9533943125aa13b8899b652b118a0b410c882c Change-Id: Ide9533943125aa13b8899b652b118a0b410c882c --- .../cdm/core/include/crypto_session.h | 18 ++ .../cdm/core/include/oemcrypto_adapter.h | 7 +- .../cdm/core/src/crypto_session.cpp | 43 ++- .../core/src/oemcrypto_adapter_dynamic.cpp | 262 ++++++++++++------ .../cdm/core/src/oemcrypto_ota_stubs.cpp | 17 ++ .../cdm/core/test/config_test_env.cpp | 30 ++ .../cdm/core/test/config_test_env.h | 2 + .../cdm/core/test/keybox_ota_test.cpp | 11 + libwvdrmengine/cdm/core/test/test_base.cpp | 23 +- libwvdrmengine/cdm/core/test/test_base.h | 3 + .../cdm/core/test/test_printers.cpp | 3 + libwvdrmengine/cdm/test/Android.mk | 4 + .../oemcrypto/include/OEMCryptoCENC.h | 109 +++++++- .../odk/include/OEMCryptoCENCCommon.h | 1 + libwvdrmengine/oemcrypto/test/common.mk | 1 + libwvdrmengine/oemcrypto/test/oec_test_data.h | 38 +++ .../oemcrypto/test/ota_keybox_test.cpp | 53 ++++ 17 files changed, 527 insertions(+), 98 deletions(-) create mode 100644 libwvdrmengine/cdm/core/src/oemcrypto_ota_stubs.cpp create mode 100644 libwvdrmengine/cdm/core/test/keybox_ota_test.cpp create mode 100644 libwvdrmengine/oemcrypto/test/ota_keybox_test.cpp diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index 4d2e9371..e516a1b3 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -287,6 +287,19 @@ class CryptoSession { virtual CdmResponseType GetProvisioningMethod( SecurityLevel requested_security_level, CdmClientTokenType* token_type); + // OTA Provisioning + // TODO(sigquit): include rest of http://go/wvgerrit/126004 + + // Generates an OTA provisioning request. + // This should only be called by an instance of OtaKeyboxProvisioner. + virtual CdmResponseType PrepareOtaProvisioningRequest(bool use_test_key, + std::string* request); + + // Loads an OTA provisioning response. + // This should only be called by an instance of OtaKeyboxProvisioner. + virtual CdmResponseType LoadOtaProvisioning(bool use_test_key, + const std::string& response); + protected: // Creates an instance of CryptoSession with the given |crypto_metrics|. // |crypto_metrics| is owned by the caller, must NOT be null, and must @@ -294,6 +307,10 @@ class CryptoSession { explicit CryptoSession(metrics::CryptoMetrics* crypto_metrics); int session_count() const { return session_count_; } + bool initialized() const { return initialized_; } + void OverrideInitializedForTesting(bool initialized) { + initialized_ = initialized; + } private: friend class CryptoSessionForTest; @@ -434,6 +451,7 @@ class CryptoSession { static bool initialized_; static int session_count_; static int termination_counter_; + static bool needs_keybox_provisioning_; enum CachedBooleanProperty { // Property has not yet been checked/cached. diff --git a/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h b/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h index e52c519d..8bbee0f8 100644 --- a/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h +++ b/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h @@ -9,6 +9,12 @@ #include "wv_cdm_types.h" namespace wvcdm { +// Initialize OEMCrypto, then check the keybox and see if it is valid. If not, +// and OTA provisioning is supported, set needs_keybox_provisioning to true. +// If the keybox is not valid and OTA provisioning is not supported, set +// needs_keybox_provisioning to false and use L3 only. +OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( + bool* needs_keybox_provisioning); // This attempts to open a session at the desired security level. // If one level is not available, the other will be used instead. @@ -17,7 +23,6 @@ OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session, OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t* keybox, size_t keyBoxLength, SecurityLevel level); -OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(SecurityLevel level); OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength, SecurityLevel level); OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength, diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 051554f1..7967b49b 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -85,7 +85,7 @@ constexpr size_t kMaxSubsampleRegionSizes[] = { static_assert(ArraySize(kMaxSubsampleRegionSizes) == RESOURCE_RATING_TIER_MAX - RESOURCE_RATING_TIER_MIN + 1, "The kMaxSubsampleRegionSizes table needs to be updated to " - "reflect the supported range of resource rating tiers."); + "reflect the supported range of resource rating tiers"); constexpr size_t kDefaultMaxSubsampleRegionSize = kMaxSubsampleRegionSizes[0]; @@ -171,6 +171,7 @@ size_t GenericEncryptionBlockSize(CdmEncryptionAlgorithm algorithm) { shared_mutex CryptoSession::static_field_mutex_; shared_mutex CryptoSession::oem_crypto_mutex_; bool CryptoSession::initialized_ = false; +bool CryptoSession::needs_keybox_provisioning_ = false; int CryptoSession::session_count_ = 0; int CryptoSession::termination_counter_ = 0; std::unique_ptr CryptoSession::usage_table_header_l1_; @@ -321,8 +322,9 @@ void CryptoSession::Init() { sandbox_id.length()); metrics_->oemcrypto_set_sandbox_.Record(sandbox_id); } - M_TIME(sts = OEMCrypto_Initialize(), metrics_, oemcrypto_initialize_, - sts); + M_TIME(sts = OEMCrypto_InitializeAndCheckKeybox( + &needs_keybox_provisioning_), + metrics_, oemcrypto_initialize_, sts); }); if (OEMCrypto_SUCCESS != sts) { LOGE("OEMCrypto_Initialize failed: status = %d", static_cast(sts)); @@ -357,6 +359,10 @@ void CryptoSession::Init() { : kStringNotAvailable; LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(), api_minor_version.c_str()); + if (needs_keybox_provisioning_) { + LOGE("OEMCrypto needs provisioning"); + // TODO(fredgc,sigquit,rfrias): handle provisioning. + } } } @@ -1440,7 +1446,7 @@ size_t CryptoSession::GetMaxSubsampleRegionSize() { // If something went wrong, use the default. if (max_subsample_region_size_ == 0) { - LOGW("Unable to get maximum subsample region size. Defaulting to %zu.", + LOGW("Unable to get maximum subsample region size. Defaulting to %zu", kDefaultMaxSubsampleRegionSize); max_subsample_region_size_ = kDefaultMaxSubsampleRegionSize; } @@ -2994,7 +3000,7 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( static_assert(sizeof(fake_sample.iv) == kAes128BlockSize, "The size of an AES-128 block and the size of an AES-128 " - "IV have become misaligned."); + "IV have become misaligned"); memcpy(fake_sample.iv, block_end - kAes128BlockSize, kAes128BlockSize); } } @@ -3009,6 +3015,33 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( return sts; } +// TODO(sigquit): include rest of http://go/wvgerrit/126004 + +CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( + bool use_test_key, std::string* request) { + RETURN_IF_NULL(request, PARAMETER_NULL); + size_t buffer_length = 0; + OEMCryptoResult status = + OEMCrypto_GenerateOTARequest(nullptr, &buffer_length, use_test_key); + if (status != OEMCrypto_ERROR_SHORT_BUFFER) + return MapOEMCryptoResult(status, UNKNOWN_ERROR, + "PrepareOtaProvisioningRequest"); + std::string temp_buffer(buffer_length, '\0'); + uint8_t* buf = reinterpret_cast(&temp_buffer[0]); + status = OEMCrypto_GenerateOTARequest(buf, &buffer_length, use_test_key); + if (OEMCrypto_SUCCESS == status) request->assign(temp_buffer); + return MapOEMCryptoResult(status, UNKNOWN_ERROR, + "PrepareOtaProvisioningRequest"); +} + +CdmResponseType CryptoSession::LoadOtaProvisioning( + bool use_test_key, const std::string& response) { + OEMCryptoResult status = OEMCrypto_ProcessOTAKeybox( + reinterpret_cast(response.data()), response.size(), + use_test_key); + return MapOEMCryptoResult(status, UNKNOWN_ERROR, "LoadOtaProvisioning"); +} + template auto CryptoSession::WithStaticFieldWriteLock(const char* tag, Func body) -> decltype(body()) { diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp index 51e2c119..759c280d 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp @@ -318,7 +318,12 @@ typedef OEMCryptoResult (*L1_LoadProvisioning_t)( size_t signature_length, uint8_t* wrapped_private_key, size_t* wrapped_private_key_length); typedef uint32_t (*L1_MinorAPIVersion_t)(); - +typedef OEMCryptoResult (*L1_GenerateOTARequest_t)(uint8_t* buffer, + size_t* buffer_length, + bool use_test_key); +typedef OEMCryptoResult (*L1_ProcessOTAKeybox_t)(const uint8_t* buffer, + size_t buffer_length, + bool use_test_key); struct FunctionPointers { wvcdm::CdmSecurityLevel security_level; uint32_t version; @@ -415,6 +420,8 @@ struct FunctionPointers { L1_SelectKey_V13_t SelectKey_V13; L1_LoadTestKeybox_V13_t LoadTestKeybox_V13; L1_RefreshKeys_V14_t RefreshKeys_V14; + L1_GenerateOTARequest_t GenerateOTARequest; + L1_ProcessOTAKeybox_t ProcessOTAKeybox; }; size_t GetOffset(const std::string& message, const std::string& field) { @@ -643,7 +650,7 @@ struct LevelSession { if ((level1_.version >= min) && (level1_.version <= max)) { \ level1_.Name = (L1_##Name##_t)dlsym(level1_library_, QUOTE(Function)); \ if (!level1_.Name) { \ - LOGW("Could not load L1 %s. Falling Back to L3.", QUOTE(Function)); \ + LOGW("Could not load L1 %s. Falling back to L3.", QUOTE(Function)); \ if (level1_.Terminate) level1_.Terminate(); \ return false; \ } \ @@ -778,7 +785,7 @@ class Adapter { } OEMCryptoResult st = level1_.Initialize(); if (st != OEMCrypto_SUCCESS) { - LOGW("Could not initialize L1. Falling Back to L3."); + LOGW("Could not initialize L1. Falling back to L3."); metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode( wvcdm::metrics:: OEMCrypto_INITIALIZED_USING_L3_COULD_NOT_INITIALIZE_L1); @@ -790,7 +797,7 @@ class Adapter { metrics->SetL1MinApiVersion(kMinimumVersion); if (level1_.version < kMinimumVersion) { - LOGW("liboemcrypto.so is version %d, not %d. Falling Back to L3.", + LOGW("liboemcrypto.so is version %d, not %d. Falling back to L3.", level1_.version, kMinimumVersion); metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode( wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_WRONG_L1_VERSION); @@ -887,6 +894,8 @@ class Adapter { LOOKUP_ALL(16, MaximumUsageTableHeaderSize, OEMCrypto_MaximumUsageTableHeaderSize); LOOKUP_ALL(16, LoadProvisioning, OEMCrypto_LoadProvisioning); LOOKUP_ALL(16, MinorAPIVersion, OEMCrypto_MinorAPIVersion); + LOOKUP_ALL(16, GenerateOTARequest, OEMCrypto_GenerateOTARequest); + LOOKUP_ALL(16, ProcessOTAKeybox, OEMCrypto_ProcessOTAKeybox); // clang-format on // There was a mistake in version 16.3 of the header that did not rename @@ -915,81 +924,6 @@ class Adapter { } } - // TODO(119830252): make the code below available to a static adapter. - // Check if the keybox or oem certificate is valid, if so, we are finished - // with initialization. - OEMCryptoResult root_valid = level1_.IsKeyboxOrOEMCertValid(); - OEMCrypto_ProvisioningMethod provisioning_method = - level1_.GetProvisioningMethod(); - if (root_valid == OEMCrypto_SUCCESS) { - // The keybox or certificate is valid -- that means initialization is done - // and we only have save some metrics and return. - metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode( - (provisioning_method == OEMCrypto_Keybox) - ? wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L1_WITH_KEYBOX - : wvcdm::metrics:: - OEMCrypto_INITIALIZED_USING_L1_WITH_PROVISIONING_3_0); - return true; - } - // At this point, the keybox or cert is not valid. We look on the file - // system for one. If it is there we try to install it. - wvcdm::FileSystem file_system; - std::string filename; - if (!wvcdm::Properties::GetFactoryKeyboxPath(&filename)) { - // No keybox or cert file found. Give up. - LOGW("Bad Level 1 Root of Trust. Falling Back to L3."); - level1_.Terminate(); - metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode( - wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_BAD_KEYBOX); - return false; - } - ssize_t size = file_system.FileSize(filename); - if (size <= 0) { - // A keybox or cert file was found, but it has size 0. Give up. - LOGW("Could not find %s. Falling Back to L3.", filename.c_str()); - level1_.Terminate(); - metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode( - wvcdm::metrics:: - OEMCrypto_INITIALIZED_USING_L3_COULD_NOT_OPEN_FACTORY_KEYBOX); - return false; - } - auto file = file_system.Open(filename, file_system.kReadOnly); - if (!file) { - // A keybox or cert file was found, but can't open it. Give up. - LOGW("Could not open %s. Falling Back to L3.", filename.c_str()); - level1_.Terminate(); - metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode( - wvcdm::metrics:: - OEMCrypto_INITIALIZED_USING_L3_COULD_NOT_OPEN_FACTORY_KEYBOX); - return false; - } - std::vector root_key(size); - ssize_t size_read = file->Read(reinterpret_cast(&root_key[0]), size); - if (level1_.InstallKeyboxOrOEMCert(&root_key[0], size_read) != - OEMCrypto_SUCCESS) { - // A keybox or cert file was read, but I could not install it. Give up. - LOGE("Could NOT install root key from %s. Falling Back to L3.", - filename.c_str()); - level1_.Terminate(); - metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode( - wvcdm::metrics:: - OEMCrypto_INITIALIZED_USING_L3_COULD_NOT_INSTALL_KEYBOX); - return false; - } - if (level1_.IsKeyboxOrOEMCertValid() != OEMCrypto_SUCCESS) { - // A keybox or cert file was read and installed, but it is still not - // valid. Give up. - LOGE("Installed bad key from %s. Falling Back to L3.", filename.c_str()); - level1_.Terminate(); - metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode( - wvcdm::metrics:: - OEMCrypto_INITIALIZED_USING_L3_COULD_NOT_INSTALL_KEYBOX); - return false; - } - // A valid keybox or cert file was read and installed. Yay! return success. - LOGI("Installed root key from %s", filename.c_str()); - metrics->OemCryptoDynamicAdapterMetrics::SetInitializationMode( - wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L1_INSTALLED_KEYBOX); return true; } @@ -1133,6 +1067,77 @@ class Adapter { return result; } + // Check the L1 keybox or cert. If it is valid, return success. If not, try to + // install one. If one is not available, but OTA provisioning is supported, + // return OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING. If none of these work, + // then return the status of the L3 keybox or cert. + OEMCryptoResult ValidateOrInstallKeyboxOrCert() { + if (!level1_valid_) { + // TODO(b/189989043): add metrics. + // If level 1 not initialized, then return level 3's answer. + return level3_.IsKeyboxOrOEMCertValid ? level3_.IsKeyboxOrOEMCertValid() + : OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (!level1_.IsKeyboxOrOEMCertValid) { + // TODO(b/189989043): add metrics. + LOGE("L1 invalid function pointers. Falling back to L3"); + if (level1_.Terminate) level1_.Terminate(); + level1_valid_ = false; + return level3_.IsKeyboxOrOEMCertValid ? level3_.IsKeyboxOrOEMCertValid() + : OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + // Check if the keybox or oem certificate is valid, if so, we are finished + // with initialization. Record some metrics and return success. + const OEMCryptoResult rot_valid = level1_.IsKeyboxOrOEMCertValid(); + wvcdm::metrics::OemCryptoDynamicAdapterMetrics& metrics = + wvcdm::metrics::GetDynamicAdapterMetricsInstance(); + // Figure out provisioning method. Defaults to keybox. + const OEMCrypto_ProvisioningMethod provisioning_method = + level1_.GetProvisioningMethod ? level1_.GetProvisioningMethod() + : OEMCrypto_Keybox; + if (rot_valid == OEMCrypto_SUCCESS) { + // The keybox or certificate is valid -- that means initialization is done + // and we only have save some metrics and return. + metrics.OemCryptoDynamicAdapterMetrics::SetInitializationMode( + (provisioning_method == OEMCrypto_Keybox) + ? wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L1_WITH_KEYBOX + : wvcdm::metrics:: + OEMCrypto_INITIALIZED_USING_L1_WITH_PROVISIONING_3_0); + return OEMCrypto_SUCCESS; + } + // At this point, the L1 keybox or cert is not valid. If are able to + // install one, then we look on the file system for one. If it is there we + // try to install it. + OEMCryptoResult file_attempt = TryToInstallKeybox(); + if (file_attempt == OEMCrypto_SUCCESS) { + // If loading the keybox succeeded, we are done. good job. + metrics.OemCryptoDynamicAdapterMetrics::SetInitializationMode( + wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L1_INSTALLED_KEYBOX); + return OEMCrypto_SUCCESS; + } + if (rot_valid == OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING) { + // TODO(b/189989043): add metrics. + return OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING; + } + if (file_attempt == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + // No keybox to load, and none installed, so give up. + LOGW("Bad Level 1 Root of Trust. Falling back to L3"); + metrics.OemCryptoDynamicAdapterMetrics::SetInitializationMode( + wvcdm::metrics::OEMCrypto_INITIALIZED_USING_L3_BAD_KEYBOX); + } else { + // There was a keybox to load, but there was an error loading it. So give + // up. + LOGW("Error installing Level 1 Root of Trust. Falling back to L3"); + metrics.OemCryptoDynamicAdapterMetrics::SetInitializationMode( + wvcdm::metrics:: + OEMCrypto_INITIALIZED_USING_L3_COULD_NOT_INSTALL_KEYBOX); + } + if (level1_.Terminate) level1_.Terminate(); + level1_valid_ = false; + return level3_.IsKeyboxOrOEMCertValid ? level3_.IsKeyboxOrOEMCertValid() + : OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + private: bool level1_valid_; void* level1_library_; @@ -1149,6 +1154,51 @@ class Adapter { if (!var) return false; return !strcmp(var, "yes"); } + + // Try to install a keybox from the file system. + OEMCryptoResult TryToInstallKeybox() { + if (!level1_.InstallKeyboxOrOEMCert) return OEMCrypto_ERROR_NOT_IMPLEMENTED; + wvcdm::FileSystem file_system; + std::string filename; + if (!wvcdm::Properties::GetFactoryKeyboxPath(&filename)) { + // No keybox or cert file found. Give up. + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + const ssize_t size = file_system.FileSize(filename); + if (size <= 0) { + // The keybox file does not exit or has size 0. + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + // After this, any error will be logged differently to metrics + // because we found a keybox on the filesystem, so we expect it to work. + auto file = file_system.Open(filename, file_system.kReadOnly); + if (!file) { + // A keybox or cert file was found, but can't open it. Give up, but + // log it as a different error because the file did exist. + LOGW("Could not open %s", filename.c_str()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + std::vector root_key(size); + ssize_t size_read = + file->Read(reinterpret_cast(root_key.data()), size); + if (level1_.InstallKeyboxOrOEMCert(root_key.data(), size_read) != + OEMCrypto_SUCCESS) { + // A keybox or cert file was read, but I could not install it. Give up. + LOGE("Could NOT install root key from %s", filename.c_str()); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + if (!level1_.IsKeyboxOrOEMCertValid) return OEMCrypto_ERROR_NOT_IMPLEMENTED; + OEMCryptoResult rot_valid = level1_.IsKeyboxOrOEMCertValid(); + if (rot_valid == OEMCrypto_SUCCESS) { + // A valid keybox or cert file was read and installed. Yay! return + // success. + LOGI("Installed root key from %s", filename.c_str()); + } else { + LOGW("Installed root key from %s, but invalid(%d)", filename.c_str(), + rot_valid); + } + return rot_valid; + } }; static std::unique_ptr gAdapter; @@ -1156,6 +1206,23 @@ static std::unique_ptr gAdapter; namespace wvcdm { +OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( + bool* needs_keybox_provisioning) { + if (!needs_keybox_provisioning) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (!gAdapter) { + gAdapter.reset(new Adapter()); + } + const OEMCryptoResult status = gAdapter->Initialize(); + if (status != OEMCrypto_SUCCESS) return status; + const OEMCryptoResult keybox_status = + gAdapter->ValidateOrInstallKeyboxOrCert(); + if (keybox_status == OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING) { + *needs_keybox_provisioning = true; + return OEMCrypto_SUCCESS; + } + return keybox_status; +} + OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session, SecurityLevel level) { if (!gAdapter) return OEMCrypto_ERROR_OPEN_SESSION_FAILED; @@ -1183,15 +1250,6 @@ OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod( return fcn->GetProvisioningMethod(); } -OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(SecurityLevel level) { - if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; - const FunctionPointers* fcn = gAdapter->GetFunctionPointers(level); - if (!fcn) return OEMCrypto_ERROR_INVALID_SESSION; - if (fcn->IsKeyboxOrOEMCertValid == nullptr) - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return fcn->IsKeyboxOrOEMCertValid(); -} - OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength, SecurityLevel level) { if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; @@ -2155,7 +2213,12 @@ extern "C" OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, } extern "C" OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid() { - return OEMCrypto_IsKeyboxOrOEMCertValid(kLevelDefault); + if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + const FunctionPointers* fcn = gAdapter->GetFunctionPointers(kLevelDefault); + if (!fcn) return OEMCrypto_ERROR_INVALID_SESSION; + if (fcn->IsKeyboxOrOEMCertValid == nullptr) + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + return fcn->IsKeyboxOrOEMCertValid(); } extern "C" OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod() { @@ -2733,3 +2796,24 @@ extern "C" OEMCryptoResult OEMCrypto_FreeSecureBuffer( } return pair.fcn->FreeSecureBuffer(pair.session, output_descriptor, secure_fd); } + +extern "C" OEMCryptoResult OEMCrypto_GenerateOTARequest(uint8_t* buffer, + size_t* buffer_length, + bool use_test_key) { + if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + const FunctionPointers* fcn = gAdapter->GetFunctionPointers(kLevelDefault); + if (!fcn) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (fcn->GenerateOTARequest == nullptr) + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + return fcn->GenerateOTARequest(buffer, buffer_length, use_test_key); +} + +extern "C" OEMCryptoResult OEMCrypto_ProcessOTAKeybox(const uint8_t* buffer, + size_t buffer_length, + bool use_test_key) { + if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + const FunctionPointers* fcn = gAdapter->GetFunctionPointers(kLevelDefault); + if (!fcn) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (fcn->ProcessOTAKeybox == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; + return fcn->ProcessOTAKeybox(buffer, buffer_length, use_test_key); +} diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_ota_stubs.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_ota_stubs.cpp new file mode 100644 index 00000000..74bfb3c1 --- /dev/null +++ b/libwvdrmengine/cdm/core/src/oemcrypto_ota_stubs.cpp @@ -0,0 +1,17 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "OEMCryptoCENC.h" + +extern "C" OEMCryptoResult OEMCrypto_GenerateOTARequest(uint8_t* buffer, + size_t* buffer_length, + bool use_test_key) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +extern "C" OEMCryptoResult OEMCrypto_ProcessOTAKeybox(const uint8_t* buffer, + size_t buffer_length, + bool use_test_key) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} diff --git a/libwvdrmengine/cdm/core/test/config_test_env.cpp b/libwvdrmengine/cdm/core/test/config_test_env.cpp index 10dc6b6c..40b2cf47 100644 --- a/libwvdrmengine/cdm/core/test/config_test_env.cpp +++ b/libwvdrmengine/cdm/core/test/config_test_env.cpp @@ -82,6 +82,32 @@ const std::string kCpStagingProvisioningServiceCertificate = "8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb" "1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28"; +// Service certificate for qa.widevine.com +const std::string kCpQAProvisioningServiceCertificate = + "0abc02080312100ec8164669cc2fdfc253b3b5e763276e18abd8cdcf05228e02" + "3082010a0282010100b24d497c0cc6ab5072f97623daa49b8d5564360654d8e5" + "8df8db7a23158f1afdd04724cbadbe87001532d9d6dec3b06973666da7759ec3" + "bf3083e2d9b85e7a47c340db796b085493a460eef31d56e3f15d857713c55cdb" + "164fe09e2a06be7fb979ad55e33a59ade3712aed2445b89fc145556a9e0093fa" + "36fc3ff4d05291a9633d20c80a13cd0d924ed9078395714c30b49019f4d6f5ba" + "093ad958aee9a164ba73ec298f905662de5859d3e6fae41c063f262d29dae75e" + "8654ac9d68f3e3fccc809573d0f90704a77f9bce391d0a5f265f438119e4fb0b" + "ec27706f5c7fc888f665730b691a0431e30cb3b57dfd078838c44550c3b79b35" + "0552a92a760f90c6cf02030100013a0f71612e7769646576696e652e636f6d12" + "800331ca1662fdc97e02debdca5b6de35dce5da87b5f61b15745ccf83e66197c" + "e31bc6379ae4f6a5e4fd8a0e76f979701c5a715c06d70908563626d0dd3986b5" + "e623a7b6336789c67f0fc68f9ec68e045f85d9a06942f4af0fe47d801cf035af" + "27924f1c4cd395d15ce2f92f48044254fdefe37320471d7009160f5293183ca4" + "a09bca71f76f1457a80eebcf12706bd79256f1b02e67dc002fc81e18c00d880f" + "04b0187e6ef59ae75eaaf6b16672a887b9657f1796607d1585d1998283af1650" + "9bd9a170c262056aad69e222a4c3180d104a76d76da29082e4f2e5297d1ec44d" + "ed98c999981688089d8ff2d62f0f13b96ce5e89a4c215d60f025fa29811fcdc1" + "848fe0581f612f45733da4b4c8803ae8088dcb3b811ea9c691daccfbe9cbf603" + "13e8f85eb68f2f1d8cdf9e4fc91a46157a90fffafbd9d408b319307ea4d3d4a9" + "d2f177355ad361f5284057dec1b186beb85dcbda64bf00a164cecc66c1878961" + "7748618d069c39f365e8347acdae777cc4e3c3c3c3fe9698c4f5ee1285b0e6a9" + "675e"; + // ----------------------------------------------------------------------------- // Below are several choices for licenseing servers: production, UAT, staging // and staging and maybe Google Play. We haven't tested with Google Play in a @@ -354,4 +380,8 @@ const std::string& ConfigTestEnv::GetLicenseServerUrl( } } +std::string ConfigTestEnv::QAProvisioningServiceCertificate() { + return a2bs_hex(kCpQAProvisioningServiceCertificate); +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/config_test_env.h b/libwvdrmengine/cdm/core/test/config_test_env.h index 5e40446b..50900bf3 100644 --- a/libwvdrmengine/cdm/core/test/config_test_env.h +++ b/libwvdrmengine/cdm/core/test/config_test_env.h @@ -116,6 +116,8 @@ class ConfigTestEnv { const std::string& provisioning_service_certificate) { provisioning_service_certificate_.assign(provisioning_service_certificate); } + // The QA service certificate, used for a local provisioning server. + static std::string QAProvisioningServiceCertificate(); private: void Init(ServerConfigurationId server_id); diff --git a/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp b/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp new file mode 100644 index 00000000..16dd0836 --- /dev/null +++ b/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp @@ -0,0 +1,11 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include + +#include "crypto_session.h" + +namespace wvcdm { +TEST(OTAKeyboxTest, TestThatTheBuildFilesWork) { ASSERT_TRUE(true); } +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/test_base.cpp b/libwvdrmengine/cdm/core/test/test_base.cpp index a256c42e..87c14bd3 100644 --- a/libwvdrmengine/cdm/core/test/test_base.cpp +++ b/libwvdrmengine/cdm/core/test/test_base.cpp @@ -112,6 +112,11 @@ void show_menu(const char* prog_name, const std::string& extra_help_text) { << " in the url" << std::endl << std::endl; + std::cout << " --qa_provisioning" << std::endl; + std::cout << " use the QA provisioning cert and QA test keybox" + << std::endl + << std::endl; + std::cout << " --fake_sleep" << std::endl; std::cout << " Use a fake clock to sleep for duration tests. This cannot" << " be used with a real OEMCrypto." << std::endl @@ -162,6 +167,7 @@ bool ExtractSignedMessage(const std::string& response, } // namespace ConfigTestEnv WvCdmTestBase::default_config_(kContentProtectionUatServer); +bool WvCdmTestBase::use_qa_test_keybox_ = false; void WvCdmTestBase::StripeBuffer(std::vector* buffer, size_t size, uint8_t init) { @@ -211,6 +217,13 @@ TestCryptoSession::TestCryptoSession(metrics::CryptoMetrics* crypto_metrics) // The first CryptoSession should have initialized OEMCrypto. This is right // after that, so should tell oemcrypto to use a test keybox. if (session_count() == 1) { + if (!initialized()) { + // If not initialized, try again and see if we are just missing a keybox. + // Since we plan to install a test keybox, we can ignore keybox errors. + const OEMCryptoResult status = ::OEMCrypto_Initialize(); + if (status != OEMCrypto_SUCCESS) return; + OverrideInitializedForTesting(true); + } WvCdmTestBase::InstallTestRootOfTrust(); } } @@ -262,12 +275,14 @@ void WvCdmTestBase::SetUp() { } void WvCdmTestBase::InstallTestRootOfTrust() { + const wvoec::WidevineKeybox& test_keybox = + use_qa_test_keybox_ ? wvoec::kQATestKeybox : wvoec::kTestKeybox; switch (wvoec::global_features.derive_key_method) { case wvoec::DeviceFeatures::LOAD_TEST_KEYBOX: ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestKeybox( - reinterpret_cast(&wvoec::kTestKeybox), - sizeof(wvoec::kTestKeybox))); + reinterpret_cast(&test_keybox), + sizeof(test_keybox))); break; case wvoec::DeviceFeatures::LOAD_TEST_RSA_KEY: // Rare case: used by devices with baked in DRM cert. @@ -451,6 +466,10 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[], is_cast_receiver = true; } else if (arg == "--fake_sleep") { wvcdm::TestSleep::set_real_sleep(false); + } else if (arg == "--qa_provisioning") { + use_qa_test_keybox_ = true; + default_config_.set_provisioning_service_certificate( + default_config_.QAProvisioningServiceCertificate()); } else if (arg.find("--gtest") == 0) { // gtest arguments will be passed to gtest by the main program. continue; diff --git a/libwvdrmengine/cdm/core/test/test_base.h b/libwvdrmengine/cdm/core/test/test_base.h index 4293bd97..58cea6c9 100644 --- a/libwvdrmengine/cdm/core/test/test_base.h +++ b/libwvdrmengine/cdm/core/test/test_base.h @@ -67,6 +67,9 @@ class WvCdmTestBase : public ::testing::Test { // arguments before any tests are created. static ConfigTestEnv default_config_; + // If the tests should use the QA test keybox. + static bool use_qa_test_keybox_; + // Configuration for an individual test. This is initialized to be the // default configuration, but can be modified by the test itself. ConfigTestEnv config_; diff --git a/libwvdrmengine/cdm/core/test/test_printers.cpp b/libwvdrmengine/cdm/core/test/test_printers.cpp index 5364eb2c..d57de31e 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.cpp +++ b/libwvdrmengine/cdm/core/test/test_printers.cpp @@ -1186,6 +1186,9 @@ void PrintTo(const enum OEMCryptoResult& value, ::std::ostream* os) { case OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION: *os << "OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION"; break; + case OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING: + *os << "ERROR_NEEDS_KEYBOX_PROVISIONING"; + break; // ODK Values. case ODK_ERROR_CORE_MESSAGE: *os << "CORE_MESSAGE"; diff --git a/libwvdrmengine/cdm/test/Android.mk b/libwvdrmengine/cdm/test/Android.mk index d37ee0f6..2f12234b 100644 --- a/libwvdrmengine/cdm/test/Android.mk +++ b/libwvdrmengine/cdm/test/Android.mk @@ -97,6 +97,10 @@ test_name := initialization_data_unittest test_src_dir := ../core/test include $(LOCAL_PATH)/unit-test.mk +test_name := keybox_ota_test +test_src_dir := ../core/test +include $(LOCAL_PATH)/unit-test.mk + test_name := license_keys_unittest test_src_dir := ../core/test include $(LOCAL_PATH)/unit-test.mk diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index 4b8dc03b..6efc97ac 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -624,6 +624,9 @@ typedef enum OEMCrypto_ProvisioningMethod { #define OEMCrypto_MinorAPIVersion _oecc108 #define OEMCrypto_AllocateSecureBuffer _oecc109 #define OEMCrypto_FreeSecureBuffer _oecc110 +// Reserved 111-112. +#define OEMCrypto_GenerateOTARequest _oecc113 +#define OEMCrypto_ProcessOTAKeybox _oecc114 // clang-format on /// @addtogroup initcontrol @@ -2963,7 +2966,24 @@ OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod(void); * @retval OEMCrypto_ERROR_INVALID_RSA_KEY * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED * - * @threading + * On devices that support OEMCrypto_GenerateOTARequest and + * OEMCrypto_ProcessOTAKeybox, this function may return + * OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING when a valid keybox is not + * present. + * + * Parameters: + * none + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_BAD_MAGIC + * OEMCrypto_ERROR_BAD_CRC + * OEMCrypto_ERROR_KEYBOX_INVALID + * OEMCrypto_ERROR_INVALID_RSA_KEY + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING + * + * Threading: * This is a "Property Function" and may be called simultaneously with any * other property function or session function, but not any initialization or * usage table function, as if the CDM holds a read lock on the OEMCrypto @@ -4719,6 +4739,93 @@ OEMCryptoResult OEMCrypto_FreeSecureBuffer( /// @} +/****************************************************************************/ +/****************************************************************************/ +/* The following functions are optional. They are only used if the device + * supports OTA keybox provisioning. Widevine does not allow all devices to + * support OTA provisioning. Using an OTA provisioned keybox usually lowers a + * device's security profile in the DCSL. Please work with your Widevine Partner + * Engineer before implementing these functions to make sure you understand the + * security implications of using Keybox OTA Provisioning. + */ + +/* + * OEMCrypto_GenerateOTARequest + * + * Description: + * Generate an OTA Keybox provisioning request. The format of the + * message is specified in the document Keybox OTA Reprovisioning. If + * use_test_key is true, then the debug model key and id should be + * used. Widevine does not allow all devices to support OTA + * provisioning. Using an OTA provisioned keybox usually lowers a device's + * security profile in the DCSL. + * + * Parameters: + * [out] buffer: where the provisioning request is stored. + * [in/out] buffer_length: length of the request, in bytes. + * [in] use_test_key: If true, use the debug model key. This is used for + * testing the workflow. + * + * Returns: + * OEMCrypto_SUCCESS on success + * OEMCrypto_ERROR_SHORT_BUFFER - if buffer_length is too small. + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * Any other error will be logged. + * + * Threading: + * This is an "Initialization and Termination Function" and will not be called + * simultaneously with any other function, as if the CDM holds a write lock on + * the OEMCrypto system. It will be called only after + * OEMCrypto_IsKeyboxOrOEMCertValid() returns + * OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING immediately after initialization, + * and before any session is opened. + * + * Version: + * This method is new in API version 16. + */ +OEMCryptoResult OEMCrypto_GenerateOTARequest(uint8_t* buffer, + size_t* buffer_length, + bool use_test_key); +/* + * OEMCrypto_ProcessOTAKeybox + * + * Description: + * The buffer will be parsed as an OTA Keybox provisioning message, as + * described in the document OTA Keybox Reprovisioning. The + * signature will be verified. The keybox will be decrypted and verified. If + * use_test_key is false, the keybox will be installed permanently. + * + * If use_test_keybox is true, do not use the real model key, use the debug + * model key specified in OTA Keybox Reprovisioning. + * + * Parameters: + * [in] buffer: pointer to provisioning response. + * [in] buffer_length: length of the buffer, in bytes. + * [in] use_test_key: If true, use the debug model key. This is used for + * testing the workflow. + * + * Returns: + * OEMCrypto_SUCCESS on success + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_SIGNATURE_FAILURE - signature of message was wrong. + * OEMCrypto_ERROR_KEYBOX_INVALID - if the keybox was unpacked, but is + * invalid. + * OEMCrypto_ERROR_WRITE_KEYBOX - could not save keybox. + * Any other error will be logged. + * + * Threading: + * This is an "Initialization and Termination Function" and will not be called + * simultaneously with any other function, as if the CDM holds a write lock on + * the OEMCrypto system. It will only be called after + * OEMCrypto_GenerateOTARequest. + * + * Version: + * This method is new in API version 16. + */ +OEMCryptoResult OEMCrypto_ProcessOTAKeybox(const uint8_t* buffer, + size_t buffer_length, + bool use_test_key); + /****************************************************************************/ /****************************************************************************/ /* The following functions are deprecated. They are not required for the diff --git a/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h b/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h index d279c5c4..b1ad6e97 100644 --- a/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h +++ b/libwvdrmengine/oemcrypto/odk/include/OEMCryptoCENCCommon.h @@ -88,6 +88,7 @@ typedef enum OEMCryptoResult { OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES = 58, OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION = 59, OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION = 60, + OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING = 61, /* ODK return values */ ODK_ERROR_BASE = 1000, ODK_ERROR_CORE_MESSAGE = ODK_ERROR_BASE, diff --git a/libwvdrmengine/oemcrypto/test/common.mk b/libwvdrmengine/oemcrypto/test/common.mk index daf7c0e4..698a7d77 100644 --- a/libwvdrmengine/oemcrypto/test/common.mk +++ b/libwvdrmengine/oemcrypto/test/common.mk @@ -18,6 +18,7 @@ LOCAL_SRC_FILES:= \ oemcrypto_test.cpp \ oemcrypto_test_android.cpp \ oemcrypto_test_main.cpp \ + ota_keybox_test.cpp \ wvcrc.cpp \ ../../cdm/util/test/test_sleep.cpp \ diff --git a/libwvdrmengine/oemcrypto/test/oec_test_data.h b/libwvdrmengine/oemcrypto/test/oec_test_data.h index fbd202da..453847b5 100644 --- a/libwvdrmengine/oemcrypto/test/oec_test_data.h +++ b/libwvdrmengine/oemcrypto/test/oec_test_data.h @@ -18,6 +18,7 @@ namespace wvoec { // This is a test keybox. It will not be accepted by production systems. By // using a known keybox for these tests, the results for a given set of inputs // to a test are predictable and can be compared to the actual results. +// clang-format off static const WidevineKeybox kTestKeybox = { // Sample keybox used for test vectors { @@ -49,6 +50,43 @@ static const WidevineKeybox kTestKeybox = { 0x39, 0xf2, 0x94, 0xa7, } }; +// clang-format on + +// This test keybox is only accepted by the QA provisioning server. +// It is not valid with the production provisioning server. +// clang-format off +static const WidevineKeybox kQATestKeybox = { + // Sample keybox used for test vectors + { + // deviceID = WidevineQATestOnlyKeybox000 + 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, + 0x51, 0x41, 0x54, 0x65, 0x73, 0x74, 0x4f, 0x6e, + 0x6c, 0x79, 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x78, + 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, + }, { + // key + 0x03, 0x77, 0x0f, 0x4e, 0x29, 0x77, 0x4b, 0x43, + 0x9e, 0xd2, 0x8a, 0x94, 0x73, 0xb3, 0x26, 0x65, + }, { + // data (system ID 2000000 = 0x1E8480). + 0x00, 0x00, 0x00, 0x02, 0x00, 0x1e, 0x84, 0x80, + 0x90, 0x46, 0x8a, 0x1d, 0x27, 0x52, 0xca, 0xdb, + 0x5b, 0xf4, 0x67, 0xcb, 0xd3, 0x5e, 0x9e, 0xe9, + 0xb1, 0xcf, 0x89, 0x74, 0x08, 0x26, 0x96, 0x5b, + 0x43, 0x02, 0x7c, 0xb6, 0x4a, 0x9d, 0xf6, 0x7e, + 0x24, 0x82, 0x1d, 0xe2, 0x89, 0x52, 0x8e, 0xac, + 0xf2, 0x98, 0xac, 0x92, 0xa9, 0x40, 0x11, 0x9f, + 0x9f, 0xf8, 0x55, 0x84, 0x42, 0x04, 0x34, 0xbc, + 0x53, 0x14, 0x3d, 0x44, 0x97, 0x5c, 0xd9, 0xb4, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0x43, 0xa2, 0x67, 0x63, + } +}; +// clang-format on // A 2048 bit RSA key in PKCS#8 PrivateKeyInfo format // Used to verify the functions that manipulate RSA keys. diff --git a/libwvdrmengine/oemcrypto/test/ota_keybox_test.cpp b/libwvdrmengine/oemcrypto/test/ota_keybox_test.cpp new file mode 100644 index 00000000..1ddaf655 --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/ota_keybox_test.cpp @@ -0,0 +1,53 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// + +#include + +#include "OEMCryptoCENC.h" +#include "clock.h" +#include "log.h" +#include "oec_decrypt_fallback_chain.h" +#include "oec_device_features.h" +#include "oec_session_util.h" +#include "oec_test_data.h" +#include "oemcrypto_session_tests_helper.h" +#include "oemcrypto_types.h" +#include "platform.h" +#include "string_conversions.h" +#include "test_sleep.h" + +using namespace std; + +namespace wvoec { +class OTAKeyboxProvisioningTest : public ::testing::Test, public SessionUtil { + protected: + void SetUp() override { + ::testing::Test::SetUp(); + wvcdm::TestSleep::SyncFakeClock(); + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name()); + OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + } + + void TearDown() override { + OEMCrypto_Terminate(); + ::testing::Test::TearDown(); + } +}; + +TEST_F(OTAKeyboxProvisioningTest, BasicTest) { + OEMCryptoResult result = OEMCrypto_IsKeyboxValid(); + if (result == OEMCrypto_SUCCESS) { + cout << " " + << "Keybox valid after initialization. Skipping rest of test." << endl; + return; + } + ASSERT_EQ(result, OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING); + cout << " " + << "OTA Keybox functions supported. Device needs provisioning." << endl; +} +} // namespace wvoec From 52bd1d206e815adba0b0eb8802bb83e3755be412 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Wed, 15 Sep 2021 02:56:19 -0700 Subject: [PATCH 02/12] Added an OTA keybox provisioner. [ Merge of http://go/wvgerrit/133729 ] The OtaKeyboxProvisioner is a system-wide provisioner for sharing the provisioning workflow between CDM engines. Bug: 189232882 Test: GtsMediaTestCases Change-Id: I873af3087cc05e1831bdd1d2c14fb002b73e6902 Added keybox provisioning proto fields. [ Merge of http://go/wvgerrit/133730 and http://go/ag/15113032 ] This CL copies over the required license_protocol.proto changes that are required for OTA keybox provisioning. These fields are defined in the server-side certificate_provisioning.proto, defined in http://cl/377533774. Note, changes are slightly different from server proto due to the RVC version of license_protocol.proto being out of date with SC and newer changes. Bug: 189232882 Test: run_x86_64_tests Change-Id: I55fcf6a7ac2ba4b6026b9acc63e822ff33c431d9 Added OTA keybox provisioning device files. [ Merge of http://go/wvgerrit/133743 and http://go/ag/15421141 ] This change adds a new set of proto messages/fields the CDM's device files for recording device and engine information around OTA keybox provisioning (OKP). To make cleanup and thread protection possible, there is a single file which will contain all the information for the device as a whole and each CDM engine tied to an app/origin. Bug: 189232882 Test: Linux unit tests Change-Id: Iaf80cd6342f32657e04416750d9b278d935821a5 Client ID for OKP requests. [ Merge of http://go/wvgerrit/133744 and http://go/ag/15645331 ] Extended the CDM ClientIdentification class to support a subset of client info used for OKP requests. Bug: 189232882 Test: Android unit tests Merged-In: I6aafb4f2164efe69bc733ece0a912f0e91893b91 Change-Id: I6aafb4f2164efe69bc733ece0a912f0e91893b91 --- libwvdrmengine/cdm/Android.bp | 1 + .../core/include/certificate_provisioning.h | 5 + .../cdm/core/include/client_identification.h | 26 +- .../cdm/core/include/crypto_session.h | 16 +- .../cdm/core/include/ota_keybox_provisioner.h | 68 +++++ .../cdm/core/src/certificate_provisioning.cpp | 12 +- .../cdm/core/src/client_identification.cpp | 28 +- .../cdm/core/src/crypto_session.cpp | 42 ++- .../cdm/core/src/device_files.proto | 67 +++++ libwvdrmengine/cdm/core/src/license.cpp | 3 +- .../cdm/core/src/license_protocol.proto | 23 ++ .../cdm/core/src/ota_keybox_provisioner.cpp | 83 ++++++ .../core/test/ota_keybox_provisioner_test.cpp | 242 ++++++++++++++++++ 13 files changed, 586 insertions(+), 30 deletions(-) create mode 100644 libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h create mode 100644 libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp create mode 100644 libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp diff --git a/libwvdrmengine/cdm/Android.bp b/libwvdrmengine/cdm/Android.bp index 4e66959b..c7ea68af 100644 --- a/libwvdrmengine/cdm/Android.bp +++ b/libwvdrmengine/cdm/Android.bp @@ -54,6 +54,7 @@ cc_library_static { CORE_SRC_DIR + "/license.cpp", CORE_SRC_DIR + "/license_key_status.cpp", CORE_SRC_DIR + "/oemcrypto_adapter_dynamic.cpp", + CORE_SRC_DIR + "/ota_keybox_provisioner.cpp", CORE_SRC_DIR + "/policy_engine.cpp", CORE_SRC_DIR + "/policy_timers.cpp", CORE_SRC_DIR + "/policy_timers_v15.cpp", diff --git a/libwvdrmengine/cdm/core/include/certificate_provisioning.h b/libwvdrmengine/cdm/core/include/certificate_provisioning.h index afdc4770..7baac324 100644 --- a/libwvdrmengine/cdm/core/include/certificate_provisioning.h +++ b/libwvdrmengine/cdm/core/include/certificate_provisioning.h @@ -67,6 +67,11 @@ class CertificateProvisioning { static bool ExtractAndDecodeSignedMessageForTesting( const std::string& provisioning_response, std::string* result); + // Retrieve the provisioning server URL used for certificate + // provisioning. This will be the same value as returned in + // |default_url| by GetProvisioningRequest(). + static void GetProvisioningServerUrl(std::string* default_url); + private: CdmResponseType GetProvisioningRequestInternal( SecurityLevel requested_security_level, CdmCertificateType cert_type, diff --git a/libwvdrmengine/cdm/core/include/client_identification.h b/libwvdrmengine/cdm/core/include/client_identification.h index ed96de0a..7f6078be 100644 --- a/libwvdrmengine/cdm/core/include/client_identification.h +++ b/libwvdrmengine/cdm/core/include/client_identification.h @@ -5,33 +5,35 @@ #ifndef WVCDM_CORE_CLIENT_IDENTIFICATION_H_ #define WVCDM_CORE_CLIENT_IDENTIFICATION_H_ +#include + // ClientIdentification fills in the ClientIdentification portion // of the License or Provisioning request messages. - #include "disallow_copy_and_assign.h" #include "license_protocol.pb.h" #include "wv_cdm_types.h" namespace wvcdm { - class CryptoSession; class ClientIdentification { public: - ClientIdentification() : is_license_request_(true) {} + ClientIdentification() {} virtual ~ClientIdentification() {} // Call this method when used with provisioning requests - CdmResponseType Init(CryptoSession* crypto_session); + CdmResponseType InitForProvisioning(CryptoSession* crypto_session); // Use in conjunction with license requests // |client_token| must be provided // |crypto_session| input parameter, mandatory - CdmResponseType Init(const std::string& client_token, - CryptoSession* crypto_session); + CdmResponseType InitForLicenseRequest(const std::string& client_token, + CryptoSession* crypto_session); - // Fill the ClientIdentification portion of the license or provisioning - // request + CdmResponseType InitForOtaKeyboxProvisioning(CryptoSession* crypto_session); + + // Fill the ClientIdentification portion of the license, DRM cert + // provisioning or OTA keybox provisioning request. // |app_parameters| parameters provided by client/app to be included in // provisioning/license request. optional, only used // if |is_license_request| is true @@ -49,13 +51,13 @@ class ClientIdentification { bool GetProvisioningTokenType( video_widevine::ClientIdentification::TokenType* token_type); - bool is_license_request_; + bool is_license_request_ = false; + bool is_okp_request_ = false; std::string client_token_; - CryptoSession* crypto_session_; + std::string device_id_; + CryptoSession* crypto_session_ = nullptr; CORE_DISALLOW_COPY_AND_ASSIGN(ClientIdentification); }; - } // namespace wvcdm - #endif // WVCDM_CORE_CLIENT_IDENTIFICATION_H_ diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index e516a1b3..a052feed 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -26,6 +26,7 @@ namespace wvcdm { class CryptoKey; class CryptoSessionFactory; +class OtaKeyboxProvisioner; class UsageTableHeader; using CryptoKeyMap = std::map; @@ -41,6 +42,7 @@ OEMCrypto_Substring GetSubstring(const std::string& message = "", bool set_zero = false); OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode); + class CryptoSession { public: using HdcpCapability = OEMCrypto_HDCP_Capability; @@ -288,7 +290,14 @@ class CryptoSession { SecurityLevel requested_security_level, CdmClientTokenType* token_type); // OTA Provisioning - // TODO(sigquit): include rest of http://go/wvgerrit/126004 + + bool needs_keybox_provisioning() const { return needs_keybox_provisioning_; } + + // Returns a system-wide singleton instance of OtaKeyboxProvisioner + // to be used for OTA provisioning requests/responses across apps. + // Returns a null pointer if OTA provisioning is NOT supported, or + // if the device has already been provisioned. + virtual OtaKeyboxProvisioner* GetOtaKeyboxProvisioner(); // Generates an OTA provisioning request. // This should only be called by an instance of OtaKeyboxProvisioner. @@ -504,6 +513,11 @@ class CryptoSession { static std::mutex factory_mutex_; static std::unique_ptr factory_; + // A singleton instance of OtaKeyboxProvisioner. Only one will + // be created for the system if OTA keybox provisioning is both + // required and supported by L1. + static std::unique_ptr ota_keybox_provisioner_l1_; + CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession); }; // class CryptoSession diff --git a/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h b/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h new file mode 100644 index 00000000..297f16b8 --- /dev/null +++ b/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h @@ -0,0 +1,68 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ +#define WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ + +#include +#include +#include + +#include "disallow_copy_and_assign.h" +#include "wv_cdm_types.h" + +namespace wvcdm { +class CryptoSession; + +// Wrapper around an OEMCrypto system-wide OTA keybox provisioning +// workflow. +class OtaKeyboxProvisioner { + public: + // Creates a new OTA keybox provisioner. This should only be + // created once and object ownership belongs to the CryptoSession + // module. + static std::unique_ptr Create(); + + ~OtaKeyboxProvisioner(); + + // === Request/response API === + + // Returns true if a provisioning response has been provided + // and accepted by OEMCrytpo. + bool IsProvisioned() const { return is_provisioned_; } + + uint32_t request_count() const { return request_count_; } + uint32_t response_count() const { return response_count_; } + + // Generates an OTA provisioning request. + // Generating a request will succeed so long as OTA provisioning + // is supported and no valid response has been provided. + CdmResponseType GenerateProvisioningRequest(CryptoSession* crypto_session, + std::string* request); + + // Accepts a provisioning response from the OTA provisioning + // server. The first response which is successfully loaded is + // is used. Any subsequent response after the first successful + // response is silently discarded. + CdmResponseType HandleProvisioningResponse(CryptoSession* crypto_session, + const std::string& response); + + private: + OtaKeyboxProvisioner(); + + bool is_provisioned_ = false; + + // These counters are for debugging purposes. + // Number of requests generated. + uint32_t request_count_ = 0; + // Number of responses provided. + uint32_t response_count_ = 0; + + // It is expected that multiple CDM engines may interact with the + // OtaKeyboxProvisioner instance simultaneously. + mutable std::mutex mutex_; + + CORE_DISALLOW_COPY_AND_ASSIGN(OtaKeyboxProvisioner); +}; // class OtaKeyboxProvisioner +} // namespace wvcdm +#endif // WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ diff --git a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp index 78242bf6..f7fcf296 100644 --- a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp +++ b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp @@ -118,6 +118,16 @@ using video_widevine::SignedProvisioningMessage; using video_widevine:: SignedProvisioningMessage_ProvisioningProtocolVersion_VERSION_1_1; +// static +void CertificateProvisioning::GetProvisioningServerUrl( + std::string* default_url) { + if (default_url == nullptr) { + LOGE("Output |default_url| is null"); + return; + } + default_url->assign(kProvisioningServerUrl); +} + CdmResponseType CertificateProvisioning::Init( const std::string& service_certificate) { std::string certificate = service_certificate.empty() @@ -221,7 +231,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal( ProvisioningRequest provisioning_request; wvcdm::ClientIdentification id; - status = id.Init(crypto_session_.get()); + status = id.InitForProvisioning(crypto_session_.get()); if (status != NO_ERROR) return status; video_widevine::ClientIdentification client_id; diff --git a/libwvdrmengine/cdm/core/src/client_identification.cpp b/libwvdrmengine/cdm/core/src/client_identification.cpp index e8e399fd..2b068e6a 100644 --- a/libwvdrmengine/cdm/core/src/client_identification.cpp +++ b/libwvdrmengine/cdm/core/src/client_identification.cpp @@ -66,35 +66,44 @@ using video_widevine::ProvisioningRequest; using video_widevine::ProvisioningResponse; using video_widevine::SignedProvisioningMessage; -CdmResponseType ClientIdentification::Init(CryptoSession* crypto_session) { +CdmResponseType ClientIdentification::InitForProvisioning( + CryptoSession* crypto_session) { if (crypto_session == nullptr) { LOGE("Crypto session not provided"); return PARAMETER_NULL; } - is_license_request_ = false; crypto_session_ = crypto_session; return NO_ERROR; } -CdmResponseType ClientIdentification::Init(const std::string& client_token, - CryptoSession* crypto_session) { +CdmResponseType ClientIdentification::InitForLicenseRequest( + const std::string& client_token, CryptoSession* crypto_session) { if (crypto_session == nullptr) { LOGE("Crypto session not provided"); return PARAMETER_NULL; } - if (client_token.empty()) { LOGE("Client token is empty"); return PARAMETER_NULL; } - is_license_request_ = true; client_token_ = client_token; crypto_session_ = crypto_session; return NO_ERROR; } +CdmResponseType ClientIdentification::InitForOtaKeyboxProvisioning( + CryptoSession* crypto_session) { + if (crypto_session == nullptr) { + LOGE("Crypto session not provided"); + return PARAMETER_NULL; + } + is_okp_request_ = true; + crypto_session_ = crypto_session; + return NO_ERROR; +} + /* * Return the ClientIdentification message token type for provisioning request. * NOTE: a DRM Cert should never be presented to the provisioning server. @@ -107,7 +116,7 @@ CdmResponseType ClientIdentification::Prepare( client_id->set_type( video_widevine::ClientIdentification::DRM_DEVICE_CERTIFICATE); client_id->set_token(client_token_); - } else { + } else if (!is_okp_request_) { video_widevine::ClientIdentification::TokenType token_type; if (!GetProvisioningTokenType(&token_type)) { LOGE("Failed to get provisioning token type"); @@ -189,6 +198,11 @@ CdmResponseType ClientIdentification::Prepare( client_id->set_provider_client_token(provider_client_token); } + if (is_okp_request_) { + // Capabilities is not important for OTA keybox provisionining. + return NO_ERROR; + } + ClientCapabilities* client_capabilities = client_id->mutable_client_capabilities(); diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 7967b49b..7249f9dd 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -20,6 +20,7 @@ #include "crypto_key.h" #include "entitlement_key_session.h" #include "log.h" +#include "ota_keybox_provisioner.h" #include "platform.h" #include "privacy_crypto.h" #include "properties.h" @@ -178,6 +179,7 @@ std::unique_ptr CryptoSession::usage_table_header_l1_; std::unique_ptr CryptoSession::usage_table_header_l3_; std::recursive_mutex CryptoSession::usage_table_mutex_; std::atomic CryptoSession::request_id_index_source_(0); +std::unique_ptr CryptoSession::ota_keybox_provisioner_l1_; size_t GetOffset(std::string message, std::string field) { size_t pos = message.find(field); @@ -360,8 +362,15 @@ void CryptoSession::Init() { LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(), api_minor_version.c_str()); if (needs_keybox_provisioning_) { - LOGE("OEMCrypto needs provisioning"); - // TODO(fredgc,sigquit,rfrias): handle provisioning. + WithStaticFieldWriteLock("OtaKeyboxProvisioner", [&] { + if (!ota_keybox_provisioner_l1_) { + LOGD("OEMCrypto needs keybox provisioning"); + // Only create once. Possible that OEMCrypto is initialized + // and terminated many times over the life cycle of the OTA + // keybox provisioning process. + ota_keybox_provisioner_l1_ = OtaKeyboxProvisioner::Create(); + } + }); } } } @@ -3011,11 +3020,20 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( remaining_input_data -= chunk_size; AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, chunk_size); } - return sts; } -// TODO(sigquit): include rest of http://go/wvgerrit/126004 +OtaKeyboxProvisioner* CryptoSession::GetOtaKeyboxProvisioner() { + const auto getter = [&]() -> OtaKeyboxProvisioner* { + // If not set, then OTA keybox provisioning is not supported or + // not needed. + if (!ota_keybox_provisioner_l1_) return nullptr; + // May have already been initialized. + if (ota_keybox_provisioner_l1_->IsProvisioned()) return nullptr; + return ota_keybox_provisioner_l1_.get(); + }; + return WithStaticFieldReadLock("GetOtaKeyboxProvisioner", getter); +} CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( bool use_test_key, std::string* request) { @@ -3026,17 +3044,25 @@ CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( if (status != OEMCrypto_ERROR_SHORT_BUFFER) return MapOEMCryptoResult(status, UNKNOWN_ERROR, "PrepareOtaProvisioningRequest"); - std::string temp_buffer(buffer_length, '\0'); - uint8_t* buf = reinterpret_cast(&temp_buffer[0]); + if (buffer_length == 0) { + LOGE("OTA request size is zero"); + return UNKNOWN_ERROR; + } + request->resize(buffer_length); + uint8_t* buf = reinterpret_cast(&request->front()); status = OEMCrypto_GenerateOTARequest(buf, &buffer_length, use_test_key); - if (OEMCrypto_SUCCESS == status) request->assign(temp_buffer); + if (OEMCrypto_SUCCESS != status) { + request->clear(); + } else if (buffer_length != request->size()) { + request->resize(buffer_length); + } return MapOEMCryptoResult(status, UNKNOWN_ERROR, "PrepareOtaProvisioningRequest"); } CdmResponseType CryptoSession::LoadOtaProvisioning( bool use_test_key, const std::string& response) { - OEMCryptoResult status = OEMCrypto_ProcessOTAKeybox( + const OEMCryptoResult status = OEMCrypto_ProcessOTAKeybox( reinterpret_cast(response.data()), response.size(), use_test_key); return MapOEMCryptoResult(status, UNKNOWN_ERROR, "LoadOtaProvisioning"); diff --git a/libwvdrmengine/cdm/core/src/device_files.proto b/libwvdrmengine/cdm/core/src/device_files.proto index c52cee1f..98f15701 100644 --- a/libwvdrmengine/cdm/core/src/device_files.proto +++ b/libwvdrmengine/cdm/core/src/device_files.proto @@ -125,6 +125,71 @@ message UsageTableInfo { optional bool use_lru = 3 [default = false]; } +// Stores information related to a device's experience with OTA Keybox +// Provisioning (OKP). Only devices which both support OKP and require +// OKP should create this file. Otherwise, this information is not +// needed. +message OtaKeyboxProvisioningInfo { + // Engine-specific information about OKP. + message OkpEngineInfo { + // Engine identifier. + optional bytes app_id = 1; + optional bytes origin = 2; + reserved 3 to 5; // Reserved for future engine composite keys. + // Counters for engine-specific OKP events. + // These counters are reset after a certain amount of time + // (OKP period) since the last event. + // Number of calls to openSession() where it is recommended + // to the app to try keybox provisioning. + optional uint32 try_okp_counter = 6; + // Number of calls to getProvisionRequest(). + optional uint32 generate_request_counter = 7; + // Number of failed calls to provideProvisionRequest(). + optional uint32 failed_response_counter = 8; + + // The value of |last_event_time| and |backoff_start_time| are set + // using the system's wall-clock in epoch seconds. A value of + // zero indicates it's not set. + + // Time of the last engine OKP event (change of the above counters; + // the beginning of the current OKP period). + // Zero indicates no event has yet occurred. + optional int64 last_event_time = 9; + // Beginning of an app/origin backoff period. + // Zero indicates that engine is not in a backoff state. + optional int64 backoff_start_time = 10; + // Intended length of “backoff period”. This will be assigned a + // random duration initially, then double each time an engine + // enters a backoff state. This is base on Google's recommended + // exponential backoff rules. + // Value of 0 indicates that backoff has not yet occurred. + optional int64 backoff_duration = 11; + } + + enum OkpDeviceState { + // Not yet checked for provisioning state. This should be a + // transitory state only. Device which do not need OTA Keybox + // Provisioning should simply not store this file. + OKP_UNKNOWN = 0; + // OEMCrypto has reported that keybox provisioning is required and + // that the device supports OKP. Device may or may not be in the + // process of performing provisioning. + OKP_NEEDS_PROVISIONING = 1; + // The device has successfully provisioned its keybox. + OKP_PROVISIONED = 2; + } + // Device-wide OKP state. + optional OkpDeviceState state = 1; + // Time when the CDM service first discovers that it needs to + // provision the L1 keybox. + optional int64 first_checked_time = 2; + // System time of when a successful provisioning request has been + // received. Only relevant if |state| is OKP_PROVISIONED. + optional int64 provisioning_time = 3; + // A list of all records for each identifiable engine. + repeated OkpEngineInfo engine_infos = 4; +} + message File { enum FileType { DEVICE_CERTIFICATE = 1; @@ -132,6 +197,7 @@ message File { USAGE_INFO = 3; HLS_ATTRIBUTES = 4; USAGE_TABLE_INFO = 5; + OKP_INFO = 6; } enum FileVersion { VERSION_1 = 1; } @@ -143,6 +209,7 @@ message File { optional UsageInfo usage_info = 5; optional HlsAttributes hls_attributes = 6; optional UsageTableInfo usage_table_info = 7; + optional OtaKeyboxProvisioningInfo okp_info = 8; } message HashedFile { diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index f85cbfe6..51b8805a 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -1046,7 +1046,8 @@ CdmResponseType CdmLicense::PrepareClientId( return CLIENT_TOKEN_NOT_SET; } - CdmResponseType status = id.Init(client_token_, crypto_session_); + CdmResponseType status = + id.InitForLicenseRequest(client_token_, crypto_session_); if (status != NO_ERROR) return status; video_widevine::ClientIdentification* client_id = diff --git a/libwvdrmengine/cdm/core/src/license_protocol.proto b/libwvdrmengine/cdm/core/src/license_protocol.proto index eaf2a45e..1ad2ce1a 100644 --- a/libwvdrmengine/cdm/core/src/license_protocol.proto +++ b/libwvdrmengine/cdm/core/src/license_protocol.proto @@ -532,6 +532,14 @@ message ProvisioningRequest { // Serialized, encrypted session keys. Required. optional bytes encrypted_session_keys = 2; } + // This message contains the custom serialized message for OTA provisioning + // using Android Attestation and a device id as authentication. + message AndroidAttestationOtaKeyboxRequest { + // The request contains custom serialized and signed data for the + // Android Attestation OTA request. + optional bytes ota_request = 1; + } + oneof clear_or_encrypted_client_id { // Device root of trust and other client identification. Required. ClientIdentification client_id = 1; @@ -555,6 +563,8 @@ message ProvisioningRequest { // SessionKeys encrypted using a service cert public key. // Required for keybox provisioning. optional EncryptedSessionKeys encrypted_session_keys = 8; + // The custom request for Android Attestation OTA. + optional AndroidAttestationOtaKeyboxRequest android_ota_keybox_request = 9; } // Provisioning response sent by the provisioning server to client devices. @@ -579,6 +589,14 @@ message ProvisioningResponse { // Devices in this series have been revoked. Provisioning is not possible. REVOKED_DEVICE_SERIES = 2; } + // This message contains the custom response for Android Attestation OTA + // provisioning which uses the Android Attestation keybox and a device id + // from the chip set. + message AndroidAttestationOtaKeyboxResponse { + // The response contains custom serialized and signed data for the + // Android Attestation OTA keybox provisioning. + optional bytes ota_response = 1; + } // AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded. // Required. For X.509 certificates, the private RSA key may also include @@ -603,6 +621,9 @@ message ProvisioningResponse { // than |status| may be empty and should be ignored if the |status| // is present and not NO_ERROR optional ProvisioningStatus status = 7; + // The Android Attestation OTA response. Only populated if the request + // was an Android Attestation OTA request. + optional AndroidAttestationOtaKeyboxResponse android_ota_keybox_response = 8; } // Protocol-specific context data used to hold the state of the server in @@ -654,6 +675,8 @@ message SignedProvisioningMessage { PROVISIONING_20 = 2; // Keybox factory-provisioned devices. PROVISIONING_30 = 3; // OEM certificate factory-provisioned devices. ARCPP_PROVISIONING = 4; // ChromeOS/Arc++ devices. + // Android-Attestation-based OTA keyboxes. + ANDROID_ATTESTATION_KEYBOX_OTA = 6; INTEL_SIGMA_101 = 101; // Intel Sigma 1.0.1 protocol. } diff --git a/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp b/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp new file mode 100644 index 00000000..e88109a8 --- /dev/null +++ b/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp @@ -0,0 +1,83 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "ota_keybox_provisioner.h" + +#include + +#include "crypto_session.h" +#include "log.h" +#include "string_conversions.h" + +namespace wvcdm { +using UniqueLock = std::unique_lock; +namespace { +// Indicates not to use the test keybox for OTA provisioning. +constexpr bool kProductionKeybox = false; +} // namespace + +// static +std::unique_ptr OtaKeyboxProvisioner::Create() { + return std::unique_ptr(new OtaKeyboxProvisioner()); +} + +OtaKeyboxProvisioner::OtaKeyboxProvisioner() : mutex_() {} + +OtaKeyboxProvisioner::~OtaKeyboxProvisioner() {} + +CdmResponseType OtaKeyboxProvisioner::GenerateProvisioningRequest( + CryptoSession* crypto_session, std::string* request) { + if (crypto_session == nullptr) { + LOGE("Input |crypto_session| is null"); + return PARAMETER_NULL; + } + if (request == nullptr) { + LOGE("Output |request| is null"); + return PARAMETER_NULL; + } + UniqueLock lock(mutex_); + // Do not generate new requests if already provisioned. + if (IsProvisioned()) { + LOGW("Already provisioned"); + // TODO(sigquit): Use a response code that will indicate to the + // caller that the system is already provisioned. + return UNKNOWN_ERROR; + } + const CdmResponseType result = + crypto_session->PrepareOtaProvisioningRequest(kProductionKeybox, request); + if (result != NO_ERROR) { + return result; + } + LOGV("OTA request generated: request = %s", b2a_hex(*request).c_str()); + request_count_++; + return NO_ERROR; +} + +CdmResponseType OtaKeyboxProvisioner::HandleProvisioningResponse( + CryptoSession* crypto_session, const std::string& response) { + if (crypto_session == nullptr) { + LOGE("Input |crypto_session| is null"); + return PARAMETER_NULL; + } + if (response.empty()) { + LOGE("OTA provisioning response is empty"); + return EMPTY_PROVISIONING_RESPONSE; + } + UniqueLock lock(mutex_); + if (IsProvisioned()) { + // Response already received, silently dropping. + response_count_++; + return NO_ERROR; + } + const CdmResponseType result = + crypto_session->LoadOtaProvisioning(kProductionKeybox, response); + if (result != NO_ERROR) { + return result; + } + LOGD("OTA response successfully processed: response = %s", + b2a_hex(response).c_str()); + is_provisioned_ = true; + response_count_ = 1; + return NO_ERROR; +} +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp b/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp new file mode 100644 index 00000000..ca10e39c --- /dev/null +++ b/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp @@ -0,0 +1,242 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "ota_keybox_provisioner.h" + +#include +#include + +#include "crypto_session.h" + +namespace wvcdm { +using ::testing::DoAll; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +namespace { +const std::string kEmptyString; +const std::string kFakeOtaProvisioningRequest = + "Totally real device ID, not fake" // Device ID : 32 bytes + "Totally real model ID, also real" // Model ID : 32 bytes + "Super secure key" // Encryped session key : 16 bytes + "Undoubtedly authentic signature!"; // Signature : 32 bytes + +const std::string kFakeOtaProvisioningResponse = + "Definitely an IV" // IV : 16 bytes + // Keybox : 128 bytes + "I'm a keybox, look at my keys and box-like appearance []. You might " + "be thinking 'you are not a real keybox', but you'd be wrong" + "Scribble scribble dot slash dot "; // Signature : 32 bytes + +const std::string kAnotherFakeOtaProvisioningResponse = + "Looks like an IV" // IV : 16 bytes + // Keybox : 128 bytes + "I am also a keybox. It's so safe to assume I'm a real keybox that " + "attempting to verify that will look very embarrassing for you" + "A drawing of boat with dolphins "; // Signature : 32 bytes + +class MockCryptoSession : public CryptoSession { + public: + MockCryptoSession() : CryptoSession(&crypto_metrics_) {} + ~MockCryptoSession() {} + + bool IsOpen() override { return is_open_; } + void SetIsOpen(bool is_open) { is_open_ = is_open; } + + MOCK_METHOD2(PrepareOtaProvisioningRequest, + CdmResponseType(bool, std::string*)); + MOCK_METHOD2(LoadOtaProvisioning, CdmResponseType(bool, const std::string&)); + + void ExpectRequest(const std::string& request, CdmResponseType result) { + if (result == NO_ERROR) { + EXPECT_CALL(*this, PrepareOtaProvisioningRequest(false, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(request), Return(NO_ERROR))); + } else { + EXPECT_CALL(*this, PrepareOtaProvisioningRequest(false, NotNull())) + .WillOnce(Return(result)); + } + } + + void ExpectResponse(const std::string& response, CdmResponseType result) { + EXPECT_CALL(*this, LoadOtaProvisioning(false, response)) + .WillOnce(Return(result)); + } + + private: + bool is_open_ = false; + + static metrics::CryptoMetrics crypto_metrics_; +}; + +metrics::CryptoMetrics MockCryptoSession::crypto_metrics_; +} // namespace + +class OtaKeyboxProvisionerTest : public ::testing::Test { + protected: + void SetUp() override { + crypto_session_.reset(new MockCryptoSession()); + provisioner_ = OtaKeyboxProvisioner::Create(); + } + + void TearDown() override { + crypto_session_.reset(); + provisioner_.reset(); + } + + std::unique_ptr crypto_session_; + std::unique_ptr provisioner_; +}; + +TEST_F(OtaKeyboxProvisionerTest, SingleRequestSingleResponse) { + // Pre-request conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(0u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); + + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &request)); + EXPECT_EQ(request, kFakeOtaProvisioningRequest); + + // Post-request, pre-response conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(1u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); + + // Load response. + const std::string response = kFakeOtaProvisioningResponse; + crypto_session_->ExpectResponse(response, NO_ERROR); + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), response)); + + // Post-response conditions. + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_EQ(1u, provisioner_->request_count()); + EXPECT_EQ(1u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, MultipleRequestsMultipleResponse) { + // Generate first request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string first_request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &first_request)); + EXPECT_EQ(first_request, kFakeOtaProvisioningRequest); + + // Generate second request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string second_request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &second_request)); + EXPECT_EQ(second_request, kFakeOtaProvisioningRequest); + + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(2u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); + + // Load first response. + const std::string first_response = kFakeOtaProvisioningResponse; + crypto_session_->ExpectResponse(first_response, NO_ERROR); + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), first_response)); + + // Loading second response should be silently dropped. + // No call to CryptoSession. + const std::string second_response = kAnotherFakeOtaProvisioningResponse; + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), second_response)); + + // Post-response conditions. + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_EQ(2u, provisioner_->request_count()); + EXPECT_EQ(2u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, NullRequestParameters) { + // Null CryptoSession + std::string request; + EXPECT_NE(NO_ERROR, + provisioner_->GenerateProvisioningRequest(nullptr, &request)); + + // Null request, no call to CryptoSession. + EXPECT_NE(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), nullptr)); + // Counter should not increase. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(0u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, EmptyRequest) { + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &request)); + + // Attempt to load empty response. No call to CryptoSession. + const std::string response = ""; + EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), response)); + + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(1u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, OtaProvisioningNotImplemented) { + // Generate request. + crypto_session_->ExpectRequest(kEmptyString, NOT_IMPLEMENTED_ERROR); + std::string request; + EXPECT_EQ(NOT_IMPLEMENTED_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &request)); + // Counter should not increase. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(0u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, ResponseRejected) { + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &request)); + + // Attempt to load response. OEMCrypto rejects response. + const std::string response = kFakeOtaProvisioningResponse; + crypto_session_->ExpectResponse(response, UNKNOWN_ERROR); + EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), response)); + + // Should not be provisioned. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(1u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, GenerateRequestAfterProvisioning) { + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string first_request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &first_request)); + + // Load response. + const std::string response = kFakeOtaProvisioningResponse; + crypto_session_->ExpectResponse(response, NO_ERROR); + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), response)); + + // Attempt to generate second request. Should fail. + std::string second_request; + EXPECT_NE(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &second_request)); + + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_EQ(1u, provisioner_->request_count()); + EXPECT_EQ(1u, provisioner_->response_count()); +} +} // namespace wvcdm From 39558526f665b23e02de823236f8e87274a4eb2b Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Wed, 15 Sep 2021 05:00:19 -0700 Subject: [PATCH 03/12] Wrapped OKP info into several classes. [ Merge of http://go/wvgerrit/133744 ] This changes adds several small classes which contain and manage system and engine information related to OTA keybox provisioning. These classes closely map to the OKP device file messages. Bug: 189232882 Test: Linux unit tests Change-Id: Ia9334c38f9d7ea89b30d9ad05f0595570bb38658 Storing and loading OKP info. [ Merge of http://go/wvgerrit/133763 and http://go/ag/15645333 ] This change extends the DeviceFiles module to be able to store and load OKP info. Mild data validation is performed when storing and loading the information. Bug: 189232882 Test: Android unit tests Change-Id: I077de3234157252f2255a4389bf82a8d5344a355 System OKP fallback policy. [ Merge of http://go/wvgerrit/133783 and http://go/ag/15645334 ] SystemFallbackPolicy provides a thread-safe interface for accessing and modifying OKP info. Bug: 189232882 Test: Android unit tests Change-Id: I4e43e3bc047ed5fb6cb517b53e4094e812b70e1e Engine OKP provisioner. [ Merge of http://go/wvgerrit/133803 and http://go/ag/15645335 ] The OtaKeyboxProvisioner provides a CdmEngine-specific context for performing OTA keybox provisioning. Utilizes the system-wide SystemFallbackPolicy to relay provisioning status between engines. The provisioner will handle message wrapping and unwrapping of the raw OTA keybox request / response into the SignedProvisioningMessage which is sent to/received from the provisioning server. [ Partial merge of http://go/wvgerrit/125844 ] Note: Includes partial CryptoSession changes from various CLs. CryptoSession functionality has been stripped to reduce impact of this CL. Bug: 189232882 Test: Android unit tests Change-Id: I282bf7d1887daefb2250af1bd595c4dc3dfcfb29 Integrated OKP into CDM Engine [ Merge of http://go/wvgerrit/133804 and http://go/ag/15646376 ] Extended the functionality of the CdmEngine to check if the device requires OKP and to initialize OKP resources if required. The functionality of OpenSession() and GetProvisioningRequest() have been the most affected. If OKP is required, these methods will signal to the app that provisioning is required and will return an OKP request. Once a device is provisioned, the OKP data is cleared away and the CdmEngine will resume normal operation. Engines created after a device is provisioned will immediately enter normal operations. The exception is for CdmEngines which failed to perform OKP for some reason and are still running. Those apps will need to restart before gaining access to L1 operations. Bug: 187646550 Test: Android integration tests Merged-In: Ia572a66a7b73479355758aa3d0c682691eaca0fc Change-Id: Ia572a66a7b73479355758aa3d0c682691eaca0fc --- libwvdrmengine/cdm/Android.bp | 2 + libwvdrmengine/cdm/core/include/cdm_engine.h | 26 +- libwvdrmengine/cdm/core/include/cdm_session.h | 9 +- .../core/include/certificate_provisioning.h | 2 +- .../cdm/core/include/crypto_session.h | 20 +- .../cdm/core/include/device_files.h | 10 + .../cdm/core/include/okp_fallback_policy.h | 113 ++++++ libwvdrmengine/cdm/core/include/okp_info.h | 78 ++++ .../cdm/core/include/ota_keybox_provisioner.h | 89 +++-- .../cdm/core/include/wv_cdm_types.h | 2 + libwvdrmengine/cdm/core/src/cdm_engine.cpp | 233 ++++++++++- libwvdrmengine/cdm/core/src/cdm_session.cpp | 10 +- .../cdm/core/src/certificate_provisioning.cpp | 105 +++-- .../cdm/core/src/crypto_session.cpp | 25 +- libwvdrmengine/cdm/core/src/device_files.cpp | 161 ++++++++ .../cdm/core/src/device_files.proto | 55 +-- .../cdm/core/src/okp_fallback_policy.cpp | 208 ++++++++++ libwvdrmengine/cdm/core/src/okp_info.cpp | 32 ++ .../cdm/core/src/ota_keybox_provisioner.cpp | 269 +++++++++++-- .../cdm/core/test/device_files_unittest.cpp | 106 +++++ .../core/test/okp_fallback_policy_test.cpp | 372 ++++++++++++++++++ .../core/test/ota_keybox_provisioner_test.cpp | 371 +++++++++++------ .../cdm/core/test/test_printers.cpp | 13 +- libwvdrmengine/cdm/core/test/test_printers.h | 6 +- libwvdrmengine/cdm/test/Android.mk | 9 + .../cdm/test/request_license_test.cpp | 2 +- libwvdrmengine/include/WVErrors.h | 4 +- libwvdrmengine/include/mapErrors-inl.h | 4 + libwvdrmengine/include_hidl/mapErrors-inl.h | 2 + libwvdrmengine/run_all_unit_tests.sh | 2 + 30 files changed, 2010 insertions(+), 330 deletions(-) create mode 100644 libwvdrmengine/cdm/core/include/okp_fallback_policy.h create mode 100644 libwvdrmengine/cdm/core/include/okp_info.h create mode 100644 libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp create mode 100644 libwvdrmengine/cdm/core/src/okp_info.cpp create mode 100644 libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp diff --git a/libwvdrmengine/cdm/Android.bp b/libwvdrmengine/cdm/Android.bp index c7ea68af..a03c47ec 100644 --- a/libwvdrmengine/cdm/Android.bp +++ b/libwvdrmengine/cdm/Android.bp @@ -54,6 +54,8 @@ cc_library_static { CORE_SRC_DIR + "/license.cpp", CORE_SRC_DIR + "/license_key_status.cpp", CORE_SRC_DIR + "/oemcrypto_adapter_dynamic.cpp", + CORE_SRC_DIR + "/okp_fallback_policy.cpp", + CORE_SRC_DIR + "/okp_info.cpp", CORE_SRC_DIR + "/ota_keybox_provisioner.cpp", CORE_SRC_DIR + "/policy_engine.cpp", CORE_SRC_DIR + "/policy_timers.cpp", diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index 734cad36..e85cc9ba 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -24,11 +24,11 @@ #include "wv_cdm_types.h" namespace wvcdm { - class CdmClientPropertySet; class CdmEngineFactory; class CdmSession; class CryptoEngine; +class OtaKeyboxProvisioner; class UsagePropertySet; class WvCdmEventListener; @@ -379,6 +379,13 @@ class CdmEngine { void CloseExpiredReleaseSessions(); + // Returns "true" if |okp_provisioner_| should be checked. + bool OkpCheck(); + // Returns "true" if CdmEngine should always fallback to L3. + bool OkpIsInFallbackMode(); + void OkpTriggerFallback(); + void OkpCleanUp(); + // instance variables /* @@ -423,6 +430,23 @@ class CdmEngine { // occur that may subsequently call back into CdmEngine. std::recursive_mutex session_map_lock_; + // OTA Keybox Provisioning (OKP) + // Engine should check for the OKP status of the device before opening + // sessions or generating DRM cert provisioning requests. + bool okp_initialized_ = false; + // If OKP is required, then the engine should create an instance + // of |okp_provisioner_|. If the instance exists, it should be used + // for GetProvisionRequest, ProvideProvisionRequest, and + // OpenSession when requested with default security level. + std::unique_ptr okp_provisioner_; + // Should the engine need to fallback, this flag should be set to + // true and |okp_provisioner_| should be cleared. All follow-up + // requests from the app with security level default should use L3. + bool okp_fallback_ = false; + // To prevent race conditions around the engine's OKP state, this mutex + // should be locked before the use of any of the |okp_*| variables. + std::mutex okp_mutex_; + CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine); }; diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 37a30055..226f6c80 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -60,9 +60,13 @@ class CdmSession { // // |event_listener| is caller owned, may be null, but must be in scope as long // as the session is in scope. + // + // |forced_level3|_is used to specify whether the "default" security level + // should always use L3 even if L1 is available. virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set, const CdmSessionId* forced_session_id, - WvCdmEventListener* event_listener); + WvCdmEventListener* event_listener, + bool forced_level3); // Restores an offline session identified by the |key_set_id| and // |license_type|. The |error_detail| will be filled with an internal error @@ -280,6 +284,9 @@ class CdmSession { bool is_temporary_; CdmSecurityLevel security_level_; SecurityLevel requested_security_level_; + // If |forced_level3_|, |security_level_| and |requested_security_level_| + // MUST be set to kSecurityLevelL3 and kLevel3, respectively. + bool forced_level3_ = false; CdmAppParameterMap app_parameters_; bool atsc_mode_enabled_ = false; std::string drm_certificate_; diff --git a/libwvdrmengine/cdm/core/include/certificate_provisioning.h b/libwvdrmengine/cdm/core/include/certificate_provisioning.h index 7baac324..972925cc 100644 --- a/libwvdrmengine/cdm/core/include/certificate_provisioning.h +++ b/libwvdrmengine/cdm/core/include/certificate_provisioning.h @@ -64,7 +64,7 @@ class CertificateProvisioning { // Removes json wrapping if applicable to extract the // SignedProvisioningMessage - static bool ExtractAndDecodeSignedMessageForTesting( + static bool ExtractAndDecodeSignedMessage( const std::string& provisioning_response, std::string* result); // Retrieve the provisioning server URL used for certificate diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index a052feed..6d04ba4a 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -23,12 +23,15 @@ #include "wv_cdm_types.h" namespace wvcdm { - class CryptoKey; class CryptoSessionFactory; class OtaKeyboxProvisioner; class UsageTableHeader; +namespace okp { +class SystemFallbackPolicy; +} // namespace okp + using CryptoKeyMap = std::map; // Crypto session utility functions used by KeySession implementations. @@ -42,7 +45,6 @@ OEMCrypto_Substring GetSubstring(const std::string& message = "", bool set_zero = false); OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode); - class CryptoSession { public: using HdcpCapability = OEMCrypto_HDCP_Capability; @@ -293,11 +295,11 @@ class CryptoSession { bool needs_keybox_provisioning() const { return needs_keybox_provisioning_; } - // Returns a system-wide singleton instance of OtaKeyboxProvisioner - // to be used for OTA provisioning requests/responses across apps. - // Returns a null pointer if OTA provisioning is NOT supported, or - // if the device has already been provisioned. - virtual OtaKeyboxProvisioner* GetOtaKeyboxProvisioner(); + // Returns a system-wide singleton instance of SystemFallbackPolicy + // to be used for communicating OTA keybox provisioning state between + // apps. Returns a null pointer if OTA provisioning is not supported, + // or if the device has already been provisioned. + static okp::SystemFallbackPolicy* GetOkpFallbackPolicy(); // Generates an OTA provisioning request. // This should only be called by an instance of OtaKeyboxProvisioner. @@ -513,10 +515,10 @@ class CryptoSession { static std::mutex factory_mutex_; static std::unique_ptr factory_; - // A singleton instance of OtaKeyboxProvisioner. Only one will + // A singleton instance of SystemFallbackPolicy. Only one will // be created for the system if OTA keybox provisioning is both // required and supported by L1. - static std::unique_ptr ota_keybox_provisioner_l1_; + static std::unique_ptr okp_fallback_policy_l1_; CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession); }; // class CryptoSession diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index c62001b5..9516471b 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -12,6 +12,7 @@ #include "crypto_wrapped_key.h" #include "device_files.pb.h" #include "disallow_copy_and_assign.h" +#include "okp_info.h" #include "platform.h" #include "wv_cdm_types.h" @@ -271,6 +272,11 @@ class DeviceFiles { virtual bool DeleteUsageTableInfo(); + // OTA Keybox Provisioning (OKP) information. + virtual bool StoreOkpInfo(const okp::SystemFallbackInfo& info); + virtual bool RetrieveOkpInfo(okp::SystemFallbackInfo* info); + virtual bool DeleteOkpInfo(); + private: // This method will retrieve the certificate and perform expiry validation // appropriate for a given certificate type @@ -299,6 +305,7 @@ class DeviceFiles { static std::string GetHlsAttributesFileNameExtension(); static std::string GetLicenseFileNameExtension(); static std::string GetUsageTableFileName(); + static std::string GetOkpInfoFileName(); static std::string GetFileNameSafeHash(const std::string& input); #if defined(UNIT_TEST) @@ -327,6 +334,9 @@ class DeviceFiles { FRIEND_TEST(DeviceFilesTest, StoreCertificateInvalidParams); FRIEND_TEST(DeviceFilesTest, StoreLicenses); FRIEND_TEST(DeviceFilesTest, UpdateLicenseState); + FRIEND_TEST(DeviceFilesTest, OkpInfo_FileDoesNotExist); + FRIEND_TEST(DeviceFilesTest, OkpInfo_DeleteFile); + FRIEND_TEST(DeviceFilesTest, OkpInfo_StoreAndRetrieve); FRIEND_TEST(DeviceFilesUsageInfoTest, Delete); FRIEND_TEST(DeviceFilesUsageInfoTest, DeleteAll); FRIEND_TEST(DeviceFilesUsageInfoTest, Read); diff --git a/libwvdrmengine/cdm/core/include/okp_fallback_policy.h b/libwvdrmengine/cdm/core/include/okp_fallback_policy.h new file mode 100644 index 00000000..36129a63 --- /dev/null +++ b/libwvdrmengine/cdm/core/include/okp_fallback_policy.h @@ -0,0 +1,113 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CORE_OKP_FALLBACK_POLICY_H_ +#define WVCDM_CORE_OKP_FALLBACK_POLICY_H_ +#include + +#include +#include + +#include "clock.h" +#include "disallow_copy_and_assign.h" +#include "okp_info.h" + +namespace wvcdm { +class DeviceFiles; +class FileSystem; +// OTA Keybox Provisioning (OKP) +namespace okp { +static constexpr int64_t kSecondsPerHour = 60 * 60; +static constexpr int64_t kSecondsPerDay = kSecondsPerHour * 24; +// Initial backoff duration. Subsequent backoff durations for the +// same engine will double its previous duration. +static constexpr int64_t kAverageInitialBackoffDuration = kSecondsPerDay; +static constexpr int64_t kInitalBackoffDurationDelta = kSecondsPerHour * 12; +// Minimum backoff duration which an device will be required to +// backoff the first time. +static constexpr int64_t kMinInitialBackoffDuration = + kAverageInitialBackoffDuration - kInitalBackoffDurationDelta; +static constexpr int64_t kMaxInitialBackoffDuration = + kAverageInitialBackoffDuration + kInitalBackoffDurationDelta; + +// SystemFallbackPolicy is a centralized OKP state manager which allows +// multiple CDM engines to communicate between each other. In a production +// build, there should only be at most one SystemFallbackPolicy instance. +class SystemFallbackPolicy { + public: + // Creates a new instance of SystemFallbackPolicy. If there exists + // OKP information for the device in storage, it will be loaded and + // the system policy will resume from its previous state. If no + // OKP information exists, then the policy begins new. + // Caller should immediately mark the fallback policy as requiring + // provisioning. + static std::unique_ptr Create(); + // Creates a new instance of SystemFallbackPolicy for testing. + // The testing instance of SystemFallbackPolicy behaves similar to a + // production instance, except that it will not use device storage. + // Optionally, a fake clock may be used for timestamp operations + // and/or fake data may be used to initialize the policy. + // Params: + // - |info| (optional) + // Fake device OKP info to use as a resume point. If not + // specified, then policy begins the same as if no OKP + // device info exists. + // - |clock| (optional) + // Fake/mock clock to be used instead of the CDM's default + // Clock. + static std::unique_ptr CreateForTesting( + Clock* clock = nullptr); + static std::unique_ptr CreateForTesting( + const SystemFallbackInfo& info, Clock* clock = nullptr); + + // == System Info == + const SystemFallbackInfo& info() const { return info_; } + SystemState state() const { return info_.state(); } + void MarkNeedsProvisioning(); + void TriggerFallback(); + void MarkProvisioned(); + + bool IsProvisioned(); + bool IsInFallbackMode(); + + ~SystemFallbackPolicy(); + + private: + SystemFallbackPolicy(); + + // Checks the device's file system for OKP info and loads it. + // If the info does not exist, policy begins fresh. + void TryRestore(); + + void StoreInfo(); + + int64_t GetSecondsSinceBackoffStart() const; + void EndBackoffPeriod(); + + void SetClockForTesting(Clock* clock) { + clock_ref_ = (clock == nullptr) ? &clock_ : clock; + } + int64_t GetCurrentTime() const { return clock_ref_->GetCurrentTime(); } + + bool IsTestMode() const; + + SystemFallbackInfo info_; + + // Handle for the DeviceFiles instance used to store the OKP + // information. + // Not set for test instances. + std::unique_ptr fs_; + std::unique_ptr device_files_; + + Clock clock_; // System clock + Clock* clock_ref_ = nullptr; // Pointer to clock to be used. + + // All public methods must lock to protect from simultaneous + // engine access. + mutable std::mutex mutex_; + + CORE_DISALLOW_COPY_AND_ASSIGN(SystemFallbackPolicy); +}; // class SystemFallbackPolicy +} // namespace okp +} // namespace wvcdm +#endif // WVCDM_CORE_OKP_FALLBACK_POLICY_H_ diff --git a/libwvdrmengine/cdm/core/include/okp_info.h b/libwvdrmengine/cdm/core/include/okp_info.h new file mode 100644 index 00000000..3bd91424 --- /dev/null +++ b/libwvdrmengine/cdm/core/include/okp_info.h @@ -0,0 +1,78 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CORE_OKP_SYSTEM_INFO_H_ +#define WVCDM_CORE_OKP_SYSTEM_INFO_H_ + +#include + +namespace wvcdm { +// OTA Keybox Provisioning (OKP) +namespace okp { +enum class SystemState { + kUnknown = 0, + kNeedsProvisioning = 1, + kFallbackMode = 2, // Fallback indicates provisioning is needed. + kProvisioned = 3 + // Note: "Not needed" is represented by an absence of info. +}; +// Converts a SystemState value to a human readable string. Intended +// to be used for debug logging. +const char* SystemStateToString(SystemState state); + +// Container for all the device information related to OKP. +class SystemFallbackInfo { + public: + SystemState state() const { return state_; } + void SetState(SystemState state) { state_ = state; } + + bool HasFirstCheckedTime() const { return first_checked_time_ != 0; } + int64_t first_checked_time() const { return first_checked_time_; } + void SetFirstCheckedTime(int64_t time) { + first_checked_time_ = (time > 0 ? time : 0); + } + + bool HasBackoffStartTime() const { return backoff_start_time_ > 0; } + int64_t backoff_start_time() const { return backoff_start_time_; } + void SetBackoffStartTime(int64_t time) { + backoff_start_time_ = (time > 0 ? time : 0); + } + void ClearBackoffStartTime() { backoff_start_time_ = 0; } + + bool HasBackoffDuration() const { return backoff_duration_ > 0; } + int64_t backoff_duration() const { return backoff_duration_; } + void SetBackoffDuration(int64_t duration) { + backoff_duration_ = (duration > 0 ? duration : 0); + } + void DoubleBackoffDuration() { backoff_duration_ *= 2; } + + bool HasProvisioningTime() const { return provisioning_time_ != 0; } + int64_t provisioning_time() const { return provisioning_time_; } + void SetProvisioningTime(int64_t time) { + provisioning_time_ = (time > 0 ? time : 0); + } + void ClearProvisioningTime() { provisioning_time_ = 0; } + + void Clear() { + state_ = SystemState::kUnknown; + first_checked_time_ = 0; + backoff_start_time_ = 0; + backoff_duration_ = 0; + provisioning_time_ = 0; + } + + bool operator==(const SystemFallbackInfo& other) const; + bool operator!=(const SystemFallbackInfo& other) const { + return !(*this == other); + } + + private: + SystemState state_ = SystemState::kUnknown; + int64_t first_checked_time_ = 0; + int64_t backoff_start_time_ = 0; + int64_t backoff_duration_ = 0; + int64_t provisioning_time_ = 0; +}; // class SystemFallbackInfo +} // namespace okp +} // namespace wvcdm +#endif // WVCDM_CORE_OKP_SYSTEM_INFO_H_ diff --git a/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h b/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h index 297f16b8..0bb41dd5 100644 --- a/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h +++ b/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h @@ -5,62 +5,79 @@ #define WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ #include -#include #include +#include "client_identification.h" #include "disallow_copy_and_assign.h" +#include "metrics_collections.h" #include "wv_cdm_types.h" namespace wvcdm { class CryptoSession; +namespace okp { +class SystemFallbackPolicy; +} // namespace okp -// Wrapper around an OEMCrypto system-wide OTA keybox provisioning -// workflow. +// A CdmEngine-specific OTA keybox provisioning context. class OtaKeyboxProvisioner { public: - // Creates a new OTA keybox provisioner. This should only be - // created once and object ownership belongs to the CryptoSession - // module. - static std::unique_ptr Create(); + // Creates a new OtaKeyboxProvisioner. + // Checks for the system fallback policy and if the device + // requires provisioning. + // |crypto_metrics| - CryptoMetrics instance that is used in the + // the calling EngineMetrics. + static std::unique_ptr Create( + metrics::CryptoMetrics* crypto_metrics); + static std::unique_ptr CreateForTesting( + std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy); ~OtaKeyboxProvisioner(); + // Returns true if the underlying SystemFallbackPolicy is + // provisioned. + // Note: This may change without a call to HandleProvisioningResponse() + // on this instance as provisioning is a system-wide responsibility. + bool IsProvisioned() const; + bool IsInFallbackMode() const; + + // Indicates that a request has been successfully generated. + uint32_t request_generated() const { return request_generated_; } + // Indicates that a response has been successfully received by + // this provisioner. + bool response_received() const { return response_received_; } + // === Request/response API === - // Returns true if a provisioning response has been provided - // and accepted by OEMCrytpo. - bool IsProvisioned() const { return is_provisioned_; } - - uint32_t request_count() const { return request_count_; } - uint32_t response_count() const { return response_count_; } - - // Generates an OTA provisioning request. - // Generating a request will succeed so long as OTA provisioning - // is supported and no valid response has been provided. - CdmResponseType GenerateProvisioningRequest(CryptoSession* crypto_session, - std::string* request); - - // Accepts a provisioning response from the OTA provisioning - // server. The first response which is successfully loaded is - // is used. Any subsequent response after the first successful - // response is silently discarded. - CdmResponseType HandleProvisioningResponse(CryptoSession* crypto_session, - const std::string& response); + // Generates and prepares a OTA Keybox Provisioning request, packing + // it into a SignedProvisioningMessage. + // |default_url| will be populated with the URL of the provisioning + // server used for OTA keybox provisioning. + CdmResponseType GetProvisioningRequest(std::string* request, + std::string* default_url); + // Receives, unwraps and loads the OTA Keybox Provisioning response. + // |response| must be a SignedProvisioningMessage containing an + // OTA keybox provisioning response. + CdmResponseType HandleProvisioningResponse(const std::string& response); private: - OtaKeyboxProvisioner(); + OtaKeyboxProvisioner(std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy); - bool is_provisioned_ = false; + bool Init(); - // These counters are for debugging purposes. - // Number of requests generated. - uint32_t request_count_ = 0; - // Number of responses provided. - uint32_t response_count_ = 0; + void CleanUp(); - // It is expected that multiple CDM engines may interact with the - // OtaKeyboxProvisioner instance simultaneously. - mutable std::mutex mutex_; + std::unique_ptr crypto_session_; + ClientIdentification client_id_; + + // Pointer to the system-wide okp::SystemFallbackPolicy. This class + // does not take ownership of this pointer. + okp::SystemFallbackPolicy* fallback_policy_ = nullptr; + + // These flags are for debugging purposes. + bool request_generated_ = false; + bool response_received_ = false; CORE_DISALLOW_COPY_AND_ASSIGN(OtaKeyboxProvisioner); }; // class OtaKeyboxProvisioner diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 382f54de..e176997c 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -421,6 +421,8 @@ enum CdmResponseType : int32_t { CERT_PROVISIONING_RESPONSE_ERROR_10 = 366, CLIENT_TOKEN_NOT_SET = 367, USAGE_ENTRY_ALREADY_LOADED = 368, + PARSE_OKP_RESPONSE_ERROR = 369, + OKP_ALREADY_PROVISIONED = 370, // Don't forget to add new values to // * core/test/test_printers.cpp. // * android/include/mapErrors-inl.h diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index aa96dd12..dee205f4 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -21,6 +21,7 @@ #include "device_files.h" #include "file_store.h" #include "log.h" +#include "ota_keybox_provisioner.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" @@ -119,16 +120,56 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, } LOGD("forced_session_id = %s", IdPtrToString(forced_session_id)); } + bool forced_level3 = false; + if (OkpCheck()) { + bool okp_provisioned = false; + bool fallback = false; + { + std::unique_lock lock(okp_mutex_); + if (!okp_provisioner_) { + // Very rare race condition. Possible if two calls to OpenSession + // occur the same time. Cleanup would have been performed. + if (okp_fallback_) { + fallback = true; + } else { + okp_provisioned = true; + } + } else if (okp_provisioner_->IsProvisioned()) { + okp_provisioned = true; + } else if (okp_provisioner_->IsInFallbackMode()) { + fallback = true; + } + } + if (okp_provisioned) { + // OKP not required, engine may assume normal operations. + OkpCleanUp(); + } else if (fallback) { + LOGD("Engine is falling back to L3"); + OkpTriggerFallback(); + forced_level3 = true; + } else { + // OKP is required. + return NEED_PROVISIONING; + } + } else { + std::unique_lock lock(okp_mutex_); + // |okp_fallback_| would have been set previously if required. + if (okp_fallback_) forced_level3 = true; + } CloseExpiredReleaseSessions(); std::unique_ptr new_session( new CdmSession(file_system_, metrics_->AddSession())); - const CdmResponseType sts = - new_session->Init(property_set, forced_session_id, event_listener); - if (sts == NEED_PROVISIONING) { - // Reserve a session ID so the CDM can return success. - if (session_id) *session_id = new_session->GenerateSessionId(); + const CdmResponseType sts = new_session->Init(property_set, forced_session_id, + event_listener, forced_level3); + if (sts != NO_ERROR) { + if (sts == NEED_PROVISIONING) { + // Reserve a session ID so the CDM can return success. + if (session_id) *session_id = new_session->GenerateSessionId(); + } else { + LOGE("Bad session init: status = %d", static_cast(sts)); + } return sts; } if (sts != NO_ERROR) { @@ -499,6 +540,14 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); + // Force OKP check on CryptoSession. Only concerned if engine + // has fallen back to L3. + if (security_level == kLevelDefault && OkpIsInFallbackMode()) { + LOGD("Engine is falling back to L3 for query: token = %s", + query_token.c_str()); + security_level = kLevel3; + } + // Add queries here, that can be answered before a session is opened if (query_token == QUERY_KEY_SECURITY_LEVEL) { const CdmSecurityLevel found_security_level = @@ -877,6 +926,7 @@ CdmResponseType CdmEngine::QueryOemCryptoSessionId( return session->QueryOemCryptoSessionId(query_response); } +// static bool CdmEngine::IsSecurityLevelSupported(CdmSecurityLevel level) { LOGI("level = %s", CdmSecurityLevelToString(level)); metrics::CryptoMetrics alternate_crypto_metrics; @@ -914,6 +964,42 @@ CdmResponseType CdmEngine::GetProvisioningRequest( return INVALID_PROVISIONING_REQUEST_PARAM_2; } + if (requested_security_level == kLevelDefault) { + if (OkpCheck()) { + if (okp_provisioner_->IsProvisioned()) { + // OKP not required, engine may assume normal operations. + OkpCleanUp(); + } else if (okp_provisioner_->IsInFallbackMode()) { + LOGD("Engine is falling back to L3"); + OkpTriggerFallback(); + requested_security_level = kLevel3; + } else { + // OKP is required. + const CdmResponseType status = + okp_provisioner_->GetProvisioningRequest(request, default_url); + if (status == NO_ERROR) return NO_ERROR; + if (status == NOT_IMPLEMENTED_ERROR) { + LOGW("OKP not supoprted, falling back to L3"); + OkpTriggerFallback(); + requested_security_level = kLevel3; + } else if (status == OKP_ALREADY_PROVISIONED) { + LOGD("OKP already completed, continuing in normal operation"); + OkpCleanUp(); + // Continue with normal provisioning request. + } else { + LOGE("Failed to generate OKP request: status = %d", + static_cast(status)); + return status; + } + } + } else { + std::unique_lock lock(okp_mutex_); + if (okp_fallback_) { + requested_security_level = kLevel3; + } + } + } + // TODO(b/141705730): Remove usage entries on provisioning. if (!cert_provisioning_) { cert_provisioning_.reset( @@ -959,6 +1045,32 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( cert_provisioning_.reset(); return INVALID_PROVISIONING_PARAMETERS_2; } + + if (requested_security_level == kLevelDefault) { + bool use_okp = false; + CdmResponseType okp_res = UNKNOWN_ERROR; + { + std::unique_lock lock(okp_mutex_); + if (okp_provisioner_) { + use_okp = true; + // If the engine initiated OKP previously, it must complete it + // regardless of whether the device has fallen back to L3. + okp_res = okp_provisioner_->HandleProvisioningResponse(response); + } else if (okp_fallback_) { + requested_security_level = kLevel3; + } + } + if (use_okp) { + // Cannot hold lock when calling OkpCleanUp() or OkpTriggerFallback(). + if (okp_res == NO_ERROR) { + OkpCleanUp(); + } else { + OkpTriggerFallback(); + } + return okp_res; + } + } + if (!cert_provisioning_) { // Certificate provisioning object has been released. Check if a concurrent // provisioning attempt has succeeded before declaring failure. @@ -991,6 +1103,11 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( } bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) { + LOGI("security_level = %d", static_cast(security_level)); + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } // To validate whether the given security level is provisioned, we attempt to // initialize a CdmSession. This verifies the existence of a certificate and // attempts to load it. If this fails, initialization will return an error. @@ -1008,6 +1125,10 @@ bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) { CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { LOGI("security_level = %s", CdmSecurityLevelToString(security_level)); + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } // Devices with baked-in DRM certs cannot be reprovisioned and therefore must // not be unprovisioned. std::unique_ptr crypto_session( @@ -1046,11 +1167,15 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { CdmResponseType CdmEngine::ListStoredLicenses( CdmSecurityLevel security_level, std::vector* key_set_ids) { - DeviceFiles handle(file_system_); if (!key_set_ids) { LOGE("Output |key_set_ids| is null"); return INVALID_PARAMETERS_ENG_22; } + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } + DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); return LIST_LICENSE_ERROR_1; @@ -1066,11 +1191,15 @@ CdmResponseType CdmEngine::ListUsageIds( const std::string& app_id, CdmSecurityLevel security_level, std::vector* ksids, std::vector* provider_session_tokens) { - DeviceFiles handle(file_system_); if (!ksids && !provider_session_tokens) { LOGE("Outputs |ksids| and |provider_session_tokens| are null"); return INVALID_PARAMETERS_ENG_23; } + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } + DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); return LIST_USAGE_ERROR_1; @@ -1088,6 +1217,10 @@ CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id, const std::string& key_set_id) { LOGI("app_id = %s, key_set_id = %s", IdToString(app_id), IdToString(key_set_id)); + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); @@ -1105,12 +1238,15 @@ CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id, CdmResponseType CdmEngine::GetOfflineLicenseState( const CdmKeySetId& key_set_id, CdmSecurityLevel security_level, CdmOfflineLicenseState* license_state) { + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Cannot initialize device files"); return GET_OFFLINE_LICENSE_STATE_ERROR_1; } - DeviceFiles::CdmLicenseData license_data; DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; if (!handle.RetrieveLicense(key_set_id, &license_data, &sub_error_code)) { @@ -1124,6 +1260,10 @@ CdmResponseType CdmEngine::GetOfflineLicenseState( CdmResponseType CdmEngine::RemoveOfflineLicense( const CdmKeySetId& key_set_id, CdmSecurityLevel security_level) { + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } UsagePropertySet property_set; property_set.set_security_level( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); @@ -1289,7 +1429,10 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, LOGE("Output |usage_info| is null"); return PARAMETER_NULL; } - + if (requested_security_level == kLevelDefault && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + requested_security_level = kLevel3; + } if (!usage_property_set_) { usage_property_set_.reset(new UsagePropertySet()); } @@ -1939,4 +2082,76 @@ void CdmEngine::CloseExpiredReleaseSessions() { CloseSession(*iter); } } + +bool CdmEngine::OkpCheck() { + std::unique_lock lock(okp_mutex_); + if (okp_initialized_) { + return static_cast(okp_provisioner_); + } + okp_initialized_ = true; + // Creating a CryptoSession will initialize OEMCrypto and flag the need + // for OKP. + std::unique_ptr crypto_session( + CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); + if (!crypto_session->needs_keybox_provisioning()) { + // System does not require OKP provisioning. + return false; + } + okp_provisioner_ = OtaKeyboxProvisioner::Create(metrics_->GetCryptoMetrics()); + if (!okp_provisioner_) { + LOGE("Failed to create engine OKP handler, falling back to L3"); + okp_fallback_ = true; + return false; + } + if (okp_provisioner_->IsProvisioned()) { + // This should have been caught by call to needs_keybox_provisioning(), + // but possible with simultaneous apps. + okp_provisioner_.reset(); + return false; + } + if (okp_provisioner_->IsInFallbackMode()) { + LOGD("Engine is in OKP fallback mode"); + okp_fallback_ = true; + okp_provisioner_.reset(); + return false; + } + return true; +} + +bool CdmEngine::OkpIsInFallbackMode() { + const bool check = OkpCheck(); + std::unique_lock lock(okp_mutex_); + if (!check || !okp_provisioner_ || okp_fallback_) { + return okp_fallback_; + } + if (!okp_provisioner_->IsInFallbackMode()) { + return false; + } + // Trigger fallback. + LOGD("Engine is entering OKP fallback mode"); + okp_provisioner_.reset(); + okp_fallback_ = true; + return true; +} + +void CdmEngine::OkpTriggerFallback() { + std::unique_lock lock(okp_mutex_); + if (!okp_initialized_) { + LOGD("Call to OKP fallback before OKP setup"); + return; + } + if (okp_fallback_) return; + LOGD("Engine is entering OKP fallback mode"); + okp_provisioner_.reset(); + okp_fallback_ = true; +} + +void CdmEngine::OkpCleanUp() { + std::unique_lock lock(okp_mutex_); + if (!okp_initialized_) { + LOGD("Call to OKP fallback before OKP setup"); + return; + } + okp_provisioner_.reset(); +} } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 33443f45..4120117e 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -111,19 +111,21 @@ CdmSession::~CdmSession() { CdmResponseType CdmSession::Init( CdmClientPropertySet* cdm_client_property_set) { - return Init(cdm_client_property_set, nullptr, nullptr); + return Init(cdm_client_property_set, nullptr, nullptr, false); } CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, const CdmSessionId* forced_session_id, - WvCdmEventListener* event_listener) { + WvCdmEventListener* event_listener, + bool forced_level3) { if (initialized_) { LOGE("Failed due to previous initialization"); return REINIT_ERROR; } - if (cdm_client_property_set && cdm_client_property_set->security_level() == - QUERY_VALUE_SECURITY_LEVEL_L3) { + if ((cdm_client_property_set && cdm_client_property_set->security_level() == + QUERY_VALUE_SECURITY_LEVEL_L3) || + forced_level3) { requested_security_level_ = kLevel3; security_level_ = kSecurityLevelL3; } diff --git a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp index f7fcf296..424c105a 100644 --- a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp +++ b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp @@ -51,59 +51,6 @@ const std::string kCpProductionServiceCertificate = wvcdm::a2bs_hex( "26e0c050f3fd3ebe68cef9903ef6405b25fc6e31f93559fcff05657662b3653a" "8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb" "1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28"); - -/* - * Provisioning response is a base64-encoded protobuf, optionally within a - * JSON wrapper. If the JSON wrapper is present, extract the embedded response - * message. Then perform the base64 decode and return the result. - * - * If an error occurs during the parse or the decode, return an empty string. - */ -bool ExtractAndDecodeSignedMessage(const std::string& provisioning_response, - std::string* result) { - const std::string json_start_substr("\"signedResponse\": \""); - const std::string json_end_substr("\""); - std::string message_string; - - if (result == nullptr) { - LOGE("Output parameter |result| is not provided"); - return false; - } - - size_t start = provisioning_response.find(json_start_substr); - - if (start == provisioning_response.npos) { - // Message is not properly wrapped - reject it. - LOGE("Cannot locate start substring"); - result->clear(); - return false; - } - - // Appears to be JSON-wrapped protobuf - find end of protobuf portion. - const size_t end = provisioning_response.find( - json_end_substr, start + json_start_substr.length()); - if (end == provisioning_response.npos) { - LOGE("Cannot locate end substring"); - result->clear(); - return false; - } - - size_t b64_string_size = end - start - json_start_substr.length(); - message_string.assign(provisioning_response, - start + json_start_substr.length(), b64_string_size); - - if (message_string.empty()) { - LOGE("CDM provisioning response is empty"); - result->clear(); - return false; - } - - // Decode the base64-encoded message. - const std::vector decoded_message = - wvcdm::Base64SafeDecode(message_string); - result->assign(decoded_message.begin(), decoded_message.end()); - return true; -} } // namespace // Protobuf generated classes. using video_widevine::ClientIdentification_ClientCapabilities; @@ -497,10 +444,56 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( return NO_ERROR; } -// Static -bool CertificateProvisioning::ExtractAndDecodeSignedMessageForTesting( +// Provisioning response is a base64-encoded protobuf, optionally within a +// JSON wrapper. If the JSON wrapper is present, extract the embedded response +// message. Then perform the base64 decode and return the result. +// +// If an error occurs during the parse or the decode, return an empty string. +// static +bool CertificateProvisioning::ExtractAndDecodeSignedMessage( const std::string& provisioning_response, std::string* result) { - return ExtractAndDecodeSignedMessage(provisioning_response, result); + const std::string json_start_substr("\"signedResponse\": \""); + const std::string json_end_substr("\""); + std::string message_string; + + if (result == nullptr) { + LOGE("Output parameter |result| is not provided"); + return false; + } + + size_t start = provisioning_response.find(json_start_substr); + + if (start == provisioning_response.npos) { + // Message is not properly wrapped - reject it. + LOGE("Cannot locate start substring"); + result->clear(); + return false; + } + + // Appears to be JSON-wrapped protobuf - find end of protobuf portion. + const size_t end = provisioning_response.find( + json_end_substr, start + json_start_substr.length()); + if (end == provisioning_response.npos) { + LOGE("Cannot locate end substring"); + result->clear(); + return false; + } + + size_t b64_string_size = end - start - json_start_substr.length(); + message_string.assign(provisioning_response, + start + json_start_substr.length(), b64_string_size); + + if (message_string.empty()) { + LOGE("CDM provisioning response is empty"); + result->clear(); + return false; + } + + // Decode the base64-encoded message. + const std::vector decoded_message = + wvcdm::Base64SafeDecode(message_string); + result->assign(decoded_message.begin(), decoded_message.end()); + return true; } bool CertificateProvisioning::ExtractDeviceInfo( diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 7249f9dd..c6b1b16c 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -20,7 +20,7 @@ #include "crypto_key.h" #include "entitlement_key_session.h" #include "log.h" -#include "ota_keybox_provisioner.h" +#include "okp_fallback_policy.h" #include "platform.h" #include "privacy_crypto.h" #include "properties.h" @@ -179,7 +179,8 @@ std::unique_ptr CryptoSession::usage_table_header_l1_; std::unique_ptr CryptoSession::usage_table_header_l3_; std::recursive_mutex CryptoSession::usage_table_mutex_; std::atomic CryptoSession::request_id_index_source_(0); -std::unique_ptr CryptoSession::ota_keybox_provisioner_l1_; +std::unique_ptr + CryptoSession::okp_fallback_policy_l1_; size_t GetOffset(std::string message, std::string field) { size_t pos = message.find(field); @@ -362,13 +363,15 @@ void CryptoSession::Init() { LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(), api_minor_version.c_str()); if (needs_keybox_provisioning_) { - WithStaticFieldWriteLock("OtaKeyboxProvisioner", [&] { - if (!ota_keybox_provisioner_l1_) { + WithStaticFieldWriteLock("SystemFallbackPolicy", [&] { + if (!okp_fallback_policy_l1_) { LOGD("OEMCrypto needs keybox provisioning"); // Only create once. Possible that OEMCrypto is initialized // and terminated many times over the life cycle of the OTA // keybox provisioning process. - ota_keybox_provisioner_l1_ = OtaKeyboxProvisioner::Create(); + okp_fallback_policy_l1_ = okp::SystemFallbackPolicy::Create(); + if (okp_fallback_policy_l1_) + okp_fallback_policy_l1_->MarkNeedsProvisioning(); } }); } @@ -3023,16 +3026,16 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( return sts; } -OtaKeyboxProvisioner* CryptoSession::GetOtaKeyboxProvisioner() { - const auto getter = [&]() -> OtaKeyboxProvisioner* { +okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() { + const auto getter = [&]() -> okp::SystemFallbackPolicy* { // If not set, then OTA keybox provisioning is not supported or // not needed. - if (!ota_keybox_provisioner_l1_) return nullptr; + if (!okp_fallback_policy_l1_) return nullptr; // May have already been initialized. - if (ota_keybox_provisioner_l1_->IsProvisioned()) return nullptr; - return ota_keybox_provisioner_l1_.get(); + if (okp_fallback_policy_l1_->IsProvisioned()) return nullptr; + return okp_fallback_policy_l1_.get(); }; - return WithStaticFieldReadLock("GetOtaKeyboxProvisioner", getter); + return WithStaticFieldReadLock("GetOkpFallbackPolicy", getter); } CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( diff --git a/libwvdrmengine/cdm/core/src/device_files.cpp b/libwvdrmengine/cdm/core/src/device_files.cpp index 3871af80..979d287d 100644 --- a/libwvdrmengine/cdm/core/src/device_files.cpp +++ b/libwvdrmengine/cdm/core/src/device_files.cpp @@ -95,6 +95,7 @@ const char kTrue[] = "true"; const char kUsageInfoFileNameExt[] = ".bin"; const char kUsageInfoFileNamePrefix[] = "usage"; const char kUsageTableFileName[] = "usgtable.bin"; +const char kOkpInfoFileName[] = "okp.bin"; const char kWildcard[] = "*"; // TODO(b/192430982): Renable expiration of legacy DRM certificates // constexpr int64_t kFourMonthsInSeconds = (2 * 30 + 2 * 31) * 24 * 60 * 60; @@ -1627,6 +1628,164 @@ bool DeviceFiles::HasCertificate(CertificateType certificate_type) { return FileExists(certificate_file_name); } +bool DeviceFiles::StoreOkpInfo(const okp::SystemFallbackInfo& info) { + using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo; + using okp::SystemState; + RETURN_FALSE_IF_UNINITIALIZED(); + if (security_level_ != kSecurityLevelL1) { + LOGE("OKP info is only supported by L1: level = %d", + static_cast(security_level_)); + return false; + } + video_widevine_client::sdk::File file; + file.set_type(video_widevine_client::sdk::File::OKP_INFO); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + StoredOkpInfo* stored_info = file.mutable_okp_info(); + switch (info.state()) { + case SystemState::kNeedsProvisioning: + stored_info->set_state(StoredOkpInfo::OKP_NEEDS_PROVISIONING); + break; + case SystemState::kFallbackMode: + stored_info->set_state(StoredOkpInfo::OKP_FALLBACK_MODE); + break; + case SystemState::kProvisioned: + stored_info->set_state(StoredOkpInfo::OKP_PROVISIONED); + break; + case SystemState::kUnknown: + default: + LOGE("Unexpected OKP state: state = %d", static_cast(info.state())); + return false; + } + if (info.first_checked_time() <= 0) { + LOGE("OKP first checked time is missing"); + return false; + } + stored_info->set_first_checked_time(info.first_checked_time()); + + if (info.state() == SystemState::kProvisioned) { + if (!info.HasProvisioningTime()) { + LOGE("OKP set as provisioned, but missing provisioning time"); + return false; + } + stored_info->set_provisioning_time(info.provisioning_time()); + } else if (info.state() == SystemState::kFallbackMode) { + if (!info.HasBackoffStartTime() || !info.HasBackoffDuration()) { + LOGE("OKP fallback information is missing "); + return false; + } + stored_info->set_backoff_start_time(info.backoff_start_time()); + stored_info->set_backoff_duration(info.backoff_duration()); + } else { + if (info.HasBackoffDuration()) { + // Store backoff duration from before. + stored_info->set_backoff_duration(info.backoff_duration()); + } + } + + std::string serialized_file; + file.SerializeToString(&serialized_file); + return StoreFileWithHash(GetOkpInfoFileName(), serialized_file) == kNoError; +} + +bool DeviceFiles::RetrieveOkpInfo(okp::SystemFallbackInfo* info) { + using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo; + using okp::SystemState; + RETURN_FALSE_IF_UNINITIALIZED(); + RETURN_FALSE_IF_NULL(info); + info->Clear(); + if (security_level_ != kSecurityLevelL1) { + LOGE("OKP info is only supported by L1: level = %d", + static_cast(security_level_)); + return false; + } + // File meta-data validation. + video_widevine_client::sdk::File file; + if (RetrieveHashedFile(GetOkpInfoFileName(), &file) != kNoError) { + LOGE("Unable to retrieve OKP info file"); + return false; + } + if (file.type() != video_widevine_client::sdk::File::OKP_INFO) { + LOGE("Incorrect file type: type = %d, expected_type = %d", + static_cast(file.type()), + static_cast(video_widevine_client::sdk::File::OKP_INFO)); + return false; + } + if (file.version() != video_widevine_client::sdk::File::VERSION_1) { + LOGE("Incorrect file version: version = %d, expected_version = %d", + static_cast(file.version()), + static_cast(video_widevine_client::sdk::File::VERSION_1)); + return false; + } + if (!file.has_okp_info()) { + // OKP info is only stored if at least 1 field is non-empty. This + // must be an error. + LOGD("OKP info is not present in file"); + return false; + } + + const StoredOkpInfo& stored_info = file.okp_info(); + switch (stored_info.state()) { + case StoredOkpInfo::OKP_NEEDS_PROVISIONING: + info->SetState(SystemState::kNeedsProvisioning); + break; + case StoredOkpInfo::OKP_FALLBACK_MODE: + info->SetState(SystemState::kFallbackMode); + break; + case StoredOkpInfo::OKP_PROVISIONED: + info->SetState(SystemState::kProvisioned); + break; + case StoredOkpInfo::OKP_UNKNOWN: + default: + LOGE("Unexpected OKP state: stored_state = %d", + static_cast(stored_info.state())); + return false; + } + + if (stored_info.first_checked_time() <= 0) { + LOGE("OKP first check time not present"); + info->Clear(); + return false; + } + info->SetFirstCheckedTime(stored_info.first_checked_time()); + + if (info->state() == SystemState::kProvisioned) { + if (stored_info.provisioning_time() <= 0) { + LOGE("OKP set as provisioned, but missing provisioning time"); + info->Clear(); + return false; + } + info->SetProvisioningTime(stored_info.provisioning_time()); + return true; + } + + if (info->state() == SystemState::kFallbackMode) { + if (stored_info.backoff_start_time() <= 0 || + stored_info.backoff_duration() <= 0) { + LOGE("OKP backoff information is missing"); + info->Clear(); + return false; + } + info->SetBackoffStartTime(stored_info.backoff_start_time()); + info->SetBackoffDuration(stored_info.backoff_duration()); + return true; + } + // Provisioned. + if (stored_info.backoff_duration() > 0) { + info->SetBackoffDuration(stored_info.backoff_duration()); + } + return true; +} + +bool DeviceFiles::DeleteOkpInfo() { + RETURN_FALSE_IF_UNINITIALIZED(); + if (security_level_ != kSecurityLevelL1) { + LOGE("OKP info is only supported by L1: level = %d", + static_cast(security_level_)); + return false; + } + return RemoveFile(GetOkpInfoFileName()); +} + DeviceFiles::ResponseType DeviceFiles::StoreFileWithHash( const std::string& name, const std::string& serialized_file) { std::string hash = Sha256Hash(serialized_file); @@ -1845,6 +2004,8 @@ std::string DeviceFiles::GetUsageInfoFileName(const std::string& app_id) { return kUsageInfoFileNamePrefix + hash + kUsageInfoFileNameExt; } +std::string DeviceFiles::GetOkpInfoFileName() { return kOkpInfoFileName; } + std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) { return Base64SafeEncode(Md5Hash(input)); } diff --git a/libwvdrmengine/cdm/core/src/device_files.proto b/libwvdrmengine/cdm/core/src/device_files.proto index 98f15701..76266813 100644 --- a/libwvdrmengine/cdm/core/src/device_files.proto +++ b/libwvdrmengine/cdm/core/src/device_files.proto @@ -130,42 +130,6 @@ message UsageTableInfo { // OKP should create this file. Otherwise, this information is not // needed. message OtaKeyboxProvisioningInfo { - // Engine-specific information about OKP. - message OkpEngineInfo { - // Engine identifier. - optional bytes app_id = 1; - optional bytes origin = 2; - reserved 3 to 5; // Reserved for future engine composite keys. - // Counters for engine-specific OKP events. - // These counters are reset after a certain amount of time - // (OKP period) since the last event. - // Number of calls to openSession() where it is recommended - // to the app to try keybox provisioning. - optional uint32 try_okp_counter = 6; - // Number of calls to getProvisionRequest(). - optional uint32 generate_request_counter = 7; - // Number of failed calls to provideProvisionRequest(). - optional uint32 failed_response_counter = 8; - - // The value of |last_event_time| and |backoff_start_time| are set - // using the system's wall-clock in epoch seconds. A value of - // zero indicates it's not set. - - // Time of the last engine OKP event (change of the above counters; - // the beginning of the current OKP period). - // Zero indicates no event has yet occurred. - optional int64 last_event_time = 9; - // Beginning of an app/origin backoff period. - // Zero indicates that engine is not in a backoff state. - optional int64 backoff_start_time = 10; - // Intended length of “backoff period”. This will be assigned a - // random duration initially, then double each time an engine - // enters a backoff state. This is base on Google's recommended - // exponential backoff rules. - // Value of 0 indicates that backoff has not yet occurred. - optional int64 backoff_duration = 11; - } - enum OkpDeviceState { // Not yet checked for provisioning state. This should be a // transitory state only. Device which do not need OTA Keybox @@ -175,19 +139,30 @@ message OtaKeyboxProvisioningInfo { // that the device supports OKP. Device may or may not be in the // process of performing provisioning. OKP_NEEDS_PROVISIONING = 1; + // Device still needs provisioning, but has reached a condition + // where it should backoff from attempting OKP for a period of + // time. + OKP_FALLBACK_MODE = 2; // The device has successfully provisioned its keybox. - OKP_PROVISIONED = 2; + OKP_PROVISIONED = 3; } // Device-wide OKP state. optional OkpDeviceState state = 1; // Time when the CDM service first discovers that it needs to // provision the L1 keybox. optional int64 first_checked_time = 2; + // Beginning of a backoff period. + // Zero indicates that engine is not in a backoff state. + optional int64 backoff_start_time = 3; + // Intended length of “backoff period”. This will be assigned a + // random duration initially, then double each time an engine enters + // a backoff state. This is based on Google's recommended exponential + // backoff rules. + // Value of 0 indicates that backoff has not yet occurred. + optional int64 backoff_duration = 4; // System time of when a successful provisioning request has been // received. Only relevant if |state| is OKP_PROVISIONED. - optional int64 provisioning_time = 3; - // A list of all records for each identifiable engine. - repeated OkpEngineInfo engine_infos = 4; + optional int64 provisioning_time = 5; } message File { diff --git a/libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp b/libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp new file mode 100644 index 00000000..4e6505e6 --- /dev/null +++ b/libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp @@ -0,0 +1,208 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "okp_fallback_policy.h" + +#include + +#include "cdm_random.h" +#include "device_files.h" +#include "file_store.h" +#include "log.h" +#include "wv_cdm_types.h" + +namespace wvcdm { +namespace okp { +using UniqueLock = std::unique_lock; +namespace { +constexpr int64_t kErrorTime = -1; + +int64_t GenerateInitialBackoffDuration() { + return static_cast(CdmRandom::RandomInRange( + kMinInitialBackoffDuration, kMaxInitialBackoffDuration)); +} +} // namespace + +// static +std::unique_ptr SystemFallbackPolicy::Create() { + std::unique_ptr fs(new FileSystem()); + std::unique_ptr device_files(new DeviceFiles(fs.get())); + if (!device_files->Init(kSecurityLevelL1)) { + LOGE("Failed to initialize device files"); + return nullptr; + } + std::unique_ptr policy(new SystemFallbackPolicy()); + policy->fs_ = std::move(fs); + policy->device_files_ = std::move(device_files); + policy->TryRestore(); + return policy; +} + +// static +std::unique_ptr SystemFallbackPolicy::CreateForTesting( + Clock* clock) { + std::unique_ptr policy(new SystemFallbackPolicy()); + if (clock != nullptr) { + policy->SetClockForTesting(clock); + } + // Device files are not supported for test instances. + return policy; +} + +// static +std::unique_ptr SystemFallbackPolicy::CreateForTesting( + const SystemFallbackInfo& info, Clock* clock) { + std::unique_ptr policy(new SystemFallbackPolicy()); + if (clock != nullptr) { + policy->SetClockForTesting(clock); + } + policy->info_ = info; + // Device files are not supported for test instances. + return policy; +} + +SystemFallbackPolicy::SystemFallbackPolicy() : clock_(), clock_ref_(&clock_) {} + +SystemFallbackPolicy::~SystemFallbackPolicy() { StoreInfo(); } + +void SystemFallbackPolicy::TryRestore() { + if (!device_files_->RetrieveOkpInfo(&info_)) { + info_.Clear(); + return; + } + LOGI("Restored OKP info: state = %s", + okp::SystemStateToString(info_.state())); + // Calling will end fallback mode if the backoff period has elapsed. + IsInFallbackMode(); +} + +void SystemFallbackPolicy::StoreInfo() { + if (IsTestMode()) { + // Testing instances may not set |device_files_|. + LOGV("Test instance, not storing"); + return; + } + device_files_->StoreOkpInfo(info_); +} + +// Can enter kNeedsProvisioning state from any other state other than +// kFallbackMode. +void SystemFallbackPolicy::MarkNeedsProvisioning() { + UniqueLock lock(mutex_); + switch (state()) { + case SystemState::kNeedsProvisioning: + case SystemState::kFallbackMode: + return; // Nothing to do. + case SystemState::kProvisioned: + case SystemState::kUnknown: + break; + default: + // Should not happen, but if it does continue anyways. + LOGW("Unknown state: %d", static_cast(state())); + } + info_.Clear(); + info_.SetFirstCheckedTime(GetCurrentTime()); + info_.SetState(SystemState::kNeedsProvisioning); + StoreInfo(); +} + +// Can only enter kFallbackMode if in state kNeedsProvisioning +void SystemFallbackPolicy::TriggerFallback() { + UniqueLock lock(mutex_); + switch (state()) { + case SystemState::kNeedsProvisioning: + case SystemState::kUnknown: // Should not happen. + break; // OK to fallback + case SystemState::kFallbackMode: + // Already in fallback mode. Expected if there are multiple + // engines are attempting OKP and fail for the same reason. + return; + case SystemState::kProvisioned: { + LOGW("Cannot fallback, already provisioned"); + return; + } + default: + LOGE("Unexpected state: %d", static_cast(state())); + return; + } + info_.SetState(SystemState::kFallbackMode); + const int64_t current_time = GetCurrentTime(); + if (!info_.HasFirstCheckedTime()) { + info_.SetFirstCheckedTime(current_time); + } + info_.SetBackoffStartTime(GetCurrentTime()); + if (info_.HasBackoffDuration()) { + // Doubling backoff duration for exponential backoff. + info_.DoubleBackoffDuration(); + } else { + // Use a random backoff period to avoid server spam across all devices. + info_.SetBackoffDuration(GenerateInitialBackoffDuration()); + } + StoreInfo(); +} + +void SystemFallbackPolicy::MarkProvisioned() { + UniqueLock lock(mutex_); + if (state() == SystemState::kProvisioned) return; + info_.SetState(SystemState::kProvisioned); + const int64_t current_time = GetCurrentTime(); + if (!info_.HasFirstCheckedTime()) { + info_.SetFirstCheckedTime(current_time); + } + info_.SetProvisioningTime(current_time); + info_.ClearBackoffStartTime(); + StoreInfo(); +} + +bool SystemFallbackPolicy::IsProvisioned() { + UniqueLock lock(mutex_); + return state() == SystemState::kProvisioned; +} + +bool SystemFallbackPolicy::IsInFallbackMode() { + UniqueLock lock(mutex_); + if (state() != SystemState::kFallbackMode) return false; + // Check if fallback period has ended. + const int64_t backoff_duration = info_.backoff_duration(); + const int64_t current_backoff_length = GetSecondsSinceBackoffStart(); + if (backoff_duration == 0 || current_backoff_length == kErrorTime) { + // Possible error condition if device files and/or system clock is + // modified. + LOGE( + "Unexpected backoff state, ending backoff: backoff_duration = " + "%" PRId64 ", current_backoff_length = %" PRId64, + backoff_duration, current_backoff_length); + EndBackoffPeriod(); + } else if (current_backoff_length < backoff_duration) { + // Still in backoff period, resume fallback mode. + return true; + } else { + // Backoff period has ended, may attempt OKP again. + LOGD("Backoff period has ended"); + EndBackoffPeriod(); + } + StoreInfo(); + return false; // Only stored if previously in fallback and has ended. +} + +int64_t SystemFallbackPolicy::GetSecondsSinceBackoffStart() const { + if (!info_.HasBackoffStartTime()) return 0; + const int64_t backoff_start_time = info_.backoff_start_time(); + const int64_t current_time = GetCurrentTime(); + if (current_time < backoff_start_time) { + LOGE("Current time is less than start of backoff"); + return kErrorTime; + } + return current_time - backoff_start_time; +} + +void SystemFallbackPolicy::EndBackoffPeriod() { + info_.SetState(SystemState::kNeedsProvisioning); + info_.ClearBackoffStartTime(); +} + +bool SystemFallbackPolicy::IsTestMode() const { + return !static_cast(device_files_); +} +} // namespace okp +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/okp_info.cpp b/libwvdrmengine/cdm/core/src/okp_info.cpp new file mode 100644 index 00000000..30edfb4e --- /dev/null +++ b/libwvdrmengine/cdm/core/src/okp_info.cpp @@ -0,0 +1,32 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "okp_info.h" + +namespace wvcdm { +namespace okp { +const char* SystemStateToString(SystemState state) { + switch (state) { + case SystemState::kNeedsProvisioning: + return "NeedsProvisioning"; + case SystemState::kFallbackMode: + return "FallbackMode"; + case SystemState::kProvisioned: + return "Provisioned"; + case SystemState::kUnknown: + default: + return "Unknown"; + } +} + +bool SystemFallbackInfo::operator==(const SystemFallbackInfo& other) const { + if (this == &other) return true; + if (state_ != other.state_) return false; + if (first_checked_time_ != other.first_checked_time_) return false; + if (backoff_start_time_ != other.backoff_start_time_) return false; + if (backoff_duration_ != other.backoff_duration_) return false; + if (provisioning_time_ != other.provisioning_time_) return false; + return true; +} +} // namespace okp +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp b/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp index e88109a8..180ab424 100644 --- a/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp +++ b/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp @@ -3,81 +3,272 @@ // Agreement. #include "ota_keybox_provisioner.h" +#include + #include +#include "certificate_provisioning.h" #include "crypto_session.h" +#include "license_protocol.pb.h" #include "log.h" +#include "okp_fallback_policy.h" +#include "properties.h" #include "string_conversions.h" namespace wvcdm { -using UniqueLock = std::unique_lock; +using video_widevine::ProvisioningRequest; +using video_widevine::ProvisioningResponse; +using video_widevine::SignedProvisioningMessage; +using OtaRequest = ProvisioningRequest::AndroidAttestationOtaKeyboxRequest; +using OtaResponse = ProvisioningResponse::AndroidAttestationOtaKeyboxResponse; namespace { // Indicates not to use the test keybox for OTA provisioning. constexpr bool kProductionKeybox = false; +const CdmAppParameterMap kEmptyAppParameters; +const std::string kEmptyString; } // namespace // static -std::unique_ptr OtaKeyboxProvisioner::Create() { - return std::unique_ptr(new OtaKeyboxProvisioner()); +std::unique_ptr OtaKeyboxProvisioner::Create( + metrics::CryptoMetrics* crypto_metrics) { + if (crypto_metrics == nullptr) { + LOGE("Input |crypto_metrics| is null"); + return nullptr; + } + // Get system fallback policy. + okp::SystemFallbackPolicy* fallback_policy = + CryptoSession::GetOkpFallbackPolicy(); + if (fallback_policy == nullptr) { + LOGE("No system fallback policy"); + return nullptr; + } + // Setup crypto session. + std::unique_ptr crypto_session( + CryptoSession::MakeCryptoSession(crypto_metrics)); + crypto_session->Open(kLevelDefault); + if (!crypto_session->IsOpen()) { + LOGE("Failed to open crypto session for OKP provisioner"); + return nullptr; + } + const CdmSecurityLevel security_level = crypto_session->GetSecurityLevel(); + if (security_level != kSecurityLevelL1) { + LOGE("Failed to open L1 crypto session: security_level = %d", + static_cast(security_level)); + crypto_session.reset(); + return nullptr; + } + std::unique_ptr engine_provisioner( + new OtaKeyboxProvisioner(std::move(crypto_session), fallback_policy)); + if (!engine_provisioner->Init()) { + LOGE("Failed to initialize OKP provisioner"); + return nullptr; + } + return engine_provisioner; } -OtaKeyboxProvisioner::OtaKeyboxProvisioner() : mutex_() {} - -OtaKeyboxProvisioner::~OtaKeyboxProvisioner() {} - -CdmResponseType OtaKeyboxProvisioner::GenerateProvisioningRequest( - CryptoSession* crypto_session, std::string* request) { - if (crypto_session == nullptr) { - LOGE("Input |crypto_session| is null"); - return PARAMETER_NULL; +// static +std::unique_ptr OtaKeyboxProvisioner::CreateForTesting( + std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy) { + if (!crypto_session) { + LOGE("Input |crypto_metrics| is null"); + return nullptr; } + if (fallback_policy == nullptr) { + LOGE("Input |fallback_policy| is null"); + return nullptr; + } + std::unique_ptr engine_provisioner( + new OtaKeyboxProvisioner(std::move(crypto_session), fallback_policy)); + if (!engine_provisioner->Init()) { + LOGE("Failed to initialize OKP provisioner"); + return nullptr; + } + return engine_provisioner; +} + +OtaKeyboxProvisioner::OtaKeyboxProvisioner( + std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy) + : crypto_session_(std::move(crypto_session)), + fallback_policy_(fallback_policy) { + assert(static_cast(crypto_session_)); + assert(fallback_policy != nullptr); +} + +OtaKeyboxProvisioner::~OtaKeyboxProvisioner() { + crypto_session_.reset(); + fallback_policy_ = nullptr; +} + +bool OtaKeyboxProvisioner::Init() { + const CdmResponseType result = + client_id_.InitForOtaKeyboxProvisioning(crypto_session_.get()); + if (result != NO_ERROR) { + LOGE("Failed to initialize OKP client ID"); + return false; + } + return true; +} + +bool OtaKeyboxProvisioner::IsProvisioned() const { + return fallback_policy_->IsProvisioned(); +} + +bool OtaKeyboxProvisioner::IsInFallbackMode() const { + return fallback_policy_->IsInFallbackMode(); +} + +CdmResponseType OtaKeyboxProvisioner::GetProvisioningRequest( + std::string* request, std::string* default_url) { if (request == nullptr) { LOGE("Output |request| is null"); return PARAMETER_NULL; } - UniqueLock lock(mutex_); - // Do not generate new requests if already provisioned. + if (default_url == nullptr) { + LOGE("Output |default_url| is null"); + return PARAMETER_NULL; + } if (IsProvisioned()) { LOGW("Already provisioned"); - // TODO(sigquit): Use a response code that will indicate to the - // caller that the system is already provisioned. + CleanUp(); + return OKP_ALREADY_PROVISIONED; + } + if (!crypto_session_) { + LOGE("Crypto session has been released, OKP unavailable"); + // Caller should not reuse provisioner after failure. return UNKNOWN_ERROR; } - const CdmResponseType result = - crypto_session->PrepareOtaProvisioningRequest(kProductionKeybox, request); - if (result != NO_ERROR) { + // Step 1: Generate raw request from OEMCrypto. + std::string ota_request_data; + CdmResponseType result = crypto_session_->PrepareOtaProvisioningRequest( + kProductionKeybox, &ota_request_data); + if (result == NOT_IMPLEMENTED_ERROR) { + LOGW("OKP is not supported by OEMCrypto"); + fallback_policy_->TriggerFallback(); + CleanUp(); + return result; + } else if (result != NO_ERROR) { + LOGE("Failed to generate OKP request: status = %d", + static_cast(result)); return result; } - LOGV("OTA request generated: request = %s", b2a_hex(*request).c_str()); - request_count_++; + // Step 2: Wrap in ProvisioningRequest. + ProvisioningRequest prov_request; + auto* client_id = prov_request.mutable_client_id(); + result = client_id_.Prepare(kEmptyAppParameters, kEmptyString, client_id); + if (result != NO_ERROR) { + LOGW("Failed to prepare client ID, continuing without: result = %d", + static_cast(result)); + client_id->Clear(); + } + OtaRequest* ota_request = prov_request.mutable_android_ota_keybox_request(); + ota_request->set_ota_request(ota_request_data); + + // Step 3: Wrap in SignedProvisioningMessage. + SignedProvisioningMessage signed_request; + std::string message; + prov_request.SerializeToString(&message); + signed_request.set_message(message); + signed_request.set_provisioning_type( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA); + signed_request.SerializeToString(request); + + if (!wvcdm::Properties::provisioning_messages_are_binary()) { + *request = Base64SafeEncodeNoPad( + std::vector(request->begin(), request->end())); + } + + request_generated_ = true; + CertificateProvisioning::GetProvisioningServerUrl(default_url); return NO_ERROR; } CdmResponseType OtaKeyboxProvisioner::HandleProvisioningResponse( - CryptoSession* crypto_session, const std::string& response) { - if (crypto_session == nullptr) { - LOGE("Input |crypto_session| is null"); - return PARAMETER_NULL; - } + const std::string& response) { if (response.empty()) { - LOGE("OTA provisioning response is empty"); + LOGE("Signed provisioning message is empty"); return EMPTY_PROVISIONING_RESPONSE; } - UniqueLock lock(mutex_); if (IsProvisioned()) { - // Response already received, silently dropping. - response_count_++; + LOGD("Already provisioned"); + response_received_ = true; + CleanUp(); return NO_ERROR; } - const CdmResponseType result = - crypto_session->LoadOtaProvisioning(kProductionKeybox, response); - if (result != NO_ERROR) { - return result; + if (!request_generated_) { + LOGE("Received response without generating request"); + return UNKNOWN_ERROR; + } + if (!crypto_session_) { + LOGE("Crypto session has been released, OKP unavailable"); + // Caller should not reuse provisioner after failure. + return UNKNOWN_ERROR; + } + std::string decoded_response; + if (!wvcdm::Properties::provisioning_messages_are_binary()) { + if (!CertificateProvisioning::ExtractAndDecodeSignedMessage( + response, &decoded_response)) { + LOGE("Failed to extract OKP provisioning response"); + return PARSE_OKP_RESPONSE_ERROR; + } + } else { + decoded_response = response; + } + // Step 1: Unwrap from SignedProvisioningMessage; + SignedProvisioningMessage signed_response; + if (!signed_response.ParseFromString(decoded_response)) { + LOGE("Failed to parse SignedProvisioningMessage"); + return PARSE_OKP_RESPONSE_ERROR; + } + if (!signed_response.has_message()) { + LOGE("Signed response is missing message"); + return PARSE_OKP_RESPONSE_ERROR; + } + if (signed_response.provisioning_type() != + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA) { + LOGE("Unexpected protocol type/version: protocol_type = %d", + static_cast(signed_response.provisioning_type())); + return PARSE_OKP_RESPONSE_ERROR; + } + // Step 2: Unwrap from ProvisioningResponse. + ProvisioningResponse prov_response; + if (!prov_response.ParseFromString(signed_response.message())) { + LOGE("Failed to parse ProvisioningResponse"); + return PARSE_OKP_RESPONSE_ERROR; + } + if (!prov_response.has_android_ota_keybox_response()) { + LOGE("Missing OTA keybox response message"); + return PARSE_OKP_RESPONSE_ERROR; + } + const OtaResponse& ota_response = prov_response.android_ota_keybox_response(); + if (!ota_response.has_ota_response()) { + LOGE("Missing OTA keybox response data"); + return PARSE_OKP_RESPONSE_ERROR; + } + // Step 3: Load response. + const std::string ota_response_data = ota_response.ota_response(); + if (ota_response_data.empty()) { + LOGE("Raw OTA response is empty"); + return PARSE_OKP_RESPONSE_ERROR; + } + const CdmResponseType result = crypto_session_->LoadOtaProvisioning( + kProductionKeybox, ota_response_data); + if (result == NO_ERROR) { + LOGV("OTA response successfully processed: ota_response_data = %s", + b2a_hex(ota_response_data).c_str()); + fallback_policy_->MarkProvisioned(); + response_received_ = true; + } else { + fallback_policy_->TriggerFallback(); + } + CleanUp(); + return result; +} + +void OtaKeyboxProvisioner::CleanUp() { + if (crypto_session_) { + crypto_session_.reset(); } - LOGD("OTA response successfully processed: response = %s", - b2a_hex(response).c_str()); - is_provisioned_ = true; - response_count_ = 1; - return NO_ERROR; } } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp index 2aab53f0..2f735aa7 100644 --- a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp @@ -15,6 +15,7 @@ #include "cdm_random.h" #include "crypto_wrapped_key.h" #include "file_store.h" +#include "okp_info.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" @@ -3785,6 +3786,7 @@ using ::testing::Expectation; using ::testing::Gt; using ::testing::HasSubstr; using ::testing::InSequence; +using ::testing::Invoke; using ::testing::InvokeWithoutArgs; using ::testing::NotNull; using ::testing::Return; @@ -4882,6 +4884,110 @@ TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) { } } +// OKP info can only be stored on L1 device files. +TEST_F(DeviceFilesTest, OkpInfo_L1Only) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL3)); + + okp::SystemFallbackInfo info; + info.SetState(okp::SystemState::kNeedsProvisioning); + info.SetFirstCheckedTime(1234); + + const std::string kErrorMessage = "OKP should not be available on L3"; + EXPECT_FALSE(device_files.StoreOkpInfo(info)) << kErrorMessage; + EXPECT_FALSE(device_files.RetrieveOkpInfo(&info)) << kErrorMessage; + EXPECT_FALSE(device_files.DeleteOkpInfo()) << kErrorMessage; +} + +// Uninitialized info cannot be stored. +TEST_F(DeviceFilesTest, OkpInfo_UninitializedInfo) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + okp::SystemFallbackInfo info; // Uninitialized. + EXPECT_FALSE(device_files.StoreOkpInfo(info)); +} + +TEST_F(DeviceFilesTest, OkpInfo_FileDoesNotExist) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + const std::string kOkpInfoPath = + device_base_path_ + DeviceFiles::GetOkpInfoFileName(); + EXPECT_CALL(file_system, Exists(kOkpInfoPath)).WillOnce(Return(false)); + + okp::SystemFallbackInfo info; + EXPECT_FALSE(device_files.RetrieveOkpInfo(&info)); +} + +TEST_F(DeviceFilesTest, OkpInfo_RetrieveWithNull) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + EXPECT_FALSE(device_files.RetrieveOkpInfo(nullptr)); +} + +TEST_F(DeviceFilesTest, OkpInfo_DeleteFile) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + + // L1 - Should succeed. + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + const std::string kOkpInfoPath = + device_base_path_ + DeviceFiles::GetOkpInfoFileName(); + EXPECT_CALL(file_system, Remove(kOkpInfoPath)).WillOnce(Return(true)); + EXPECT_TRUE(device_files.DeleteOkpInfo()); + + // L3 - Should fail. + EXPECT_TRUE(device_files.Init(kSecurityLevelL3)); + EXPECT_FALSE(device_files.DeleteOkpInfo()); +} + +TEST_F(DeviceFilesTest, OkpInfo_StoreAndRetrieve) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + // Prepare data. + okp::SystemFallbackInfo info; + info.SetState(okp::SystemState::kFallbackMode); + info.SetFirstCheckedTime(1234); + info.SetBackoffStartTime(2345); + info.SetBackoffDuration(1111); + + // Set store expectations. + const std::string kOkpInfoPath = + device_base_path_ + DeviceFiles::GetOkpInfoFileName(); + MockFile* file = new MockFile(); + EXPECT_CALL(file_system, DoOpen(kOkpInfoPath, _)).WillOnce(Return(file)); + std::string serialized; + EXPECT_CALL(*file, Write(NotNull(), _)) + .WillOnce(DoAll(Invoke([&](const char* buf, size_t len) { + serialized.assign(buf, len); + }), + ReturnArg<1>())); + + EXPECT_TRUE(device_files.StoreOkpInfo(info)); + ASSERT_FALSE(serialized.empty()) << "OKP info was not serialized"; + + // Set retrieve expectations. + file = new MockFile(); + EXPECT_CALL(file_system, Exists(kOkpInfoPath)).WillOnce(Return(true)); + EXPECT_CALL(file_system, FileSize(kOkpInfoPath)) + .WillOnce(Return(serialized.size())); + EXPECT_CALL(file_system, DoOpen(kOkpInfoPath, _)).WillOnce(Return(file)); + EXPECT_CALL(*file, Read(NotNull(), _)) + .WillOnce(DoAll(SetArrayArgument<0>(serialized.begin(), serialized.end()), + Return(serialized.size()))); + + okp::SystemFallbackInfo retrieved_info; + EXPECT_TRUE(device_files.RetrieveOkpInfo(&retrieved_info)); + + EXPECT_EQ(retrieved_info, info); +} + // From a usage info file containing 3 provider sessions, 2 will be // deleted using the |key_set_id| associated with them. // It is expected that once the provider sessions are deleted, the diff --git a/libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp b/libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp new file mode 100644 index 00000000..ea57a42d --- /dev/null +++ b/libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp @@ -0,0 +1,372 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "okp_fallback_policy.h" + +#include + +#include + +#include "mock_clock.h" +#include "test_printers.h" + +namespace wvcdm { +namespace okp { +namespace { +// Thu 29 Jul 2021 10:43:21 PM UTC +constexpr int64_t kInitialTime = 1627598601; + +void SetSystemInfoAsProvisioned(SystemFallbackInfo* info, + int64_t provisioning_time) { + info->SetState(SystemState::kProvisioned); + if (!info->HasFirstCheckedTime()) { + info->SetFirstCheckedTime(kInitialTime); + } + info->ClearBackoffStartTime(); + info->SetProvisioningTime(provisioning_time); +} + +void SetSystemInfoAsFallback(SystemFallbackInfo* info, + int64_t backoff_start_time, + int64_t backoff_duration) { + info->SetState(SystemState::kFallbackMode); + if (!info->HasFirstCheckedTime()) { + info->SetFirstCheckedTime(kInitialTime); + } + info->SetBackoffStartTime(backoff_start_time); + info->SetBackoffDuration(backoff_duration); + info->ClearProvisioningTime(); +} + +class OkpFallbackPolicyTest : public ::testing::Test { + protected: + void SetUp() override { + clock_.SetTime(kInitialTime); + system_policy_ = SystemFallbackPolicy::CreateForTesting(&clock_); + ASSERT_TRUE(system_policy_); + system_policy_->MarkNeedsProvisioning(); + } + + void SetUpWithInfo(const SystemFallbackInfo& info) { + TearDown(); + system_policy_ = SystemFallbackPolicy::CreateForTesting(info, &clock_); + } + + void TearDown() override { system_policy_.reset(); } + + FrozenClock clock_; + std::unique_ptr system_policy_; +}; // class OkpFallbackPolicyTest +} // namespace + +// Test ensures that test fixture is initialized correctly. +TEST_F(OkpFallbackPolicyTest, Initialization) { + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kInitialTime, system_policy_->info().first_checked_time()); + EXPECT_EQ(SystemState::kNeedsProvisioning, system_policy_->state()); +} + +// Setup: +// 1) Device needs OKP +// 2) Some CDM engine successfully provisions, and marks the fallback +// policy as provisioned. +// Expectation: +// Policy is marked as provisioned and timestamp is updated. +TEST_F(OkpFallbackPolicyTest, SuccessfulProvisioning) { + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + + // Final checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); +} + +// Setup: +// 1) Device state is restored and is already provisioned +// Expectation: +// Fallback policy should still be marked as provisioned. +TEST_F(OkpFallbackPolicyTest, Restore_DeviceProvisioned) { + SystemFallbackInfo info; + SetSystemInfoAsProvisioned(&info, kInitialTime); + // Reinitialize. + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); +} + +// Setup: +// 1) Device needs OKP +// 2) App attempts OKP, but response fails and triggers fallback. +// Expectation: +// Policy should indicate fallback, and update info. +TEST_F(OkpFallbackPolicyTest, TriggerFallback) { + // App calls provideProvisionResponse() and fails. + constexpr int64_t kFallbackTime = kInitialTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kFallbackTime); + EXPECT_GE(system_policy_->info().backoff_duration(), + kMinInitialBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) Two apps are performing provisioning simultaneously +// 3) The first app successfully provisions device +// 4) The second app succeeds (see notes) shortly after +// Expectation: +// The time of the first provisioning should be recorded, the +// second should not. +// Note: +// The CDM should silently drop responses which arrive after +// the first successful response. The fallback policy is agnostic +// to the exact mechanism to enforce this behavor. +TEST_F(OkpFallbackPolicyTest, TwoSuccessfulProvisionings) { + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kFirstProvisioningTime = kInitialTime + 10; + clock_.SetTime(kFirstProvisioningTime); + system_policy_->MarkProvisioned(); + + // App calls provideProvisionResponse() and fails. + constexpr int64_t kSecondProvisioningTime = kFirstProvisioningTime + 10; + clock_.SetTime(kSecondProvisioningTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kFirstProvisioningTime, system_policy_->info().provisioning_time()); +} + +// Setup: +// 1) Device needs OKP +// 2) Two apps are performing provisioning simultaneously +// 3) The first app successfully provisions device +// 4) The second app fails, and triggers fallback +// Expectation: +// Once provisioned, fallback policy should remain provisioned. +// Note: +// In this case, the second engine should check if provisioned +// before triggering its own L3 fallback. +TEST_F(OkpFallbackPolicyTest, TriggerFallbackAfterProvisioning) { + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + + // App calls provideProvisionResponse() and fails. + constexpr int64_t kFallbackTime = kProvisioningTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); + EXPECT_FALSE(system_policy_->info().HasBackoffStartTime()); + EXPECT_FALSE(system_policy_->info().HasBackoffDuration()); +} + +// Setup: +// 1) Device needs OKP +// 2) Two apps are performing provisioning simultaneously +// 3) The first app fails, and triggers fallback +// 4) The seoncd app successfully provisions device +// Expectation: +// Fallback policy should indicate that the device is provisioned; +// overriding the fallback. +// Note: +// Depending on the exact timing, the first app may or may not fallback +// to L3 for the remainder of the apps life cycle. If the app did +// fallback to L3, it will be able to resume use of L1 when it restarts. +TEST_F(OkpFallbackPolicyTest, ProvisionAfterFallback) { + // App calls provideProvisionResponse() and fails. + constexpr int64_t kFallbackTime = kInitialTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kProvisioningTime = kFallbackTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); + EXPECT_FALSE(system_policy_->info().HasBackoffStartTime()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, not in fallback mode +// 3) Provisioning is attempted +// Expectation: +// Policy should resume, allowing provisioning. +TEST_F(OkpFallbackPolicyTest, Restore_NeedsProvisioning) { + SystemFallbackInfo info; + info.SetState(SystemState::kNeedsProvisioning); + info.SetFirstCheckedTime(kInitialTime); + constexpr int64_t kRestoreTime = kInitialTime + 10; + clock_.SetTime(kRestoreTime); + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().first_checked_time(), kInitialTime); + // Provision + constexpr int64_t kProvisioningTime = kRestoreTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + // Checks + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Device is still in "backoff period" +// Expectation: +// Fallback policy should continue to be in fallback mode after restore. +TEST_F(OkpFallbackPolicyTest, Restore_InBackoffPeriod) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime + (kBackoffDuration / 2); + clock_.SetTime(kRestoreTime); // Within backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Backoff period is over +// Expectation: +// Fallback policy should no longer be in fallback mode. +TEST_F(OkpFallbackPolicyTest, Restore_AfterBackoffPeriod) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime + (kBackoffDuration * 2); + clock_.SetTime(kRestoreTime); // After backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + // Backoff duration should still be recorded as it will be required + // for exponential backoff. + EXPECT_EQ(system_policy_->info().backoff_duration(), kBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Device system clock has been rewound to a time before backoff started +// Expectation: +// Restore should be successful, fallback may be canceled, provisioning +// should still be needed. +// Note: +// This is not an expected circumstance, but may occur if a user changes +// their devices system time. +TEST_F(OkpFallbackPolicyTest, Restore_InBackoffPeriod_SystemTimeRollback) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime - 250; + clock_.SetTime(kRestoreTime); // Before backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Backoff period is over +// 4) OKP fails again, tiggering fallback again +// Expectation: +// Fallback policy should re-enter fallback mode, with a backoff period +// double that of before. +TEST_F(OkpFallbackPolicyTest, Restore_AfterBackoff_FallbackAgain) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime + (kBackoffDuration * 2); + clock_.SetTime(kRestoreTime); // After backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + ASSERT_FALSE(system_policy_->IsInFallbackMode()); + + // Trigger second fallback. + constexpr int64_t kSecondFallbackTime = kRestoreTime + 10; + clock_.SetTime(kSecondFallbackTime); + system_policy_->TriggerFallback(); + + constexpr int64_t kExpectedBackoffDuration = kBackoffDuration * 2; + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kSecondFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), + kExpectedBackoffDuration); +} + +// Setup: +// 1) Device needs OKP again (see notes) +// 2) Fallback policy is restored, previously being marked as provisioned +// 3) Fallback policy is marked as needing provisioning +// Expectation: +// Once marked as needing re-provisioning, the fallback policy should +// clear is self and indicate that provisioning is needed. +// Note: +// Ideally this should never happen. Once a device is provisioned, is +// should never require OKP again. However, the device should have never +// required OKP in the first place. If a future bug arises, and a device +// requires OKP again, the fallback policy should be able to handle +// this situation without bricking L1. +TEST_F(OkpFallbackPolicyTest, Restore_NeedsProvisioningAgain) { + SystemFallbackInfo info; + constexpr int64_t kInitialProvisioningTime = kInitialTime; + SetSystemInfoAsProvisioned(&info, kInitialProvisioningTime); + constexpr int64_t kRestoreTime = kInitialProvisioningTime + 100; + clock_.SetTime(kRestoreTime); + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + ASSERT_TRUE(system_policy_->IsProvisioned()); + system_policy_->MarkNeedsProvisioning(); + + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().first_checked_time(), kRestoreTime); +} +} // namespace okp +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp b/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp index ca10e39c..daaab941 100644 --- a/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp +++ b/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp @@ -7,13 +7,22 @@ #include #include "crypto_session.h" +#include "mock_clock.h" +#include "okp_fallback_policy.h" +#include "properties.h" +#include "string_conversions.h" namespace wvcdm { using ::testing::DoAll; using ::testing::NotNull; using ::testing::Return; using ::testing::SetArgPointee; +using ::video_widevine::ProvisioningResponse; +using ::video_widevine::SignedProvisioningMessage; +using ProvisioningType = SignedProvisioningMessage::ProvisioningType; + namespace { +constexpr int64_t kInitialTime = 1629321960; // Wed 18 Aug 2021 09:26:00 PM const std::string kEmptyString; const std::string kFakeOtaProvisioningRequest = "Totally real device ID, not fake" // Device ID : 32 bytes @@ -47,6 +56,14 @@ class MockCryptoSession : public CryptoSession { CdmResponseType(bool, std::string*)); MOCK_METHOD2(LoadOtaProvisioning, CdmResponseType(bool, const std::string&)); + // Client ID + uint8_t GetSecurityPatchLevel() override { return 0x00; } + bool GetBuildInformation(std::string* info) override { + if (info == nullptr) return false; + info->assign("OtaKeyboxProvisionerTest"); + return true; + } + void ExpectRequest(const std::string& request, CdmResponseType result) { if (result == NO_ERROR) { EXPECT_CALL(*this, PrepareOtaProvisioningRequest(false, NotNull())) @@ -69,174 +86,292 @@ class MockCryptoSession : public CryptoSession { }; metrics::CryptoMetrics MockCryptoSession::crypto_metrics_; + +std::vector ToVector(const std::string& s) { + return std::vector(s.begin(), s.end()); +} +std::string FromVector(const std::vector& v) { + return std::string(v.begin(), v.end()); +} } // namespace class OtaKeyboxProvisionerTest : public ::testing::Test { protected: void SetUp() override { - crypto_session_.reset(new MockCryptoSession()); - provisioner_ = OtaKeyboxProvisioner::Create(); + clock_.SetTime(kInitialTime); + fallback_policy_ = okp::SystemFallbackPolicy::CreateForTesting(&clock_); + ASSERT_TRUE(fallback_policy_); + fallback_policy_->MarkNeedsProvisioning(); + crypto_session_ = new MockCryptoSession(); + crypto_session_->SetIsOpen(true); + provisioner_ = OtaKeyboxProvisioner::CreateForTesting( + std::unique_ptr(crypto_session_), + fallback_policy_.get()); + ASSERT_TRUE(provisioner_); + } + + void SetUpWithInfo(const okp::SystemFallbackInfo& info) { + TearDown(); + fallback_policy_ = + okp::SystemFallbackPolicy::CreateForTesting(info, &clock_); + ASSERT_TRUE(fallback_policy_); + fallback_policy_->MarkNeedsProvisioning(); + crypto_session_ = new MockCryptoSession(); + crypto_session_->SetIsOpen(true); + provisioner_ = OtaKeyboxProvisioner::CreateForTesting( + std::unique_ptr(crypto_session_), + fallback_policy_.get()); } void TearDown() override { - crypto_session_.reset(); provisioner_.reset(); + crypto_session_ = nullptr; + fallback_policy_.reset(); } - std::unique_ptr crypto_session_; + FrozenClock clock_; + std::unique_ptr fallback_policy_; + MockCryptoSession* crypto_session_ = nullptr; std::unique_ptr provisioner_; + + bool restore_messages_are_json_ = false; }; -TEST_F(OtaKeyboxProvisionerTest, SingleRequestSingleResponse) { +// Used to make a variety of SignedProvisioningMessage, including invalid +// responses. +// |provisioning_type| - ProvisioningType of outer message. +// Valid response is ANDROID_ATTESTATION_KEYBOX_OTA +// |include_response| - Include the actual ProvisioningResponse message +// bytes. Valid response is true. +// |include_ota_response| - Includes the |android_ota_keybox_response| +// field of the ProvisioningResponse message. +// |ota_response| - Raw bytes of the ota_response field of the +// AndroidAttestationOtaKeyboxResponse. +// Valid responses contain the response to be passed +// into OEMCrypto. +void MakeSignedOtaProvisioningResponseEx(ProvisioningType provisioning_type, + bool include_response, + bool include_ota_response, + const std::string& ota_response, + std::string* response) { + ProvisioningResponse prov_response; + if (include_ota_response) { + prov_response.mutable_android_ota_keybox_response()->set_ota_response( + ota_response); + } + std::string message; + prov_response.SerializeToString(&message); + + SignedProvisioningMessage signed_response; + signed_response.set_protocol_version(SignedProvisioningMessage::VERSION_1); + signed_response.set_provisioning_type(provisioning_type); + if (include_response) { + signed_response.set_message(message); + } + std::string proto_response; + signed_response.SerializeToString(&proto_response); + + if (Properties::provisioning_messages_are_binary()) { + *response = std::move(proto_response); + return; + } + response->assign("{\n\"signedResponse\": \""); + response->append(Base64SafeEncodeNoPad(ToVector(proto_response))); + response->append("\"\n}\n"); +} + +// Make a valid SignedProvisioningMessage containing an OTA keybox response. +void MakeSignedOtaProvisioningResponse(const std::string& ota_response, + std::string* response) { + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA, true, true, + ota_response, response); +} + +bool RequestContains(const std::string& request, const std::string& part) { + if (Properties::provisioning_messages_are_binary()) { + const auto request_pos = request.find(part); + return request_pos != std::string::npos; + } + const std::string decoded_request = FromVector(Base64SafeDecode(request)); + if (decoded_request.empty()) return false; + const auto request_pos = decoded_request.find(part); + return request_pos != std::string::npos; +} + +TEST_F(OtaKeyboxProvisionerTest, FullProvisioning) { // Pre-request conditions. EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(0u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); // Generate request. crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &request)); - EXPECT_EQ(request, kFakeOtaProvisioningRequest); + std::string signed_prov_message, default_url; + EXPECT_EQ(NO_ERROR, provisioner_->GetProvisioningRequest(&signed_prov_message, + &default_url)); + EXPECT_TRUE( + RequestContains(signed_prov_message, kFakeOtaProvisioningRequest)); + EXPECT_FALSE(default_url.empty()); // Post-request, pre-response conditions. EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(1u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); // Load response. - const std::string response = kFakeOtaProvisioningResponse; - crypto_session_->ExpectResponse(response, NO_ERROR); - EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), response)); + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + std::string response; + MakeSignedOtaProvisioningResponse(kFakeOtaProvisioningResponse, &response); + crypto_session_->ExpectResponse(kFakeOtaProvisioningResponse, NO_ERROR); + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); // Post-response conditions. EXPECT_TRUE(provisioner_->IsProvisioned()); - EXPECT_EQ(1u, provisioner_->request_count()); - EXPECT_EQ(1u, provisioner_->response_count()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_TRUE(provisioner_->response_received()); + EXPECT_EQ(kProvisioningTime, fallback_policy_->info().provisioning_time()); } -TEST_F(OtaKeyboxProvisionerTest, MultipleRequestsMultipleResponse) { - // Generate first request. +// The OTA keybox provisioning is a system-wide provisioning processes. +// After a particular CDM engine (A) begins the request, it may be +// completed by a different CDM engine (B) before A receives a response. +// Provisioning from A perspective should complete without issues. +TEST_F(OtaKeyboxProvisionerTest, CompletedInDifferentEngine) { + // Generate request (engine A). crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string first_request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &first_request)); - EXPECT_EQ(first_request, kFakeOtaProvisioningRequest); + std::string signed_prov_message, default_url; + EXPECT_EQ(NO_ERROR, provisioner_->GetProvisioningRequest(&signed_prov_message, + &default_url)); - // Generate second request. - crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string second_request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &second_request)); - EXPECT_EQ(second_request, kFakeOtaProvisioningRequest); - - EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(2u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); - - // Load first response. - const std::string first_response = kFakeOtaProvisioningResponse; - crypto_session_->ExpectResponse(first_response, NO_ERROR); - EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), first_response)); - - // Loading second response should be silently dropped. - // No call to CryptoSession. - const std::string second_response = kAnotherFakeOtaProvisioningResponse; - EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), second_response)); - - // Post-response conditions. + // Complete provisioning in different context (outside of engine A's scope). + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + fallback_policy_->MarkProvisioned(); + // Engine provisioner should still indicate that provisioning is complete. EXPECT_TRUE(provisioner_->IsProvisioned()); - EXPECT_EQ(2u, provisioner_->request_count()); - EXPECT_EQ(2u, provisioner_->response_count()); + EXPECT_FALSE(provisioner_->response_received()); + + // Load engine A's response. + constexpr int64_t kResponseTime = kProvisioningTime + 10; + clock_.SetTime(kResponseTime); + std::string response; + MakeSignedOtaProvisioningResponse(kAnotherFakeOtaProvisioningResponse, + &response); + // OtaKeyboxProvisioner::HandleProvisioningResponse will be called, but + // not OEMCrypto. + // Expect success. + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); + + // Post-engine-response.; + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_TRUE(provisioner_->response_received()); + EXPECT_EQ(kProvisioningTime, fallback_policy_->info().provisioning_time()); } -TEST_F(OtaKeyboxProvisionerTest, NullRequestParameters) { - // Null CryptoSession - std::string request; - EXPECT_NE(NO_ERROR, - provisioner_->GenerateProvisioningRequest(nullptr, &request)); - - // Null request, no call to CryptoSession. - EXPECT_NE(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), nullptr)); - // Counter should not increase. - EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(0u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); -} - -TEST_F(OtaKeyboxProvisionerTest, EmptyRequest) { +// Test to ensure there are no critical failures when receiving a +// malformed SignedProvisioningMessage. +TEST_F(OtaKeyboxProvisionerTest, MalformedResponseMessage) { // Generate request. crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &request)); + std::string signed_prov_message, default_url; + EXPECT_EQ(NO_ERROR, provisioner_->GetProvisioningRequest(&signed_prov_message, + &default_url)); - // Attempt to load empty response. No call to CryptoSession. - const std::string response = ""; - EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), response)); + // Invalid response 1: Cannot SignedProvisioningMessage. + std::string response = "Not a SignedProvisioningMessage"; + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + // Invalid response 2: Wrong SignedProvisioningMessage protocol. + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::PROVISIONING_30, true, true, + kFakeOtaProvisioningResponse, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Invalid response 3: Missing serialized ProvisioningResponse message. + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA, false, true, + kFakeOtaProvisioningResponse, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Invalid response 4: Missing AndroidAttestationOtaKeyboxResponse message, + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA, true, false, + kFakeOtaProvisioningResponse, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Invalid response 5: Missing raw OTA keybox response. + MakeSignedOtaProvisioningResponse(kEmptyString, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Post-response failure conditions. EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(1u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); +} + +// Test case where OEMCrypto rejects the provided OTA keybox response. +TEST_F(OtaKeyboxProvisionerTest, RejectedResponse) { + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string request, default_url; + EXPECT_EQ(NO_ERROR, + provisioner_->GetProvisioningRequest(&request, &default_url)); + + // Load response. OEMCrypto returns error. + std::string response; + MakeSignedOtaProvisioningResponse(kFakeOtaProvisioningResponse, &response); + crypto_session_->ExpectResponse(kFakeOtaProvisioningResponse, UNKNOWN_ERROR); + EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); + + // Post-response failure conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_TRUE(provisioner_->IsInFallbackMode()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); } TEST_F(OtaKeyboxProvisionerTest, OtaProvisioningNotImplemented) { // Generate request. crypto_session_->ExpectRequest(kEmptyString, NOT_IMPLEMENTED_ERROR); - std::string request; - EXPECT_EQ(NOT_IMPLEMENTED_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &request)); - // Counter should not increase. + std::string request, default_url; + EXPECT_EQ(NOT_IMPLEMENTED_ERROR, + provisioner_->GetProvisioningRequest(&request, &default_url)); + + // Post-response failure conditions. EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(0u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); -} - -TEST_F(OtaKeyboxProvisionerTest, ResponseRejected) { - // Generate request. - crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &request)); - - // Attempt to load response. OEMCrypto rejects response. - const std::string response = kFakeOtaProvisioningResponse; - crypto_session_->ExpectResponse(response, UNKNOWN_ERROR); - EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), response)); - - // Should not be provisioned. - EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(1u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); + EXPECT_TRUE(provisioner_->IsInFallbackMode()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); } TEST_F(OtaKeyboxProvisionerTest, GenerateRequestAfterProvisioning) { + fallback_policy_->MarkProvisioned(); // Generate request. - crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string first_request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &first_request)); - - // Load response. - const std::string response = kFakeOtaProvisioningResponse; - crypto_session_->ExpectResponse(response, NO_ERROR); - EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), response)); - - // Attempt to generate second request. Should fail. - std::string second_request; - EXPECT_NE(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &second_request)); + std::string request, default_url; + EXPECT_EQ(OKP_ALREADY_PROVISIONED, + provisioner_->GetProvisioningRequest(&request, &default_url)); EXPECT_TRUE(provisioner_->IsProvisioned()); - EXPECT_EQ(1u, provisioner_->request_count()); - EXPECT_EQ(1u, provisioner_->response_count()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); +} + +TEST_F(OtaKeyboxProvisionerTest, ResponseWithoutRequest) { + std::string response; + MakeSignedOtaProvisioningResponse(kFakeOtaProvisioningResponse, &response); + // Does not trigger system-wide fallback. This is a bad app. + EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); + + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_FALSE(provisioner_->IsInFallbackMode()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); // Not actually received. } } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/test_printers.cpp b/libwvdrmengine/cdm/core/test/test_printers.cpp index d57de31e..1027e598 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.cpp +++ b/libwvdrmengine/cdm/core/test/test_printers.cpp @@ -7,7 +7,6 @@ #include "test_printers.h" namespace wvcdm { - void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { switch (value) { case NO_ERROR: @@ -623,6 +622,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case NO_USAGE_ENTRIES: *os << "NO_USAGE_ENTRIES"; break; + case OKP_ALREADY_PROVISIONED: + *os << "OKP_ALREADY_PROVISIONED"; + break; case OPEN_CRYPTO_SESSION_ERROR: *os << "OPEN_CRYPTO_SESSION_ERROR"; break; @@ -632,6 +634,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case PARAMETER_NULL: *os << "PARAMETER_NULL"; break; + case PARSE_OKP_RESPONSE_ERROR: + *os << "PARSE_OKP_RESPONSE_ERROR"; + break; case PARSE_REQUEST_ERROR_1: *os << "PARSE_REQUEST_ERROR_1"; break; @@ -1210,5 +1215,9 @@ void PrintTo(const enum OEMCryptoResult& value, ::std::ostream* os) { break; } } - +namespace okp { +void PrintTo(const SystemState& state, std::ostream* os) { + *os << SystemStateToString(state); +} +} // namespace okp } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/test_printers.h b/libwvdrmengine/cdm/core/test/test_printers.h index 0df9d06e..b51adc8f 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.h +++ b/libwvdrmengine/cdm/core/test/test_printers.h @@ -8,7 +8,9 @@ #define CDM_TEST_PRINTERS_H_ #include + #include "OEMCryptoCENC.h" +#include "okp_info.h" #include "wv_cdm_types.h" namespace wvcdm { @@ -17,6 +19,8 @@ void PrintTo(const enum CdmLicenseType& value, ::std::ostream* os); void PrintTo(const enum CdmSecurityLevel& value, ::std::ostream* os); void PrintTo(const enum CdmCertificateType& value, ::std::ostream* os); void PrintTo(const enum OEMCryptoResult& value, ::std::ostream* os); +namespace okp { +void PrintTo(const SystemState& state, std::ostream* os); +} // namespace okp } // namespace wvcdm - #endif // CDM_TEST_PRINTERS_H_ diff --git a/libwvdrmengine/cdm/test/Android.mk b/libwvdrmengine/cdm/test/Android.mk index 2f12234b..09f472df 100644 --- a/libwvdrmengine/cdm/test/Android.mk +++ b/libwvdrmengine/cdm/test/Android.mk @@ -114,6 +114,15 @@ test_name := metrics_collections_unittest test_src_dir := ../metrics/test include $(LOCAL_PATH)/unit-test.mk +test_name := okp_fallback_policy_test +test_src_dir := ../core/test +include $(LOCAL_PATH)/unit-test.mk + +test_name := ota_keybox_provisioner_test +test_src_dir := ../core/test +test_main := ../core/test/test_main.cpp +include $(LOCAL_PATH)/integration-test.mk + test_name := policy_engine_constraints_unittest test_src_dir := ../core/test test_main := ../core/test/test_main.cpp diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index bd0b413a..976775c8 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -2097,7 +2097,7 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { } CdmProvisioningResponse provisioning_response; - if (!CertificateProvisioning::ExtractAndDecodeSignedMessageForTesting( + if (!CertificateProvisioning::ExtractAndDecodeSignedMessage( response, &provisioning_response)) { EXPECT_TRUE(false); return false; diff --git a/libwvdrmengine/include/WVErrors.h b/libwvdrmengine/include/WVErrors.h index 23cd2bfb..4fef1efa 100644 --- a/libwvdrmengine/include/WVErrors.h +++ b/libwvdrmengine/include/WVErrors.h @@ -302,10 +302,12 @@ enum { kCertProvisioningResponseError10 = ERROR_DRM_VENDOR_MIN + 317, kClientTokenNotSet = ERROR_DRM_VENDOR_MIN + 318, kUsageEntryAlreadyLoaded = ERROR_DRM_VENDOR_MIN + 319, + kParseOkpResponseError = ERROR_DRM_VENDOR_MIN + 320, + kOkpAlreadyProvisioned = ERROR_DRM_VENDOR_MIN + 321, // This should always follow the last error code. // The offset value should be updated each time a new error code is added. - kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 319, + kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 321, // Used by crypto test mode kErrorTestMode = ERROR_DRM_VENDOR_MAX, diff --git a/libwvdrmengine/include/mapErrors-inl.h b/libwvdrmengine/include/mapErrors-inl.h index 305644ec..f9ea0b98 100644 --- a/libwvdrmengine/include/mapErrors-inl.h +++ b/libwvdrmengine/include/mapErrors-inl.h @@ -415,8 +415,12 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { return kNoUsageEntries; case wvcdm::OFFLINE_LICENSE_PROHIBITED: return kOfflineLicenseProhibited; + case wvcdm::OKP_ALREADY_PROVISIONED: + return kOkpAlreadyProvisioned; case wvcdm::PARAMETER_NULL: return kParameterNull; + case wvcdm::PARSE_OKP_RESPONSE_ERROR: + return kParseOkpResponseError; case wvcdm::PARSE_REQUEST_ERROR_1: return kParseRequestError1; case wvcdm::PARSE_REQUEST_ERROR_2: diff --git a/libwvdrmengine/include_hidl/mapErrors-inl.h b/libwvdrmengine/include_hidl/mapErrors-inl.h index bceea4d9..5b7128b1 100644 --- a/libwvdrmengine/include_hidl/mapErrors-inl.h +++ b/libwvdrmengine/include_hidl/mapErrors-inl.h @@ -362,6 +362,8 @@ static Status mapCdmResponseType_1_0(wvcdm::CdmResponseType res) { case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_10: case wvcdm::CLIENT_TOKEN_NOT_SET: case wvcdm::USAGE_ENTRY_ALREADY_LOADED: + case wvcdm::PARSE_OKP_RESPONSE_ERROR: + case wvcdm::OKP_ALREADY_PROVISIONED: ALOGW("Returns UNKNOWN error for legacy status: %d", res); return Status::ERROR_DRM_UNKNOWN; diff --git a/libwvdrmengine/run_all_unit_tests.sh b/libwvdrmengine/run_all_unit_tests.sh index 4ce288ab..ba648703 100755 --- a/libwvdrmengine/run_all_unit_tests.sh +++ b/libwvdrmengine/run_all_unit_tests.sh @@ -128,6 +128,8 @@ adb_shell_run libwvdrmmediacrypto_test adb_shell_run license_keys_unittest adb_shell_run license_unittest adb_shell_run odk_test +adb_shell_run okp_fallback_policy_test +adb_shell_run ota_keybox_provisioner_test adb_shell_run policy_engine_constraints_unittest adb_shell_run policy_engine_unittest adb_shell_run policy_integration_test From a3166eb2bac1ce05e1b95ffcadbe3e0d7ff4b858 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Thu, 16 Sep 2021 22:08:34 +0000 Subject: [PATCH 04/12] Updates to OTA Keybox Reprovisioning Adjust OTA code to account for some design changes and add integration tests. Merge from Widevine repo of http://go/wvgerrit/133775 Change use_test_key to uint32_t type Merge from Widevine repo of http://go/wvgerrit/133774 Cleanup CDM OKP info before tests. Merge from Widevine repo of http://go/wvgerrit/133773 Change context for derivation in OTA keybox solution Merge from Widevine repo of http://go/wvgerrit/133772 Updated OTA keybox key derivation. Merge from Widevine repo of http://go/wvgerrit/133771 Use double provisioning step in integration tests Merge from Widevine repo of http://go/wvgerrit/133770 Erase keybox on initialization for OEMCrypto testbed Merge from Widevine repo of http://go/wvgerrit/133769 Add session id to OEMCrypto OTA functions Merge from Widevine repo of http://go/wvgerrit/133768 Integration test for OTA Keybox reprovisioning Merge from Widevine repo of http://go/wvgerrit/133767 Add test x509 cert for testing Merge from Widevine repo of http://go/wvgerrit/133766 OTA Keybox basic functionality in testbed Merge from Widevine repo of http://go/wvgerrit/133765 Update OTA test script to use newer build scripts Merge from Widevine repo of http://go/wvgerrit/133764 Adjust comment stype for doxygen Test: Test: unit/integration/GtsMediaTestCases Bug: 190505461 Bug: 190505461 Bug: 190505461 bug: 187646550 Bug: 187646550 Bug: 187646550 Bug: 187646550 Bug: 190505461 Bug: 187646550 Bug: 188228998 Bug: 190505461 Bug: 187646550 Merged-In: I41ff819a1fd8aca2e20adb25127fa0d9c4879b01 Change-Id: I41ff819a1fd8aca2e20adb25127fa0d9c4879b01 --- libwvdrmengine/build_all_unit_tests.sh | 3 + .../cdm/core/include/crypto_session.h | 1 + .../cdm/core/src/crypto_session.cpp | 13 +- .../core/src/oemcrypto_adapter_dynamic.cpp | 25 +- .../cdm/core/src/oemcrypto_ota_stubs.cpp | 11 +- .../cdm/core/test/keybox_ota_test.cpp | 113 +++++++- libwvdrmengine/cdm/core/test/test_base.cpp | 5 +- libwvdrmengine/cdm/test/Android.mk | 4 +- .../oemcrypto/include/OEMCryptoCENC.h | 106 ++++---- .../oemcrypto/test/ota_keybox_test.cpp | 254 +++++++++++++++++- 10 files changed, 443 insertions(+), 92 deletions(-) diff --git a/libwvdrmengine/build_all_unit_tests.sh b/libwvdrmengine/build_all_unit_tests.sh index c1faf174..2892ca04 100755 --- a/libwvdrmengine/build_all_unit_tests.sh +++ b/libwvdrmengine/build_all_unit_tests.sh @@ -48,6 +48,7 @@ WV_TEST_TARGETS="base64_test \ hidl_metrics_adapter_unittest \ http_socket_test \ initialization_data_unittest \ + keybox_ota_test \ libwvdrmdrmplugin_hidl_test \ libwvdrmdrmplugin_test \ libwvdrmengine_hidl_test \ @@ -59,6 +60,8 @@ WV_TEST_TARGETS="base64_test \ metrics_collections_unittest \ oemcrypto_test \ odk_test \ + okp_fallback_policy_test \ + ota_keybox_provisioner_test \ policy_engine_constraints_unittest \ policy_engine_unittest \ policy_integration_test \ diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index 6d04ba4a..f45b547f 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -329,6 +329,7 @@ class CryptoSession { #if defined(UNIT_TEST) friend class CertificateProvisioningTest; friend class WvCdmTestBase; + friend class CdmOtaKeyboxTest; #endif // The global factory method can be set to generate special crypto sessions diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index c6b1b16c..3bcee3ce 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -3041,9 +3041,10 @@ okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() { CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( bool use_test_key, std::string* request) { RETURN_IF_NULL(request, PARAMETER_NULL); + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); size_t buffer_length = 0; - OEMCryptoResult status = - OEMCrypto_GenerateOTARequest(nullptr, &buffer_length, use_test_key); + OEMCryptoResult status = OEMCrypto_GenerateOTARequest( + oec_session_id_, nullptr, &buffer_length, use_test_key); if (status != OEMCrypto_ERROR_SHORT_BUFFER) return MapOEMCryptoResult(status, UNKNOWN_ERROR, "PrepareOtaProvisioningRequest"); @@ -3053,7 +3054,8 @@ CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( } request->resize(buffer_length); uint8_t* buf = reinterpret_cast(&request->front()); - status = OEMCrypto_GenerateOTARequest(buf, &buffer_length, use_test_key); + status = OEMCrypto_GenerateOTARequest(oec_session_id_, buf, &buffer_length, + use_test_key ? 1 : 0); if (OEMCrypto_SUCCESS != status) { request->clear(); } else if (buffer_length != request->size()) { @@ -3065,9 +3067,10 @@ CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( CdmResponseType CryptoSession::LoadOtaProvisioning( bool use_test_key, const std::string& response) { + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); const OEMCryptoResult status = OEMCrypto_ProcessOTAKeybox( - reinterpret_cast(response.data()), response.size(), - use_test_key); + oec_session_id_, reinterpret_cast(response.data()), + response.size(), use_test_key ? 1 : 0); return MapOEMCryptoResult(status, UNKNOWN_ERROR, "LoadOtaProvisioning"); } diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp index 759c280d..d28bf1b4 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp @@ -318,12 +318,14 @@ typedef OEMCryptoResult (*L1_LoadProvisioning_t)( size_t signature_length, uint8_t* wrapped_private_key, size_t* wrapped_private_key_length); typedef uint32_t (*L1_MinorAPIVersion_t)(); -typedef OEMCryptoResult (*L1_GenerateOTARequest_t)(uint8_t* buffer, +typedef OEMCryptoResult (*L1_GenerateOTARequest_t)(OEMCrypto_SESSION session, + uint8_t* buffer, size_t* buffer_length, - bool use_test_key); -typedef OEMCryptoResult (*L1_ProcessOTAKeybox_t)(const uint8_t* buffer, + uint32_t use_test_key); +typedef OEMCryptoResult (*L1_ProcessOTAKeybox_t)(OEMCrypto_SESSION session, + const uint8_t* buffer, size_t buffer_length, - bool use_test_key); + uint32_t use_test_key); struct FunctionPointers { wvcdm::CdmSecurityLevel security_level; uint32_t version; @@ -2797,23 +2799,24 @@ extern "C" OEMCryptoResult OEMCrypto_FreeSecureBuffer( return pair.fcn->FreeSecureBuffer(pair.session, output_descriptor, secure_fd); } -extern "C" OEMCryptoResult OEMCrypto_GenerateOTARequest(uint8_t* buffer, - size_t* buffer_length, - bool use_test_key) { +extern "C" OEMCryptoResult OEMCrypto_GenerateOTARequest( + OEMCrypto_SESSION session, uint8_t* buffer, size_t* buffer_length, + uint32_t use_test_key) { if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; const FunctionPointers* fcn = gAdapter->GetFunctionPointers(kLevelDefault); if (!fcn) return OEMCrypto_ERROR_UNKNOWN_FAILURE; if (fcn->GenerateOTARequest == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return fcn->GenerateOTARequest(buffer, buffer_length, use_test_key); + return fcn->GenerateOTARequest(session, buffer, buffer_length, use_test_key); } -extern "C" OEMCryptoResult OEMCrypto_ProcessOTAKeybox(const uint8_t* buffer, +extern "C" OEMCryptoResult OEMCrypto_ProcessOTAKeybox(OEMCrypto_SESSION session, + const uint8_t* buffer, size_t buffer_length, - bool use_test_key) { + uint32_t use_test_key) { if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; const FunctionPointers* fcn = gAdapter->GetFunctionPointers(kLevelDefault); if (!fcn) return OEMCrypto_ERROR_UNKNOWN_FAILURE; if (fcn->ProcessOTAKeybox == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return fcn->ProcessOTAKeybox(buffer, buffer_length, use_test_key); + return fcn->ProcessOTAKeybox(session, buffer, buffer_length, use_test_key); } diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_ota_stubs.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_ota_stubs.cpp index 74bfb3c1..2c0b59de 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_ota_stubs.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_ota_stubs.cpp @@ -4,14 +4,15 @@ #include "OEMCryptoCENC.h" -extern "C" OEMCryptoResult OEMCrypto_GenerateOTARequest(uint8_t* buffer, - size_t* buffer_length, - bool use_test_key) { +extern "C" OEMCryptoResult OEMCrypto_GenerateOTARequest( + OEMCrypto_SESSION session, uint8_t* buffer, size_t* buffer_length, + uint32_t use_test_key) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } -extern "C" OEMCryptoResult OEMCrypto_ProcessOTAKeybox(const uint8_t* buffer, +extern "C" OEMCryptoResult OEMCrypto_ProcessOTAKeybox(OEMCrypto_SESSION session, + const uint8_t* buffer, size_t buffer_length, - bool use_test_key) { + uint32_t use_test_key) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } diff --git a/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp b/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp index 16dd0836..36dd09a9 100644 --- a/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp +++ b/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp @@ -3,9 +3,120 @@ // License Agreement. #include +#include +#include #include "crypto_session.h" +#include "properties.h" +#include "test_base.h" +#include "test_printers.h" +#include "test_sleep.h" +#include "url_request.h" + +using wvcdm::metrics::EngineMetrics; namespace wvcdm { -TEST(OTAKeyboxTest, TestThatTheBuildFilesWork) { ASSERT_TRUE(true); } +class CdmOtaKeyboxTest : public ::testing::Test { + public: + void SetUp() override { + ::testing::Test::SetUp(); + Properties::Init(); + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name()); + // Some test environments allow the model name of the device to be set + // dynamically using an environment variable. The model name will show up + // in the license server logs as the part of the device idenfication as + // "model_name". + std::string model_name = + std::string(test_info->test_case_name()) + "." + test_info->name(); + int overwrite = 1; // Set value even if already set. + setenv("MODEL_NAME", model_name.c_str(), overwrite); + // Reset the crypto session factory, so that a default one will be created. + // For these tests, we do *not* want to use the test crypto session factory + // because it automatically installs the test keybox on initialization. We + // do not want that because we are testing keybox provisioning. + CryptoSession::SetCryptoSessionFactory(nullptr); + } + + void Provision(TestCdmEngine* cdm_engine) { + ConfigTestEnv config = WvCdmTestBase::default_config_; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority; + CdmProvisioningRequest prov_request; + std::string provisioning_server_url; + CdmResponseType result = cdm_engine->GetProvisioningRequest( + cert_type, cert_authority, config.provisioning_service_certificate(), + kLevelDefault, &prov_request, &provisioning_server_url); + ASSERT_EQ(NO_ERROR, result); + + LOGV("Provisioning request: req = %s", prov_request.c_str()); + + // Ignore URL provided by CdmEngine. Use ours, as configured + // for test vs. production server. + provisioning_server_url.assign(config.provisioning_server()); + + // Make request. + UrlRequest url_request(provisioning_server_url); + if (!url_request.is_connected()) { + LOGE("Failed to connect to provisioning server: url = %s", + provisioning_server_url.c_str()); + } + url_request.PostCertRequestInQueryString(prov_request); + + // Receive and parse response. + std::string http_message; + ASSERT_TRUE(url_request.GetResponse(&http_message)) + << "Failed to get provisioning response"; + LOGV("http_message: \n%s\n", http_message.c_str()); + + std::string cert, wrapped_key; + ASSERT_EQ(NO_ERROR, cdm_engine->HandleProvisioningResponse( + http_message, kLevelDefault, &cert, &wrapped_key)); + } +}; + +TEST_F(CdmOtaKeyboxTest, TestThatTheBuildFilesWork) { ASSERT_TRUE(true); } + +TEST_F(CdmOtaKeyboxTest, BasicTest) { + FileSystem file_system; + TestCdmEngine cdm_engine(&file_system, + std::shared_ptr(new EngineMetrics)); + CdmSessionId session_id; + ConfigTestEnv config = WvCdmTestBase::default_config_; + + CdmResponseType status = cdm_engine.OpenSession(config.key_system(), nullptr, + nullptr, &session_id); + // If there is no keybox or there is no drm cert, we should get a + // NEED_PROVISIONING response. If there is a keybox and a drm cert, there + // should be no other error and this test is finished. + if (status != NEED_PROVISIONING) { + ASSERT_EQ(NO_ERROR, status); + std::cout << "Device does not need provisioning. Skipping rest of test.\n"; + return; + } + std::cout << "First provisioning process.\n"; + Provision(&cdm_engine); + + // After the first provisioning pass, we try to open a session again. If the + // first provisioning was to install a keybox, this provisioning should be to + // install a drm cert. If the first provisioning step installed a drm cert, + // there should be no other error and this test is finished. + status = cdm_engine.OpenSession(config.key_system(), nullptr, nullptr, + &session_id); + if (status != NEED_PROVISIONING) { + ASSERT_EQ(NO_ERROR, status); + std::cout << "Device does not need provisioning. Skipping rest of test.\n"; + return; + } + std::cout << "Second provisioning process.\n"; + Provision(&cdm_engine); + + // After the second provisioning pass, we should be able to open a session + // and continue. + status = cdm_engine.OpenSession(config.key_system(), nullptr, nullptr, + &session_id); + ASSERT_EQ(NO_ERROR, status); + // Full recovery will be tested with higher level integration tests. +} } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/test_base.cpp b/libwvdrmengine/cdm/core/test/test_base.cpp index 87c14bd3..d064a37b 100644 --- a/libwvdrmengine/cdm/core/test/test_base.cpp +++ b/libwvdrmengine/cdm/core/test/test_base.cpp @@ -419,7 +419,6 @@ void WvCdmTestBase::EnsureProvisioned() { std::shared_ptr(new EngineMetrics)); CdmResponseType status = cdm_engine.OpenSession(config_.key_system(), nullptr, nullptr, &session_id); - CdmAppParameterMap app_parameters; CdmKeySetId key_set_id; InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, binary_key_id()); @@ -430,7 +429,8 @@ void WvCdmTestBase::EnsureProvisioned() { app_parameters, &key_request); } - if (status == NEED_PROVISIONING) { + // There are situations where we need two provisioning steps. + for (int count = 0; count < 2 && status == NEED_PROVISIONING; count++) { Provision(); status = cdm_engine.OpenSession(config_.key_system(), nullptr, nullptr, &session_id); @@ -439,7 +439,6 @@ void WvCdmTestBase::EnsureProvisioned() { status = cdm_engine.GenerateKeyRequest(session_id, key_set_id, init_data, kLicenseTypeStreaming, app_parameters, &key_request); - ASSERT_EQ(KEY_MESSAGE, status); } ASSERT_EQ(KEY_MESSAGE, status); ASSERT_NE("", session_id) << "Could not open CDM session."; diff --git a/libwvdrmengine/cdm/test/Android.mk b/libwvdrmengine/cdm/test/Android.mk index 09f472df..20d4c522 100644 --- a/libwvdrmengine/cdm/test/Android.mk +++ b/libwvdrmengine/cdm/test/Android.mk @@ -97,9 +97,11 @@ test_name := initialization_data_unittest test_src_dir := ../core/test include $(LOCAL_PATH)/unit-test.mk +# This test is only intended for devices that support OTA keybox provisioning. test_name := keybox_ota_test test_src_dir := ../core/test -include $(LOCAL_PATH)/unit-test.mk +test_main := ../core/test/test_main.cpp +include $(LOCAL_PATH)/integration-test.mk test_name := license_keys_unittest test_src_dir := ../core/test diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index 6efc97ac..e40a340c 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -2959,31 +2959,19 @@ OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod(void); * If the device has an OEM Certificate, this validates the certificate * private key. * + * On devices that support OEMCrypto_GenerateOTARequest and + * OEMCrypto_ProcessOTAKeybox, this function may return + * OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING when a valid keybox is not present. + * * @retval OEMCrypto_SUCCESS * @retval OEMCrypto_ERROR_BAD_MAGIC * @retval OEMCrypto_ERROR_BAD_CRC * @retval OEMCrypto_ERROR_KEYBOX_INVALID * @retval OEMCrypto_ERROR_INVALID_RSA_KEY * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING * - * On devices that support OEMCrypto_GenerateOTARequest and - * OEMCrypto_ProcessOTAKeybox, this function may return - * OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING when a valid keybox is not - * present. - * - * Parameters: - * none - * - * Returns: - * OEMCrypto_SUCCESS - * OEMCrypto_ERROR_BAD_MAGIC - * OEMCrypto_ERROR_BAD_CRC - * OEMCrypto_ERROR_KEYBOX_INVALID - * OEMCrypto_ERROR_INVALID_RSA_KEY - * OEMCrypto_ERROR_SYSTEM_INVALIDATED - * OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING - * - * Threading: + * @threading * This is a "Property Function" and may be called simultaneously with any * other property function or session function, but not any initialization or * usage table function, as if the CDM holds a read lock on the OEMCrypto @@ -4752,27 +4740,25 @@ OEMCryptoResult OEMCrypto_FreeSecureBuffer( /* * OEMCrypto_GenerateOTARequest * - * Description: - * Generate an OTA Keybox provisioning request. The format of the - * message is specified in the document Keybox OTA Reprovisioning. If - * use_test_key is true, then the debug model key and id should be - * used. Widevine does not allow all devices to support OTA - * provisioning. Using an OTA provisioned keybox usually lowers a device's - * security profile in the DCSL. + * Generate an OTA Keybox provisioning request. The format of the + * message is specified in the document Keybox OTA Reprovisioning. If + * use_test_key is true, then the debug model key and id should be + * used. Widevine does not allow all devices to support OTA + * provisioning. Using an OTA provisioned keybox usually lowers a device's + * security profile in the DCSL. * - * Parameters: - * [out] buffer: where the provisioning request is stored. - * [in/out] buffer_length: length of the request, in bytes. - * [in] use_test_key: If true, use the debug model key. This is used for - * testing the workflow. + * @param[in] session: handle for the session to be used. + * @param[out] buffer: where the provisioning request is stored. + * @param[in/out] buffer_length: length of the request, in bytes. + * @param[in] use_test_key: If non-zero, use the debug model key. This is used + * for testing the workflow. * - * Returns: - * OEMCrypto_SUCCESS on success - * OEMCrypto_ERROR_SHORT_BUFFER - if buffer_length is too small. - * OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_SHORT_BUFFER - if buffer_length is too small. + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED * Any other error will be logged. * - * Threading: + * @threading * This is an "Initialization and Termination Function" and will not be called * simultaneously with any other function, as if the CDM holds a write lock on * the OEMCrypto system. It will be called only after @@ -4780,51 +4766,51 @@ OEMCryptoResult OEMCrypto_FreeSecureBuffer( * OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING immediately after initialization, * and before any session is opened. * - * Version: + * @version * This method is new in API version 16. */ -OEMCryptoResult OEMCrypto_GenerateOTARequest(uint8_t* buffer, +OEMCryptoResult OEMCrypto_GenerateOTARequest(OEMCrypto_SESSION session, + uint8_t* buffer, size_t* buffer_length, - bool use_test_key); + uint32_t use_test_key); /* * OEMCrypto_ProcessOTAKeybox * - * Description: - * The buffer will be parsed as an OTA Keybox provisioning message, as - * described in the document OTA Keybox Reprovisioning. The - * signature will be verified. The keybox will be decrypted and verified. If - * use_test_key is false, the keybox will be installed permanently. + * The buffer will be parsed as an OTA Keybox provisioning message, as + * described in the document OTA Keybox Reprovisioning. The + * signature will be verified. The keybox will be decrypted and verified. If + * use_test_key is false, the keybox will be installed permanently. * - * If use_test_keybox is true, do not use the real model key, use the debug - * model key specified in OTA Keybox Reprovisioning. + * If use_test_keybox is true, do not use the real model key, use the debug + * model key specified in OTA Keybox Reprovisioning. * - * Parameters: - * [in] buffer: pointer to provisioning response. - * [in] buffer_length: length of the buffer, in bytes. - * [in] use_test_key: If true, use the debug model key. This is used for - * testing the workflow. + * @param[in] session: handle for the session to be used. + * @param[in] buffer: pointer to provisioning response. + * @param[in] buffer_length: length of the buffer, in bytes. + * @param[in] use_test_key: If non-zero, use the debug model key. This is used + * for testing the workflow. * - * Returns: - * OEMCrypto_SUCCESS on success - * OEMCrypto_ERROR_NOT_IMPLEMENTED - * OEMCrypto_ERROR_SIGNATURE_FAILURE - signature of message was wrong. - * OEMCrypto_ERROR_KEYBOX_INVALID - if the keybox was unpacked, but is + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE - signature of message was wrong. + * @retval OEMCrypto_ERROR_KEYBOX_INVALID - if the keybox was unpacked, but is * invalid. - * OEMCrypto_ERROR_WRITE_KEYBOX - could not save keybox. + * @retval OEMCrypto_ERROR_WRITE_KEYBOX - could not save keybox. * Any other error will be logged. * - * Threading: + * @threading * This is an "Initialization and Termination Function" and will not be called * simultaneously with any other function, as if the CDM holds a write lock on * the OEMCrypto system. It will only be called after * OEMCrypto_GenerateOTARequest. * - * Version: + * @version * This method is new in API version 16. */ -OEMCryptoResult OEMCrypto_ProcessOTAKeybox(const uint8_t* buffer, +OEMCryptoResult OEMCrypto_ProcessOTAKeybox(OEMCrypto_SESSION session, + const uint8_t* buffer, size_t buffer_length, - bool use_test_key); + uint32_t use_test_key); /****************************************************************************/ /****************************************************************************/ diff --git a/libwvdrmengine/oemcrypto/test/ota_keybox_test.cpp b/libwvdrmengine/oemcrypto/test/ota_keybox_test.cpp index 1ddaf655..e7d723af 100644 --- a/libwvdrmengine/oemcrypto/test/ota_keybox_test.cpp +++ b/libwvdrmengine/oemcrypto/test/ota_keybox_test.cpp @@ -1,31 +1,169 @@ -// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. // +#include #include +#include #include "OEMCryptoCENC.h" -#include "clock.h" +#include "arraysize.h" #include "log.h" -#include "oec_decrypt_fallback_chain.h" -#include "oec_device_features.h" +#include "oec_key_deriver.h" #include "oec_session_util.h" #include "oec_test_data.h" #include "oemcrypto_session_tests_helper.h" #include "oemcrypto_types.h" #include "platform.h" #include "string_conversions.h" -#include "test_sleep.h" using namespace std; namespace wvoec { +namespace { +const std::string kOption2Label = "WV_KB_REPROV_V02"; +constexpr size_t kLabelLength = 16u; +constexpr size_t kDeviceIdLength = 32u; +constexpr size_t kMinimumRequestLength = + kLabelLength // Start with a label. + + kDeviceIdLength // Fixed length device id. + + sizeof(uint32_t) // cert length. + // variable cert length. + + HMAC_SHA256_SIGNATURE_SIZE; // signature. +static const uint8_t TestKeyPKCS8[] = { + 0x30, 0x82, 0x04, 0xbf, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa9, 0x30, 0x82, 0x04, 0xa5, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, + 0x01, 0x00, 0xa6, 0x33, 0x7f, 0x22, 0x05, 0xae, 0xcf, 0xae, 0x29, 0x67, + 0xa9, 0x3a, 0xe7, 0x08, 0xbf, 0x61, 0x92, 0x62, 0x4a, 0xd4, 0x82, 0x37, + 0x4d, 0x08, 0xa1, 0x77, 0x9f, 0x31, 0xa8, 0x92, 0xc5, 0x8b, 0x85, 0x84, + 0x12, 0x66, 0x52, 0xe6, 0x4b, 0x4c, 0xde, 0xdd, 0x43, 0x85, 0x53, 0xd1, + 0x0c, 0x79, 0x2c, 0xcf, 0x38, 0x4b, 0x72, 0x50, 0xf4, 0x34, 0x15, 0x0b, + 0xbe, 0x06, 0xf7, 0x60, 0x66, 0xda, 0xea, 0x40, 0x25, 0x38, 0x3e, 0xeb, + 0x6e, 0x13, 0x1b, 0xd0, 0x04, 0xd9, 0x4a, 0xa0, 0x7c, 0x7a, 0xb8, 0x64, + 0xde, 0xdb, 0x55, 0x93, 0xed, 0xb9, 0x93, 0x89, 0x83, 0x61, 0x2f, 0x23, + 0x15, 0x91, 0x28, 0xd2, 0x7a, 0x02, 0x69, 0x12, 0x70, 0x19, 0xea, 0xfc, + 0x31, 0xd5, 0xdd, 0x85, 0xeb, 0x81, 0x2f, 0xae, 0xb3, 0x48, 0x94, 0xbe, + 0x57, 0x3b, 0xac, 0x0a, 0x4f, 0x9e, 0x24, 0x2e, 0xae, 0x4e, 0x13, 0x53, + 0x34, 0xe1, 0xcd, 0x66, 0xcc, 0xa6, 0x3a, 0x89, 0x8c, 0x43, 0xef, 0x65, + 0xe7, 0x40, 0xc5, 0x09, 0xb8, 0x36, 0xd6, 0xcd, 0x41, 0xdf, 0x29, 0xbb, + 0x23, 0xa2, 0x54, 0xa5, 0x9f, 0x37, 0x6c, 0xbd, 0x8d, 0xd6, 0x8c, 0x33, + 0xa5, 0xd8, 0x4a, 0xc9, 0x08, 0x35, 0x41, 0xfb, 0xb0, 0x8f, 0x74, 0xdc, + 0xbd, 0x35, 0x01, 0x65, 0xe1, 0x06, 0x6d, 0x41, 0xf4, 0x81, 0x4a, 0xfc, + 0xc3, 0xb3, 0x1c, 0xb7, 0x18, 0xdc, 0x29, 0x4e, 0xea, 0x1b, 0x98, 0xd5, + 0x7d, 0x51, 0x60, 0xcd, 0xfd, 0xdb, 0x74, 0x39, 0x43, 0xa7, 0xc7, 0x0d, + 0xe8, 0x8c, 0xd9, 0xc7, 0xb9, 0xdc, 0x42, 0x08, 0x34, 0x43, 0x2f, 0xf2, + 0x5b, 0xb6, 0x3e, 0x6a, 0x37, 0xb9, 0x08, 0x6a, 0xdf, 0x43, 0x32, 0x0e, + 0x38, 0xd3, 0x3a, 0xeb, 0x13, 0x74, 0xd2, 0x02, 0x36, 0xed, 0xa0, 0x7c, + 0xc8, 0x55, 0xc5, 0xbf, 0x58, 0xbd, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, + 0x82, 0x01, 0x01, 0x00, 0x85, 0x0f, 0x93, 0x88, 0x34, 0x8e, 0x89, 0x3d, + 0x01, 0x6f, 0x39, 0xa0, 0xab, 0xd9, 0x68, 0x07, 0x80, 0xff, 0xea, 0xb3, + 0x0a, 0x71, 0xa5, 0xdd, 0xf4, 0x0f, 0xe6, 0x47, 0x06, 0x94, 0x43, 0x4d, + 0xf9, 0x9e, 0x0c, 0x71, 0x19, 0x8b, 0xc0, 0xdb, 0x91, 0x4e, 0x0a, 0x41, + 0xd3, 0x21, 0xf9, 0xdf, 0x85, 0xcd, 0x7d, 0x5f, 0x81, 0xed, 0x68, 0x25, + 0xce, 0x77, 0xb1, 0x32, 0xb8, 0x98, 0xd8, 0xa0, 0x09, 0x8d, 0x43, 0x7f, + 0x2d, 0x78, 0xa5, 0x8e, 0xec, 0xe4, 0x75, 0x0d, 0x56, 0x5e, 0xf8, 0x69, + 0xf3, 0xf8, 0xe6, 0x27, 0x29, 0xf3, 0x9e, 0x0e, 0xc6, 0x1d, 0x01, 0x2f, + 0x2c, 0x62, 0xe5, 0x60, 0x6b, 0x13, 0x5f, 0x95, 0x50, 0x73, 0xef, 0x86, + 0x00, 0x88, 0xda, 0x89, 0x43, 0xaa, 0x75, 0x2a, 0xdf, 0x76, 0xf9, 0x98, + 0x71, 0x65, 0x96, 0x47, 0x72, 0xec, 0x6a, 0x63, 0xcc, 0xca, 0x06, 0xc7, + 0xb6, 0x58, 0x8d, 0x3e, 0x8a, 0x87, 0x4f, 0x5a, 0x5c, 0x1d, 0x82, 0x40, + 0x61, 0x13, 0xed, 0x6d, 0x21, 0x88, 0x1d, 0xb7, 0x70, 0xda, 0xc9, 0x9d, + 0xb2, 0x29, 0x44, 0x0a, 0xf2, 0xdb, 0xaa, 0x1e, 0xdb, 0x0a, 0x92, 0xf8, + 0x42, 0x1d, 0xe6, 0x75, 0x6a, 0xce, 0x2f, 0xd2, 0xea, 0x9d, 0x71, 0x98, + 0x27, 0x0d, 0xf2, 0xc9, 0x60, 0x85, 0x31, 0x88, 0x1c, 0x91, 0x55, 0xe3, + 0xf8, 0x7e, 0xe6, 0xcf, 0x7b, 0x66, 0x27, 0x45, 0x1e, 0xaa, 0x0a, 0x5a, + 0xfe, 0x07, 0x0a, 0x3a, 0xfd, 0x04, 0xdc, 0xae, 0x5a, 0xf3, 0xc5, 0x3d, + 0x18, 0x36, 0x49, 0x97, 0x5c, 0x42, 0xef, 0x9e, 0x4a, 0xcd, 0xa7, 0x23, + 0xca, 0x17, 0xc4, 0x09, 0x55, 0x15, 0xd4, 0x23, 0x7c, 0x19, 0xb2, 0x24, + 0x87, 0x8f, 0x75, 0x70, 0xca, 0xb4, 0x98, 0x33, 0x8a, 0xf2, 0xdd, 0x15, + 0x8d, 0x27, 0x76, 0xe4, 0xbb, 0xbb, 0x23, 0x2d, 0x02, 0x81, 0x81, 0x00, + 0xdc, 0x3b, 0x21, 0x8d, 0xf2, 0x17, 0xff, 0xc1, 0xc6, 0xfb, 0xe2, 0xdb, + 0x29, 0xed, 0x9e, 0xfd, 0xb6, 0xd5, 0xe7, 0x23, 0x29, 0x86, 0xdc, 0x65, + 0xfc, 0x8b, 0x86, 0x39, 0x52, 0x1e, 0xa4, 0x30, 0x72, 0x14, 0x12, 0x24, + 0x72, 0xb4, 0x97, 0x77, 0x61, 0x7c, 0x34, 0x5a, 0x0a, 0x1d, 0x12, 0xfe, + 0xc7, 0x1f, 0x06, 0x79, 0x8c, 0xf5, 0x41, 0xdd, 0x79, 0x8f, 0xeb, 0x17, + 0xf3, 0x32, 0x32, 0x13, 0x37, 0xee, 0x73, 0xeb, 0x82, 0xfa, 0x7b, 0x55, + 0x16, 0xb0, 0x3e, 0x2f, 0x6f, 0xb6, 0xa6, 0x38, 0x99, 0xaf, 0xde, 0xfd, + 0x3a, 0x48, 0xa2, 0x95, 0x70, 0x14, 0x06, 0xf9, 0x10, 0x0f, 0x48, 0x72, + 0x0d, 0x48, 0x69, 0xfc, 0x81, 0xf1, 0x07, 0x5c, 0x99, 0x44, 0xe9, 0x02, + 0xd5, 0x61, 0x36, 0x31, 0x64, 0x02, 0x5a, 0x1d, 0x3e, 0xae, 0xde, 0x08, + 0xd2, 0xde, 0x42, 0xac, 0xf1, 0xe1, 0x38, 0x9f, 0x02, 0x81, 0x81, 0x00, + 0xc1, 0x31, 0xe3, 0x45, 0xec, 0x53, 0xa5, 0x56, 0xe0, 0xc1, 0xe3, 0xf2, + 0xeb, 0xb0, 0xe5, 0x84, 0xdd, 0x56, 0x59, 0x7c, 0xf4, 0x65, 0x66, 0x8c, + 0x9c, 0x66, 0x55, 0x2a, 0x2c, 0x3c, 0x46, 0xf7, 0xac, 0x36, 0xd8, 0x2f, + 0x27, 0x97, 0x57, 0x64, 0x6e, 0xc7, 0x5f, 0x43, 0xf9, 0x82, 0x27, 0xf3, + 0xc4, 0xfa, 0xc6, 0xb1, 0xea, 0x2d, 0xcc, 0x36, 0x3a, 0x37, 0x22, 0xb6, + 0x7e, 0x6a, 0x25, 0xab, 0x1a, 0xd2, 0x3e, 0x38, 0x38, 0x9d, 0x04, 0xc0, + 0xc7, 0x4a, 0xa2, 0x38, 0xb4, 0xcf, 0x9c, 0x97, 0x4c, 0x03, 0x76, 0x37, + 0x86, 0x09, 0x1e, 0x25, 0x2b, 0x67, 0x8e, 0x7b, 0xce, 0x3d, 0x50, 0xf6, + 0x7a, 0x8b, 0x00, 0x23, 0x48, 0xda, 0x6e, 0xbe, 0x4c, 0x23, 0xdb, 0x9c, + 0x4f, 0x3f, 0xa9, 0x18, 0x59, 0xf6, 0xc4, 0x33, 0xc5, 0xaa, 0x75, 0x40, + 0xf7, 0xba, 0xfc, 0x83, 0x40, 0x36, 0x85, 0x23, 0x02, 0x81, 0x80, 0x68, + 0xd9, 0xf6, 0x35, 0xc0, 0x87, 0x50, 0x8b, 0x0f, 0x93, 0xa9, 0x04, 0x33, + 0x48, 0x20, 0xa4, 0x26, 0xc2, 0x5c, 0x53, 0x4f, 0x58, 0x17, 0xe2, 0xae, + 0x84, 0x37, 0x19, 0x5f, 0x51, 0x9b, 0x56, 0x3d, 0x59, 0xf4, 0xf1, 0x49, + 0x73, 0x55, 0x91, 0xce, 0xe5, 0xf5, 0x7e, 0xd0, 0xc5, 0xda, 0xdf, 0x56, + 0x2a, 0x1d, 0x49, 0x0d, 0xa5, 0x4f, 0x00, 0x84, 0xf9, 0xd2, 0x32, 0x0a, + 0xe5, 0x61, 0x15, 0xe9, 0x51, 0x2b, 0xfb, 0x7a, 0xd6, 0x8a, 0x95, 0x8e, + 0x41, 0xc6, 0xb9, 0x8a, 0xf4, 0x68, 0xdb, 0x15, 0xc0, 0xb7, 0xe7, 0xd4, + 0x31, 0xf4, 0xc6, 0x35, 0x20, 0x33, 0xd9, 0xac, 0x9d, 0xba, 0x1e, 0x22, + 0xd8, 0xd1, 0x2d, 0x19, 0x28, 0x8a, 0x1a, 0xba, 0x16, 0x26, 0xe1, 0xe4, + 0x79, 0x6d, 0xf5, 0xc1, 0xe9, 0xa4, 0xc1, 0xbb, 0xb0, 0x41, 0xa1, 0xed, + 0xd3, 0x47, 0xe7, 0x53, 0x19, 0xa9, 0x7d, 0x02, 0x81, 0x81, 0x00, 0xb4, + 0xd6, 0x5e, 0xb7, 0xd7, 0xe3, 0xe0, 0x13, 0x37, 0x65, 0x26, 0x5a, 0xff, + 0x75, 0x61, 0x12, 0x02, 0x20, 0xce, 0xb9, 0x21, 0x07, 0x3d, 0x7b, 0x86, + 0xf6, 0x5e, 0xe7, 0x8f, 0xea, 0x88, 0x3f, 0x53, 0x4b, 0x2f, 0x06, 0xcc, + 0x97, 0x64, 0x2d, 0x55, 0x68, 0x77, 0xea, 0xe7, 0xc5, 0x86, 0x62, 0x2e, + 0xd2, 0xd2, 0x64, 0x3d, 0x20, 0xcb, 0x53, 0x43, 0x20, 0xd2, 0xf4, 0x61, + 0xd6, 0x38, 0x16, 0x36, 0x8f, 0xef, 0xbf, 0xae, 0x76, 0x83, 0xb9, 0x73, + 0x92, 0x8f, 0xd0, 0x66, 0xa7, 0x23, 0x1b, 0x98, 0x02, 0x71, 0x88, 0xbd, + 0x85, 0x11, 0x5b, 0x97, 0x8a, 0x62, 0x9b, 0xce, 0xcc, 0x24, 0x59, 0xe3, + 0x10, 0xf3, 0x7b, 0x13, 0xb9, 0xab, 0x09, 0xa3, 0xb9, 0xb8, 0xda, 0x52, + 0x6f, 0xf3, 0x77, 0x20, 0xd6, 0xd4, 0x86, 0xe5, 0x92, 0x8e, 0x18, 0xd7, + 0x0a, 0x87, 0x4c, 0xd0, 0x31, 0x78, 0x63, 0x02, 0x81, 0x81, 0x00, 0xc2, + 0x8c, 0xea, 0xa2, 0x9b, 0xfa, 0x42, 0x98, 0x70, 0x5a, 0xf9, 0x73, 0x78, + 0xee, 0xd6, 0x2b, 0x3e, 0x5b, 0xf4, 0x2f, 0x84, 0x60, 0x71, 0x9b, 0xce, + 0xf4, 0x9b, 0x88, 0x25, 0x0a, 0x29, 0x41, 0x4b, 0x4d, 0x36, 0xcf, 0xd9, + 0x86, 0xac, 0x75, 0xa2, 0xed, 0x56, 0x5b, 0x6e, 0x87, 0x1e, 0x32, 0x04, + 0x13, 0xaf, 0xa5, 0x1e, 0xf5, 0x0f, 0x9d, 0x93, 0x4a, 0x29, 0x90, 0x23, + 0x0e, 0xf9, 0x8e, 0xfd, 0x2d, 0xfe, 0x2b, 0x79, 0xa6, 0x03, 0x6c, 0xcd, + 0x01, 0xee, 0xba, 0x69, 0xb3, 0xb9, 0xd4, 0xc8, 0x99, 0x0f, 0x72, 0xba, + 0x59, 0x22, 0xdc, 0x2d, 0xc3, 0x97, 0x8c, 0xa7, 0xb3, 0xbf, 0x60, 0xe1, + 0x61, 0xe5, 0xd4, 0x51, 0x6f, 0x36, 0x9c, 0x9a, 0xb8, 0x1b, 0x52, 0xec, + 0x13, 0xf3, 0xa7, 0xdb, 0xdb, 0x5d, 0x89, 0x2d, 0xd7, 0x02, 0x96, 0xaf, + 0xeb, 0x72, 0x8d, 0xd5, 0x56, 0x3b, 0x3a}; + +// TODO(fredgc): duplicate code. Move to common util package. +// Return a printable string from data. If all the characters are printable, +// then just use the string. Otherwise, convert to hex. +std::string MaybeHex(const uint8_t* data, size_t length) { + for (size_t i = 0; i < length; i++) { + if (!isprint(data[i])) return "0x" + wvcdm::HexEncode(data, length); + } + return std::string(reinterpret_cast(data), length); +} +std::string MaybeHex(const std::vector& data) { + return MaybeHex(data.data(), data.size()); +} + +std::vector GetModelKey(const std::vector& device_id) { + std::vector keymint_key( + TestKeyPKCS8, TestKeyPKCS8 + wvcdm::ArraySize(TestKeyPKCS8)); + keymint_key.insert(keymint_key.end(), device_id.begin(), device_id.end()); + std::vector key(SHA256_DIGEST_LENGTH); + SHA256(keymint_key.data(), keymint_key.size(), key.data()); + key.resize(wvoec::KEY_SIZE); // Truncate to standard AES 128 key. + return key; +} + +} // namespace + class OTAKeyboxProvisioningTest : public ::testing::Test, public SessionUtil { protected: void SetUp() override { ::testing::Test::SetUp(); - wvcdm::TestSleep::SyncFakeClock(); const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name()); @@ -49,5 +187,109 @@ TEST_F(OTAKeyboxProvisioningTest, BasicTest) { ASSERT_EQ(result, OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING); cout << " " << "OTA Keybox functions supported. Device needs provisioning." << endl; + // TODO(fredgc): Make sure that partners can use a test cert when use_test_key + // is true. + constexpr uint32_t use_test_key = 1; + size_t request_length = 0; + std::vector request; + + OEMCrypto_SESSION session_id; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_OpenSession(&session_id)); + + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_GenerateOTARequest(session_id, nullptr, &request_length, + use_test_key)); + ASSERT_NE(request_length, 0u); + request.resize(request_length); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GenerateOTARequest(session_id, request.data(), + &request_length, use_test_key)); + ASSERT_GT(request_length, kMinimumRequestLength); + request.resize(request_length); + // First 16 bytes should match the label for Option 1 or 2. + // TODO(fredgc): Write tests for Option 1 -- only Option 2 is tested now. + EXPECT_EQ(kOption2Label.length(), kLabelLength); + const uint8_t* label_ptr = request.data(); + EXPECT_EQ(kOption2Label, std::string(label_ptr, label_ptr + kLabelLength)); + // Next 32 bytes are the device id. + const uint8_t* device_id_ptr = label_ptr + kLabelLength; + const std::vector device_id(device_id_ptr, + device_id_ptr + kDeviceIdLength); + LOGD("Device id is %s", MaybeHex(device_id).c_str()); + // Next 4 bytes say that this is a cert. + uint32_t cert_length; + const uint8_t* cert_length_ptr = device_id_ptr + kDeviceIdLength; + memcpy(&cert_length, cert_length_ptr, sizeof(uint32_t)); + cert_length = ntohl(cert_length); + LOGD("Cert Length is 0x%x = %u", cert_length, cert_length); + ASSERT_EQ(request_length, kMinimumRequestLength + cert_length); + const uint8_t* cert_ptr = cert_length_ptr + sizeof(uint32_t); + const std::vector cert(cert_ptr, cert_ptr + cert_length); + LOGD("x509 attestation cert = %s", MaybeHex(cert).c_str()); + const uint8_t* signature_ptr = cert_ptr + cert_length; + const std::vector signature( + signature_ptr, signature_ptr + HMAC_SHA256_SIGNATURE_SIZE); + +#if 0 + // This unit test only works with the test model key. + const std::vector model_key = + wvcdm::a2b_hex("0102030405060708090a0b0c0d0e0f10"); +#else + // This unit test only works with the test cert's private key. + const std::vector model_key = GetModelKey(device_id); +#endif + // The server should derive the same set of keys as the client. + const std::string mac_label = "WV_SIGN"; + std::vector mac_context(mac_label.begin(), mac_label.end()); + mac_context.push_back(0); + std::copy(cert.begin(), cert.end(), std::back_inserter(mac_context)); + std::copy(device_id.begin(), device_id.end(), + std::back_inserter(mac_context)); + uint32_t bit_size = MAC_KEY_SIZE * 8 * 2; + std::string bit_size_string = wvcdm::EncodeUint32(bit_size); + std::copy(bit_size_string.begin(), bit_size_string.end(), + std::back_inserter(mac_context)); + std::string enc_label = "WV_ENCRYPT"; + std::vector enc_context(enc_label.begin(), enc_label.end()); + enc_context.push_back(0); + std::copy(cert.begin(), cert.end(), std::back_inserter(enc_context)); + std::copy(device_id.begin(), device_id.end(), + std::back_inserter(enc_context)); + bit_size = KEY_SIZE * 8; + bit_size_string = wvcdm::EncodeUint32(bit_size); + std::copy(bit_size_string.begin(), bit_size_string.end(), + std::back_inserter(enc_context)); + KeyDeriver keys; + keys.DeriveKeys(model_key.data(), mac_context, enc_context); + const std::vector message( + request.data(), + request.data() + request.size() - HMAC_SHA256_SIGNATURE_SIZE); + // Once the keys have been derived, the server should verify the signature. + std::vector computed_signature; + keys.ClientSignBuffer(message, &computed_signature); + ASSERT_EQ(signature, computed_signature); + + // The server should randomly pick an iv. + uint8_t iv[wvoec::KEY_IV_SIZE]; + EXPECT_EQ(GetRandBytes(iv, KEY_IV_SIZE), 1); + // Encrypt the keybox. + std::vector encrypted_keybox(sizeof(WidevineKeybox)); + keys.CBCEncrypt(reinterpret_cast(&kTestKeybox), + encrypted_keybox.data(), sizeof(WidevineKeybox), iv); + std::vector response(iv, iv + wvoec::KEY_IV_SIZE); + std::copy(encrypted_keybox.begin(), encrypted_keybox.end(), + std::back_inserter(response)); + std::vector response_signature; + // Sign the iv + encrypted keybox. + keys.ServerSignBuffer(response.data(), response.size(), &response_signature); + std::copy(response_signature.begin(), response_signature.end(), + std::back_inserter(response)); + // Finally, send the response back to the device. + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_ProcessOTAKeybox(session_id, response.data(), + response.size(), use_test_key)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CloseSession(session_id)); + // After installation, the keybox should be valid. + EXPECT_EQ(OEMCrypto_IsKeyboxValid(), OEMCrypto_SUCCESS); } } // namespace wvoec From 581aa1702b02d67d313834230e85faefe58607d4 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Fri, 17 Sep 2021 09:37:12 -0700 Subject: [PATCH 05/12] Assume Keybox provisioning type if OTA Keybox is supported. [ Merge of http://go/wvgerrit/133943 and http://go/wvgerrit/134043 ] Certain OEMCrypto implementations will not report their provisioning method if the keybox is invalid. If the OEMCrypto implementation supports OTA keybox provisioning and does not report its provisioning method, then keybox provisioning is assumed. Bug: 187646550 Test: unit/integration/GtsMediaTestCases Merged-In: Ie7753546e53fc73fd59803958e88edf416ee5336 Change-Id: Ie7753546e53fc73fd59803958e88edf416ee5336 --- libwvdrmengine/cdm/core/src/crypto_session.cpp | 5 +++++ libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 3bcee3ce..11d5d9f3 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -301,6 +301,11 @@ CdmResponseType CryptoSession::GetProvisioningMethod( break; case OEMCrypto_ProvisioningError: default: + if (static_cast(method) == 0 && needs_keybox_provisioning_) { + LOGW("Overriding provisioning method, assuming keybox"); + type = kClientTokenKeybox; + break; + } LOGE("OEMCrypto_GetProvisioningMethod failed: method = %d", static_cast(method)); metrics_->oemcrypto_provisioning_method_.SetError(method); diff --git a/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp b/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp index 180ab424..af5e367e 100644 --- a/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp +++ b/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp @@ -162,6 +162,8 @@ CdmResponseType OtaKeyboxProvisioner::GetProvisioningRequest( static_cast(result)); client_id->Clear(); } + LOGI("OTA request generated"); + LOGV("ota_request_data = %s", b2a_hex(ota_request_data).c_str()); OtaRequest* ota_request = prov_request.mutable_android_ota_keybox_request(); ota_request->set_ota_request(ota_request_data); @@ -255,8 +257,8 @@ CdmResponseType OtaKeyboxProvisioner::HandleProvisioningResponse( const CdmResponseType result = crypto_session_->LoadOtaProvisioning( kProductionKeybox, ota_response_data); if (result == NO_ERROR) { - LOGV("OTA response successfully processed: ota_response_data = %s", - b2a_hex(ota_response_data).c_str()); + LOGI("OTA response successfully processed"); + LOGV("ota_response_data = %s", b2a_hex(ota_response_data).c_str()); fallback_policy_->MarkProvisioned(); response_received_ = true; } else { From 5975b4e70abbcd2319df0451f73c51bd9e9c342c Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Mon, 27 Sep 2021 22:22:24 -0700 Subject: [PATCH 06/12] Formatted OTA keybox function comments. The API comments for the two new OTA keybox OEMCrypto functions required formatting to be compatible with the doxygen comment strings. Bug: 190505461 Test: Android unit tests and GTS Merged-In: Ia45dc9d727a2a904170912193709cd9416b8fe27 Change-Id: Ia45dc9d727a2a904170912193709cd9416b8fe27 (cherry picked from commit 9f2364cefd71d00b87d606dff3a3bb3287fa673f) --- .../oemcrypto/include/OEMCryptoCENC.h | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index e40a340c..439b5b76 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -624,9 +624,10 @@ typedef enum OEMCrypto_ProvisioningMethod { #define OEMCrypto_MinorAPIVersion _oecc108 #define OEMCrypto_AllocateSecureBuffer _oecc109 #define OEMCrypto_FreeSecureBuffer _oecc110 -// Reserved 111-112. +// Reserved 111-112 #define OEMCrypto_GenerateOTARequest _oecc113 #define OEMCrypto_ProcessOTAKeybox _oecc114 +// Reserved 115-121 // clang-format on /// @addtogroup initcontrol @@ -4737,9 +4738,7 @@ OEMCryptoResult OEMCrypto_FreeSecureBuffer( * security implications of using Keybox OTA Provisioning. */ -/* - * OEMCrypto_GenerateOTARequest - * +/** * Generate an OTA Keybox provisioning request. The format of the * message is specified in the document Keybox OTA Reprovisioning. If * use_test_key is true, then the debug model key and id should be @@ -4749,7 +4748,7 @@ OEMCryptoResult OEMCrypto_FreeSecureBuffer( * * @param[in] session: handle for the session to be used. * @param[out] buffer: where the provisioning request is stored. - * @param[in/out] buffer_length: length of the request, in bytes. + * @param[in,out] buffer_length: length of the request, in bytes. * @param[in] use_test_key: If non-zero, use the debug model key. This is used * for testing the workflow. * @@ -4773,28 +4772,27 @@ OEMCryptoResult OEMCrypto_GenerateOTARequest(OEMCrypto_SESSION session, uint8_t* buffer, size_t* buffer_length, uint32_t use_test_key); -/* - * OEMCrypto_ProcessOTAKeybox - * + +/** * The buffer will be parsed as an OTA Keybox provisioning message, as * described in the document OTA Keybox Reprovisioning. The * signature will be verified. The keybox will be decrypted and verified. If - * use_test_key is false, the keybox will be installed permanently. + * |use_test_key| is false, the keybox will be installed permanently. * - * If use_test_keybox is true, do not use the real model key, use the debug + * If |use_test_key| is true, do not use the real model key, use the debug * model key specified in OTA Keybox Reprovisioning. * * @param[in] session: handle for the session to be used. * @param[in] buffer: pointer to provisioning response. * @param[in] buffer_length: length of the buffer, in bytes. * @param[in] use_test_key: If non-zero, use the debug model key. This is used - * for testing the workflow. + * for testing the workflow. * * @retval OEMCrypto_SUCCESS on success * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE - signature of message was wrong. * @retval OEMCrypto_ERROR_KEYBOX_INVALID - if the keybox was unpacked, but is - * invalid. + * invalid. * @retval OEMCrypto_ERROR_WRITE_KEYBOX - could not save keybox. * Any other error will be logged. * From c21b756451710fd49f71f079c46aefc2892017e2 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Wed, 13 Oct 2021 21:06:10 +0000 Subject: [PATCH 07/12] Use MediaDrm property to ignore installed keybox Merge from Widevine repo of http://go/wvgerrit/135984 If the MediaDrm property string debugIgnoreKeyboxCount is set to 1, then the keybox will be ignored on the next initialization. This will force an OTA keybox reprovisioning. Equivalently, a 1 may be written to the file L1/debug_ignore_keybox_count.txt. In order to test a failed reprovisioning step, a value of 2 may be used. Bug: 187646550 Merged-In: Ie7d34a8b355398855f4ec43dd95dd73c5907bdeb Change-Id: Ie7d34a8b355398855f4ec43dd95dd73c5907bdeb --- libwvdrmengine/cdm/core/include/cdm_engine.h | 6 ++ .../cdm/core/include/crypto_session.h | 4 + .../cdm/core/include/oemcrypto_adapter.h | 4 + .../cdm/core/src/crypto_session.cpp | 5 + .../core/src/oemcrypto_adapter_dynamic.cpp | 96 ++++++++++++++++++- .../include/wv_content_decryption_module.h | 2 + .../cdm/src/wv_content_decryption_module.cpp | 5 + libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp | 12 +++ .../mediadrm/src_hidl/WVDrmPlugin.cpp | 12 +++ 9 files changed, 144 insertions(+), 2 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index e85cc9ba..afa73db2 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -303,6 +303,12 @@ class CdmEngine { virtual size_t SessionSize() const { return session_map_.Size(); } + // This tells the OEMCrypto adapter to ignore the next |count| keyboxes and + // report that it needs provisioning instead. + static CdmResponseType SetDebugIgnoreKeyboxCount(uint32_t count) { + return CryptoSession::SetDebugIgnoreKeyboxCount(count); + } + static CdmResponseType ParseDecryptHashString(const std::string& hash_string, CdmSessionId* id, uint32_t* frame_number, diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index f45b547f..b48cd4aa 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -295,6 +295,10 @@ class CryptoSession { bool needs_keybox_provisioning() const { return needs_keybox_provisioning_; } + // This tells the OEMCrypto adapter to ignore the next |count| keyboxes and + // report that it needs provisioning instead. + static CdmResponseType SetDebugIgnoreKeyboxCount(uint32_t count); + // Returns a system-wide singleton instance of SystemFallbackPolicy // to be used for communicating OTA keybox provisioning state between // apps. Returns a null pointer if OTA provisioning is not supported, diff --git a/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h b/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h index 8bbee0f8..8c9dc75c 100644 --- a/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h +++ b/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h @@ -16,6 +16,10 @@ namespace wvcdm { OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( bool* needs_keybox_provisioning); +// This tells the OEMCrypto adapter to ignore the next |count| keyboxes and +// report that it needs provisioning instead. +OEMCryptoResult OEMCrypto_SetDebugIgnoreKeyboxCount(uint32_t count); + // This attempts to open a session at the desired security level. // If one level is not available, the other will be used instead. OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session, diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 11d5d9f3..349cd368 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -3031,6 +3031,11 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( return sts; } +CdmResponseType CryptoSession::SetDebugIgnoreKeyboxCount(uint32_t count) { + OEMCryptoResult status = OEMCrypto_SetDebugIgnoreKeyboxCount(count); + return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetDebugIgnoreKeyboxCount"); +} + okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() { const auto getter = [&]() -> okp::SystemFallbackPolicy* { // If not set, then OTA keybox provisioning is not supported or diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp index d28bf1b4..8d57fb3e 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -626,6 +627,70 @@ class WatchDog { uint32_t uid_; }; +// The DebugIgnoreKeyboxCount functions are related to testing the Keybox OTA +// Reprovisioning solution. If a positive counter is set, then the keybox will +// be ignored through that many initializations. +std::string GetIgnoreCountFile() { + std::string path; + if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL1, + &path)) { + LOGW("DebugIgnoreKeyboxCount: Unable to get base path"); + path = "/data/"; + } + path += "debug_ignore_keybox_count.txt"; + return path; +} + +uint32_t GetDebugIgnoreKeyboxCount() { + const std::string filename = GetIgnoreCountFile(); + wvcdm::FileSystem file_system; + if (!file_system.Exists(filename)) { + return 0; + } + auto file = file_system.Open(filename, file_system.kReadOnly); + if (!file) { + LOGE("Error opening %s", filename.c_str()); + return 0; + } + ssize_t size = file_system.FileSize(filename); + std::string contents(size, ' '); + ssize_t size_read = file->Read(const_cast(contents.data()), size); + if (size != size_read) { + LOGE("Short ignore_debug_keybox_count = %zu", size_read); + return 0; + } + std::istringstream ss(contents); + uint32_t count = 0; + ss >> count; + if (ss.fail()) { + LOGE("Could not parse an integer from '%s'", contents.c_str()); + count = 0; + } + LOGD("Using IgnoreDebugKeyboxCount = %u", count); + return count; +} + +OEMCryptoResult SetDebugIgnoreKeyboxCount(uint32_t count) { + const std::string filename = GetIgnoreCountFile(); + wvcdm::FileSystem file_system; + auto file = + file_system.Open(filename, file_system.kCreate | file_system.kTruncate); + if (!file) { + LOGE("Could not create file %s", filename.c_str()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const std::string contents = std::to_string(count); + ssize_t size = contents.size(); + ssize_t size_written = file->Write(contents.data(), size); + if (size != size_written) { + LOGE("Wrote %zd bytes of %s, not %zd, to file %s", size_written, + contents.c_str(), size, filename.c_str()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + LOGD("Wrote %u to %s", count, filename.c_str()); + return OEMCrypto_SUCCESS; +} + struct LevelSession { FunctionPointers* fcn; OEMCrypto_SESSION session; @@ -1218,7 +1283,12 @@ OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( if (status != OEMCrypto_SUCCESS) return status; const OEMCryptoResult keybox_status = gAdapter->ValidateOrInstallKeyboxOrCert(); - if (keybox_status == OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING) { + uint32_t ignore_count = GetDebugIgnoreKeyboxCount(); + if (keybox_status == OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING || + ignore_count > 0) { + if (ignore_count > 0) { + LOGD("Ignoring keybox status %d", static_cast(keybox_status)); + } *needs_keybox_provisioning = true; return OEMCrypto_SUCCESS; } @@ -1494,6 +1564,10 @@ OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(uint8_t* public_cert, } return OEMCrypto_ERROR_NOT_IMPLEMENTED; } + +OEMCryptoResult OEMCrypto_SetDebugIgnoreKeyboxCount(uint32_t count) { + return SetDebugIgnoreKeyboxCount(count); +} } // namespace wvcdm extern "C" OEMCryptoResult OEMCrypto_SetSandbox(const uint8_t* sandbox_id, @@ -2818,5 +2892,23 @@ extern "C" OEMCryptoResult OEMCrypto_ProcessOTAKeybox(OEMCrypto_SESSION session, const FunctionPointers* fcn = gAdapter->GetFunctionPointers(kLevelDefault); if (!fcn) return OEMCrypto_ERROR_UNKNOWN_FAILURE; if (fcn->ProcessOTAKeybox == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return fcn->ProcessOTAKeybox(session, buffer, buffer_length, use_test_key); + const OEMCryptoResult result = + fcn->ProcessOTAKeybox(session, buffer, buffer_length, use_test_key); + if (result == OEMCrypto_SUCCESS) { + // If the new keybox was installed, and we had been told to ignore a valid + // keybox, then we should now decrement the ignore counter. + int32_t ignore_count = GetDebugIgnoreKeyboxCount(); + if (ignore_count > 0) { + ignore_count--; + const OEMCryptoResult save_result = + SetDebugIgnoreKeyboxCount(ignore_count); + if (save_result == OEMCrypto_SUCCESS) { + LOGD("Installed OTA keybox. Ignore count is now %u", ignore_count); + } else { + LOGE("Installed OTA keybox. save ignore count failed %d, count=%u", + static_cast(save_result), ignore_count); + } + } + } + return result; } diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index 4095a6ca..06a75613 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -159,6 +159,8 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler { // Closes the CdmEngine and sessions associated with the given CdmIdentifier. virtual CdmResponseType CloseCdm(const CdmIdentifier& identifier); + virtual CdmResponseType SetDebugIgnoreKeyboxCount(uint32_t count); + virtual CdmResponseType SetDecryptHash(const std::string& hash_data, CdmSessionId* session_id); virtual CdmResponseType GetDecryptHashError(const CdmSessionId& session_id, diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 53763827..ded47c25 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -500,6 +500,11 @@ CdmResponseType WvContentDecryptionModule::CloseCdm( return NO_ERROR; } +CdmResponseType WvContentDecryptionModule::SetDebugIgnoreKeyboxCount( + uint32_t count) { + return CdmEngine::SetDebugIgnoreKeyboxCount(count); +} + CdmResponseType WvContentDecryptionModule::SetDecryptHash( const std::string& hash_data, CdmSessionId* id) { if (id == nullptr) { diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index 71ec8a31..37ce2fa7 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -667,6 +668,17 @@ status_t WVDrmPlugin::setPropertyString(const String8& name, } else { mCdmIdentifier.origin = value.string(); } + } else if (name == "debugIgnoreKeyboxCount") { + std::istringstream ss(value.string()); + uint32_t count = 0; + ss >> count; + if (ss.fail()) { + ALOGE("Could not parse an integer from '%s'", value.string()); + count = 0; + return Status::BAD_VALUE; + } + CdmResponseType res = mCDM->SetDebugIgnoreKeyboxCount(count); + return mapCdmResponseType(res); } else if (name == "decryptHash") { CdmSessionId sessionId; CdmResponseType res = diff --git a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp index cb680fff..cb3709f9 100644 --- a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "WVDrmPlugin.h" @@ -1431,6 +1432,17 @@ Return WVDrmPlugin::setPropertyString(const hidl_string& propertyName, return Status::BAD_VALUE; } } + } else if (name == "debugIgnoreKeyboxCount") { + std::istringstream ss(_value); + uint32_t count = 0; + ss >> count; + if (ss.fail()) { + ALOGE("Could not parse an integer from '%s'", _value.c_str()); + count = 0; + return Status::BAD_VALUE; + } + CdmResponseType res = mCDM->SetDebugIgnoreKeyboxCount(count); + return mapCdmResponseType(res); } else if (name == "decryptHash") { wvcdm::CdmSessionId sessionId; CdmResponseType res = From 80463a0eea1f5310be027120555aac2f39699849 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Thu, 14 Oct 2021 03:17:53 +0000 Subject: [PATCH 08/12] Correct build error in non-hidl WVDrmPlugin This fix was missing in the last merge of http://go/wvgerrit/135063 Bug: 187646550 Merged-In: I2b2252f8335c36325fd76d92ac26b9fbfcff5362 Change-Id: I2b2252f8335c36325fd76d92ac26b9fbfcff5362 --- libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index 37ce2fa7..b7e67d53 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -675,7 +675,7 @@ status_t WVDrmPlugin::setPropertyString(const String8& name, if (ss.fail()) { ALOGE("Could not parse an integer from '%s'", value.string()); count = 0; - return Status::BAD_VALUE; + return android::BAD_VALUE; } CdmResponseType res = mCDM->SetDebugIgnoreKeyboxCount(count); return mapCdmResponseType(res); From eb846100d452f975568603b39ae81c3e2a4da164 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Wed, 13 Oct 2021 21:04:09 +0000 Subject: [PATCH 09/12] Update OTA Keybox tests Merge from Widevine repo of http://go/wvgerrit/135982 The basic test was failing when using the testbed oemcrypto because the testbed deletes its keybox on each initialization. The test would terminate and re-initialize oemcrypto whenever all the crypto sessions are deleted. This has been fixed by holding a crypto session alive until the end of the test. bug: 187646550 Test: test only code Merged-In: I48a3771bf5fd4aae8d262b8c7bf42f004d9b9f4c Change-Id: I48a3771bf5fd4aae8d262b8c7bf42f004d9b9f4c --- .../cdm/core/test/keybox_ota_test.cpp | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp b/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp index 36dd09a9..44b5c81b 100644 --- a/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp +++ b/libwvdrmengine/cdm/core/test/keybox_ota_test.cpp @@ -82,6 +82,27 @@ TEST_F(CdmOtaKeyboxTest, BasicTest) { FileSystem file_system; TestCdmEngine cdm_engine(&file_system, std::shared_ptr(new EngineMetrics)); + // Create a singleton crypto session to prevent OEMCrypto from being + // terminated. This is really only needed when running the test against the + // testbed OEMCrypto which does not save its keybox across init/terminate. + metrics::CryptoMetrics crypto_metrics; + std::unique_ptr crypto_session( + CryptoSession::MakeCryptoSession(&crypto_metrics)); + // Remove any existing certificate. + cdm_engine.Unprovision(kSecurityLevelL1); + std::string system_id; + CdmResponseType system_id_status = + cdm_engine.QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &system_id); + if (system_id_status == NO_ERROR) { + std::cout << " " + << "System ID before test: " << system_id << "\n"; + } else { + std::cout << " " + << "Could not find system id before test. "; + PrintTo(system_id_status, &std::cout); + std::cout << "\n"; + } + CdmSessionId session_id; ConfigTestEnv config = WvCdmTestBase::default_config_; @@ -97,6 +118,17 @@ TEST_F(CdmOtaKeyboxTest, BasicTest) { } std::cout << "First provisioning process.\n"; Provision(&cdm_engine); + system_id_status = + cdm_engine.QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &system_id); + if (system_id_status == NO_ERROR) { + std::cout << " " + << "System ID after first provisioning: " << system_id << "\n"; + } else { + std::cout << " " + << "Could not find system id after first provisioning. "; + PrintTo(system_id_status, &std::cout); + std::cout << "\n"; + } // After the first provisioning pass, we try to open a session again. If the // first provisioning was to install a keybox, this provisioning should be to @@ -111,6 +143,17 @@ TEST_F(CdmOtaKeyboxTest, BasicTest) { } std::cout << "Second provisioning process.\n"; Provision(&cdm_engine); + system_id_status = + cdm_engine.QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &system_id); + if (system_id_status == NO_ERROR) { + std::cout << " " + << "System ID after second provisioning: " << system_id << "\n"; + } else { + std::cout << " " + << "Could not find system id after second provisioning. "; + PrintTo(system_id_status, &std::cout); + std::cout << "\n"; + } // After the second provisioning pass, we should be able to open a session // and continue. From 28b45c4f1bd65bc6c212b84a2d3b8039236dda35 Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Fri, 15 Oct 2021 19:32:29 -0700 Subject: [PATCH 10/12] Update fallback policy for fast fallback. [ Cherry-pick of http://ag/16064433 ] [ Merge of http://go/wvgerrit/136329 ] CDM core has been updated to support very short fallback durations in the case of failures during OTA keybox provisioning. This is intended to be used during testing via specialized developer apps or GTS tests. Bug: 187646550 Test: Android unit tests Change-Id: I8a75d2e1c404d6caed535b087e8dd29da5c21b83 --- .../cdm/core/include/okp_fallback_policy.h | 16 ++- libwvdrmengine/cdm/core/include/okp_info.h | 1 + .../cdm/core/src/okp_fallback_policy.cpp | 42 +++++-- .../core/test/okp_fallback_policy_test.cpp | 105 ++++++++++++++++++ 4 files changed, 153 insertions(+), 11 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/okp_fallback_policy.h b/libwvdrmengine/cdm/core/include/okp_fallback_policy.h index 36129a63..419ab02c 100644 --- a/libwvdrmengine/cdm/core/include/okp_fallback_policy.h +++ b/libwvdrmengine/cdm/core/include/okp_fallback_policy.h @@ -22,13 +22,14 @@ static constexpr int64_t kSecondsPerDay = kSecondsPerHour * 24; // Initial backoff duration. Subsequent backoff durations for the // same engine will double its previous duration. static constexpr int64_t kAverageInitialBackoffDuration = kSecondsPerDay; -static constexpr int64_t kInitalBackoffDurationDelta = kSecondsPerHour * 12; +static constexpr int64_t kFastBackoffDuration = 30; // 30 seconds. +static constexpr int64_t kInitialBackoffDurationDelta = kSecondsPerHour * 12; // Minimum backoff duration which an device will be required to // backoff the first time. static constexpr int64_t kMinInitialBackoffDuration = - kAverageInitialBackoffDuration - kInitalBackoffDurationDelta; + kAverageInitialBackoffDuration - kInitialBackoffDurationDelta; static constexpr int64_t kMaxInitialBackoffDuration = - kAverageInitialBackoffDuration + kInitalBackoffDurationDelta; + kAverageInitialBackoffDuration + kInitialBackoffDurationDelta; // SystemFallbackPolicy is a centralized OKP state manager which allows // multiple CDM engines to communicate between each other. In a production @@ -70,6 +71,9 @@ class SystemFallbackPolicy { bool IsProvisioned(); bool IsInFallbackMode(); + void SetDefaultBackoffDurationRules(); + void SetFastBackoffDurationRules(); + ~SystemFallbackPolicy(); private: @@ -81,6 +85,8 @@ class SystemFallbackPolicy { void StoreInfo(); + int64_t GenerateInitialBackoffDuration(); + int64_t GetSecondsSinceBackoffStart() const; void EndBackoffPeriod(); @@ -93,6 +99,10 @@ class SystemFallbackPolicy { SystemFallbackInfo info_; + // When |fast_fallback_| is true, falling back only lasts a few + // seconds, and exponential backoff is disabled. + bool fast_fallback_ = false; + // Handle for the DeviceFiles instance used to store the OKP // information. // Not set for test instances. diff --git a/libwvdrmengine/cdm/core/include/okp_info.h b/libwvdrmengine/cdm/core/include/okp_info.h index 3bd91424..add43ea9 100644 --- a/libwvdrmengine/cdm/core/include/okp_info.h +++ b/libwvdrmengine/cdm/core/include/okp_info.h @@ -45,6 +45,7 @@ class SystemFallbackInfo { backoff_duration_ = (duration > 0 ? duration : 0); } void DoubleBackoffDuration() { backoff_duration_ *= 2; } + void ClearBackoffDuration() { backoff_duration_ = 0; } bool HasProvisioningTime() const { return provisioning_time_ != 0; } int64_t provisioning_time() const { return provisioning_time_; } diff --git a/libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp b/libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp index 4e6505e6..b40c9d46 100644 --- a/libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp +++ b/libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp @@ -16,11 +16,6 @@ namespace okp { using UniqueLock = std::unique_lock; namespace { constexpr int64_t kErrorTime = -1; - -int64_t GenerateInitialBackoffDuration() { - return static_cast(CdmRandom::RandomInRange( - kMinInitialBackoffDuration, kMaxInitialBackoffDuration)); -} } // namespace // static @@ -131,11 +126,11 @@ void SystemFallbackPolicy::TriggerFallback() { info_.SetFirstCheckedTime(current_time); } info_.SetBackoffStartTime(GetCurrentTime()); - if (info_.HasBackoffDuration()) { - // Doubling backoff duration for exponential backoff. + if (!fast_fallback_ && info_.HasBackoffDuration()) { + // Doubling backoff duration for exponential backoff. Except when + // performing fast fallback off. info_.DoubleBackoffDuration(); } else { - // Use a random backoff period to avoid server spam across all devices. info_.SetBackoffDuration(GenerateInitialBackoffDuration()); } StoreInfo(); @@ -185,6 +180,37 @@ bool SystemFallbackPolicy::IsInFallbackMode() { return false; // Only stored if previously in fallback and has ended. } +void SystemFallbackPolicy::SetDefaultBackoffDurationRules() { + UniqueLock lock(mutex_); + fast_fallback_ = false; + if (state() == SystemState::kFallbackMode) { + LOGI("Ending fallback"); + EndBackoffPeriod(); + } + info_.ClearBackoffDuration(); + StoreInfo(); +} + +void SystemFallbackPolicy::SetFastBackoffDurationRules() { + UniqueLock lock(mutex_); + fast_fallback_ = true; + if (state() == SystemState::kFallbackMode) { + LOGI("Ending fallback"); + EndBackoffPeriod(); + } + info_.ClearBackoffDuration(); + StoreInfo(); +} + +int64_t SystemFallbackPolicy::GenerateInitialBackoffDuration() { + if (fast_fallback_) { + return kFastBackoffDuration; + } + // Use a random backoff period to avoid server spam across all devices. + return static_cast(CdmRandom::RandomInRange( + kMinInitialBackoffDuration, kMaxInitialBackoffDuration)); +} + int64_t SystemFallbackPolicy::GetSecondsSinceBackoffStart() const { if (!info_.HasBackoffStartTime()) return 0; const int64_t backoff_start_time = info_.backoff_start_time(); diff --git a/libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp b/libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp index ea57a42d..ba6dddbf 100644 --- a/libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp +++ b/libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp @@ -368,5 +368,110 @@ TEST_F(OkpFallbackPolicyTest, Restore_NeedsProvisioningAgain) { EXPECT_FALSE(system_policy_->IsInFallbackMode()); EXPECT_EQ(system_policy_->info().first_checked_time(), kRestoreTime); } + +// Setup: +// 1) Device needs OKP +// 2) App requests using fast backoff settings. +// 3) Fallback occurs +// 4) After the fast fallback duration, check for if in fallback +// Expectation: +// Policy should indicate fallback, duration should be "fast" and +// the info is updated. +// After the fast fallback duration has passed, the system should +// leave fallback state. +TEST_F(OkpFallbackPolicyTest, FastRules) { + system_policy_->SetFastBackoffDurationRules(); + constexpr int64_t kFallbackTime = kInitialTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kFastBackoffDuration); + + constexpr int64_t kPostFallbackTime = + kFallbackTime + kFastBackoffDuration + 5; + clock_.SetTime(kPostFallbackTime); + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback occurs +// 3) App requests using fast backoff settings. +// 4) Another fallback occurs +// Expectation: +// 1) Setting rules to fast should end fallback +// 2) Second fallback should have a short duration. +TEST_F(OkpFallbackPolicyTest, FastRules_AfterFallback) { + // First fallback. + constexpr int64_t kFirstFallbackTime = kInitialTime + 10; + clock_.SetTime(kFirstFallbackTime); + system_policy_->TriggerFallback(); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + + // Set fast fallback. + system_policy_->SetFastBackoffDurationRules(); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + + // Second fallaback. + constexpr int64_t kSecondFallbackTime = kFirstFallbackTime + 10; + clock_.SetTime(kSecondFallbackTime); + system_policy_->TriggerFallback(); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kSecondFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kFastBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) App requests using fast backoff settings. +// 3) Fallback occurs +// 4) After the fast fallback duration, check for if in fallback +// 5) Another fallback occurs +// 6) After the fast fallback duration, check for if in fallback +// Expectation: +// There should not be any exponential backoff, similar to FastRules +// in all other ways. +TEST_F(OkpFallbackPolicyTest, FastRules_FallbackTwice) { + system_policy_->SetFastBackoffDurationRules(); + constexpr int64_t kFirstFallbackTime = kInitialTime + 10; + clock_.SetTime(kFirstFallbackTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kFirstFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kFastBackoffDuration); + + constexpr int64_t kPostFirstFallbackTime = + kFirstFallbackTime + kFastBackoffDuration + 5; + clock_.SetTime(kPostFirstFallbackTime); + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + + constexpr int64_t kSecondFallbackTime = kPostFirstFallbackTime + 10; + clock_.SetTime(kSecondFallbackTime); + system_policy_->TriggerFallback(); + + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kSecondFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kFastBackoffDuration); + + constexpr int64_t kPostSecondFallbackTime = + kSecondFallbackTime + kFastBackoffDuration + 5; + clock_.SetTime(kPostSecondFallbackTime); + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); +} } // namespace okp } // namespace wvcdm From 8b12e5acc964154cdf15b97ee25bf58bbd78e7ae Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Fri, 15 Oct 2021 19:54:02 -0700 Subject: [PATCH 11/12] Added debugOtaKeyboxFallbackDuration property. [ Cherry-pick of http://ag/16064434 ] [ Merge of http://go/wvgerrit/136330 ] This changes adds a custom debug property for changing the fallback policy used for the system. Depending on the value set, the device will either use a "fast" fallback (30 seconds) or "default" fallback (~1 day with exponential backoff). Setting this property to either "fast" or "default" will end the current fallback if it has been triggered. Bug: 187646550 Test: Android unit tests Change-Id: I5271f96139c1e468242f7fa742668cc791ffcf91 --- libwvdrmengine/cdm/core/include/cdm_engine.h | 10 ++++++++ libwvdrmengine/cdm/core/src/cdm_engine.cpp | 23 +++++++++++++++++++ .../include/wv_content_decryption_module.h | 3 +++ .../cdm/src/wv_content_decryption_module.cpp | 15 ++++++++++++ libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp | 13 +++++++++++ .../mediadrm/src_hidl/WVDrmPlugin.cpp | 13 +++++++++++ 6 files changed, 77 insertions(+) diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index afa73db2..883370a6 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -360,6 +360,16 @@ class CdmEngine { virtual void SetUserId(uint32_t user_id) { user_id_ = user_id; } virtual uint32_t GetUserId() const { return user_id_; } + // Changes the rules used for calculating the fallback duration + // when OTA keybox provisioning fails. + // Default rules use fallback duration measured in days, with exponential + // backoff. + // Fast rules use fallback durations of a few seconds, without exponential + // backoff. + // This method has no effect if OTA keybox is not required. + virtual void SetDefaultOtaKeyboxFallbackDurationRules(); + virtual void SetFastOtaKeyboxFallbackDurationRules(); + protected: friend class CdmEngineFactory; diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index dee205f4..fd5234f3 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -21,6 +21,7 @@ #include "device_files.h" #include "file_store.h" #include "log.h" +#include "okp_fallback_policy.h" #include "ota_keybox_provisioner.h" #include "properties.h" #include "string_conversions.h" @@ -2154,4 +2155,26 @@ void CdmEngine::OkpCleanUp() { } okp_provisioner_.reset(); } + +void CdmEngine::SetDefaultOtaKeyboxFallbackDurationRules() { + OkpCheck(); + std::unique_lock lock(okp_mutex_); + auto* system_fallback_policy = CryptoSession::GetOkpFallbackPolicy(); + if (!system_fallback_policy) { + LOGW("No system fallback policy available"); + return; + } + system_fallback_policy->SetDefaultBackoffDurationRules(); +} + +void CdmEngine::SetFastOtaKeyboxFallbackDurationRules() { + OkpCheck(); + std::unique_lock lock(okp_mutex_); + auto* system_fallback_policy = CryptoSession::GetOkpFallbackPolicy(); + if (!system_fallback_policy) { + LOGW("No system fallback policy available"); + return; + } + system_fallback_policy->SetFastBackoffDurationRules(); +} } // namespace wvcdm diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index 06a75613..1630ae93 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -188,6 +188,9 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler { virtual CdmResponseType GetSessionUserId(const CdmSessionId& session_id, uint32_t* user_id); + virtual bool SetDefaultOtaKeyboxFallbackDurationRules(); + virtual bool SetFastOtaKeyboxFallbackDurationRules(); + private: struct CdmInfo { CdmInfo(); diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index ded47c25..3256d102 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -645,4 +645,19 @@ CdmResponseType WvContentDecryptionModule::GetSessionUserId( *user_id = cdm_engine->GetUserId(); return NO_ERROR; } + +bool WvContentDecryptionModule::SetDefaultOtaKeyboxFallbackDurationRules() { + CdmEngine* cdm_engine = EnsureCdmForIdentifier(kDefaultCdmIdentifier); + if (!cdm_engine) return false; + cdm_engine->SetDefaultOtaKeyboxFallbackDurationRules(); + return true; +} + +bool WvContentDecryptionModule::SetFastOtaKeyboxFallbackDurationRules() { + CdmEngine* cdm_engine = EnsureCdmForIdentifier(kDefaultCdmIdentifier); + if (!cdm_engine) return false; + cdm_engine->SetFastOtaKeyboxFallbackDurationRules(); + return true; +} + } // namespace wvcdm diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index b7e67d53..76f09a49 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -689,6 +689,19 @@ status_t WVDrmPlugin::setPropertyString(const String8& name, return mapCdmResponseType(res); } else if (name == "decryptHashSessionId") { mDecryptHashSessionId = value.string(); + } else if (name == "debugOtaKeyboxFallbackDuration") { + bool success = false; + if (value == "default") { + success = mCDM->SetDefaultOtaKeyboxFallbackDurationRules(); + } else if (value == "fast") { + success = mCDM->SetFastOtaKeyboxFallbackDurationRules(); + } else { + ALOGE("Unknown OTA fallback duration value %s", value.string()); + return android::BAD_VALUE; + } + if (!success) { + return android::UNKNOWN_ERROR; + } } else if (name == "atscMode") { if (value == kEnable) { mPropertySet.set_use_atsc_mode(true); diff --git a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp index cb3709f9..aa1efe37 100644 --- a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp @@ -1470,6 +1470,19 @@ Return WVDrmPlugin::setPropertyString(const hidl_string& propertyName, ALOGE("App requested unknown ATSC mode %s", _value.c_str()); return Status::BAD_VALUE; } + } else if (name == "debugOtaKeyboxFallbackDuration") { + bool success = false; + if (value == "default") { + success = mCDM->SetDefaultOtaKeyboxFallbackDurationRules(); + } else if (value == "fast") { + success = mCDM->SetFastOtaKeyboxFallbackDurationRules(); + } else { + ALOGE("Unknown OTA fallback duration value %s", _value.c_str()); + return Status::BAD_VALUE; + } + if (!success) { + return Status::ERROR_DRM_UNKNOWN; + } } else { ALOGE("App set unknown string property %s", name.c_str()); return Status::ERROR_DRM_CANNOT_HANDLE; From f5e4c94e265a50bbfa6d907b063e5a50977b1901 Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Tue, 19 Oct 2021 13:04:03 -0700 Subject: [PATCH 12/12] Clear OTA keybox flag. [ Cherry-pick of http://ag/16087795 ] [ Merge of http://go/wvgerrit/136432 ] Once OTA keybox succeeds, the |needs_keybox_provisioning_| flag is cleared. Access to the system fallback policy is allowed after provisioning to check status. Bug: 203177668 Test: ExoPlayer test Change-Id: I2d28c896c554cfbc9b008340bb415d4c7fac62f2 (cherry picked from commit cac2dcaa6c804b2ac175ba267d05a65f98d8630a) --- .../cdm/core/include/crypto_session.h | 2 +- .../cdm/core/src/crypto_session.cpp | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index b48cd4aa..23ed6a0c 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -302,7 +302,7 @@ class CryptoSession { // Returns a system-wide singleton instance of SystemFallbackPolicy // to be used for communicating OTA keybox provisioning state between // apps. Returns a null pointer if OTA provisioning is not supported, - // or if the device has already been provisioned. + // or not required. static okp::SystemFallbackPolicy* GetOkpFallbackPolicy(); // Generates an OTA provisioning request. diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 349cd368..018f38df 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -3041,8 +3041,6 @@ okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() { // If not set, then OTA keybox provisioning is not supported or // not needed. if (!okp_fallback_policy_l1_) return nullptr; - // May have already been initialized. - if (okp_fallback_policy_l1_->IsProvisioned()) return nullptr; return okp_fallback_policy_l1_.get(); }; return WithStaticFieldReadLock("GetOkpFallbackPolicy", getter); @@ -3053,8 +3051,11 @@ CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( RETURN_IF_NULL(request, PARAMETER_NULL); RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); size_t buffer_length = 0; - OEMCryptoResult status = OEMCrypto_GenerateOTARequest( - oec_session_id_, nullptr, &buffer_length, use_test_key); + OEMCryptoResult status = + WithOecWriteLock("PrepareOtaProvisioningRequest", [&] { + return OEMCrypto_GenerateOTARequest( + oec_session_id_, nullptr, &buffer_length, use_test_key ? 1 : 0); + }); if (status != OEMCrypto_ERROR_SHORT_BUFFER) return MapOEMCryptoResult(status, UNKNOWN_ERROR, "PrepareOtaProvisioningRequest"); @@ -3064,8 +3065,10 @@ CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( } request->resize(buffer_length); uint8_t* buf = reinterpret_cast(&request->front()); - status = OEMCrypto_GenerateOTARequest(oec_session_id_, buf, &buffer_length, + status = WithOecWriteLock("PrepareOtaProvisioningRequest", [&] { + return OEMCrypto_GenerateOTARequest(oec_session_id_, buf, &buffer_length, use_test_key ? 1 : 0); + }); if (OEMCrypto_SUCCESS != status) { request->clear(); } else if (buffer_length != request->size()) { @@ -3078,9 +3081,15 @@ CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( CdmResponseType CryptoSession::LoadOtaProvisioning( bool use_test_key, const std::string& response) { RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); - const OEMCryptoResult status = OEMCrypto_ProcessOTAKeybox( - oec_session_id_, reinterpret_cast(response.data()), - response.size(), use_test_key ? 1 : 0); + const OEMCryptoResult status = WithOecWriteLock("LoadOtaProvisioning", [&] { + return OEMCrypto_ProcessOTAKeybox( + oec_session_id_, reinterpret_cast(response.data()), + response.size(), use_test_key ? 1 : 0); + }); + if (status == OEMCrypto_SUCCESS) { + WithOecWriteLock("LoadOtaProvisioning", + [&] { needs_keybox_provisioning_ = false; }); + } return MapOEMCryptoResult(status, UNKNOWN_ERROR, "LoadOtaProvisioning"); }