From 31faf51933f7130242e6e79e7e81ace6e382e78b Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Sun, 19 Dec 2021 07:28:04 +0000 Subject: [PATCH] Fall back to L3 if L1 has test keybox Merge from Widevine repo of http://go/wvgerrit/142150 (part 2) For an EVT device, without a keybox or with a test keybox, we want it to fall back to L3. However, when running the unit or integration tests it should continue running tests with test keybox. This will allow us to test L1 oemcrypto on an EVT device, while still using an EVT device for dogfooding video content at the L3 level. Bug: 210807585 Bug: 210823889 Change-Id: I30c35134239db35bb39f11f75220063181987763 --- libwvdrmengine/cdm/core/include/cdm_engine.h | 6 + .../cdm/core/include/crypto_session.h | 11 +- .../cdm/core/include/oemcrypto_adapter.h | 4 + .../cdm/core/src/crypto_session.cpp | 20 ++++ .../core/src/oemcrypto_adapter_dynamic.cpp | 109 +++++++++++++++--- libwvdrmengine/cdm/core/test/test_base.cpp | 12 +- .../cdm/src/wv_content_decryption_module.cpp | 3 +- 7 files changed, 135 insertions(+), 30 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index 883370a6..f1ed6d15 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -309,6 +309,12 @@ class CdmEngine { return CryptoSession::SetDebugIgnoreKeyboxCount(count); } + // This tells the OEMCrypto adapter to allow the device to continue with a + // test keybox. Otherwise, the keybox is reported as invalid. + static CdmResponseType SetAllowTestKeybox(bool allow) { + return CryptoSession::SetAllowTestKeybox(allow); + } + 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 0606fca2..7f23699a 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -299,6 +299,10 @@ class CryptoSession { // report that it needs provisioning instead. static CdmResponseType SetDebugIgnoreKeyboxCount(uint32_t count); + // This tells the OEMCrypto adapter to allow the device to continue with a + // test keybox. Otherwise, the keybox is reported as invalid. + static CdmResponseType SetAllowTestKeybox(bool allow); + // 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, @@ -322,13 +326,10 @@ class CryptoSession { explicit CryptoSession(metrics::CryptoMetrics* crypto_metrics); int session_count() const { return session_count_; } - bool initialized() const { return initialized_; } - void set_initialized(bool initialized) { initialized_ = initialized; } // Cache api version and fallback policy. Call this once at initialization. void CacheVersion(); - void OverrideNeedKeyboxForTesting(bool needs_keybox_provisioning) { - needs_keybox_provisioning_ = needs_keybox_provisioning; - } + // Re-initialize for running tests with a test keybox. + void ReinitializeForTest(); private: friend class CryptoSessionForTest; diff --git a/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h b/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h index 8c9dc75c..4a462f39 100644 --- a/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h +++ b/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h @@ -20,6 +20,10 @@ OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( // report that it needs provisioning instead. OEMCryptoResult OEMCrypto_SetDebugIgnoreKeyboxCount(uint32_t count); +// This tells the OEMCrypto adapter to allow the device to continue with a +// test keybox. Otherwise, the keybox is reported as invalid. +OEMCryptoResult OEMCrypto_SetAllowTestKeybox(bool allow); + // 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 a8d76020..7fc2233c 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -358,6 +358,21 @@ void CryptoSession::Init() { } } +void CryptoSession::ReinitializeForTest() { + if (initialized_) { + initialized_ = false; + if (OEMCrypto_SUCCESS != OEMCrypto_Terminate()) return; + } + // Give up if we cannot initialize at all. + if (OEMCrypto_SUCCESS != OEMCrypto_Initialize()) return; + initialized_ = true; + // For integration and unit tests we will install a test keybox and do not + // need to do keybox provisioning. + needs_keybox_provisioning_ = false; + // This was skipped in Init because initialization failed. + CacheVersion(); +} + void CryptoSession::CacheVersion() { uint32_t version; std::string api_version = @@ -3061,6 +3076,11 @@ CdmResponseType CryptoSession::SetDebugIgnoreKeyboxCount(uint32_t count) { return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetDebugIgnoreKeyboxCount"); } +CdmResponseType CryptoSession::SetAllowTestKeybox(bool allow) { + OEMCryptoResult status = OEMCrypto_SetAllowTestKeybox(allow); + return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetAllowTestKeybox"); +} + 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 95f672ac..ce13e0f6 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp @@ -641,6 +641,17 @@ std::string GetIgnoreCountFile() { return path; } +std::string GetAllowTestKeyboxFile() { + std::string path; + if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL1, + &path)) { + LOGW("GetAllowTestKeyboxFile: Unable to get base path"); + path = "/data/"; + } + path += "debug_allow_test_keybox.txt"; + return path; +} + uint32_t GetDebugIgnoreKeyboxCount() { const std::string filename = GetIgnoreCountFile(); wvcdm::FileSystem file_system; @@ -693,6 +704,49 @@ OEMCryptoResult SetDebugIgnoreKeyboxCount(uint32_t count) { return OEMCrypto_SUCCESS; } +bool GetAllowTestKeybox() { + const std::string filename = GetAllowTestKeyboxFile(); + 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 allow_test_keybox = %zu", size_read); + return 0; + } + // skip whitespace or any extra garbage. + return (std::string::npos != contents.find("true")); +} + +OEMCryptoResult SetAllowTestKeybox(bool allow) { + const std::string filename = GetAllowTestKeyboxFile(); + 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 = allow ? "true\n" : "false\n"; + const size_t size = contents.size(); + ssize_t size_written = file->Write(contents.data(), size); + if (static_cast(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 %s to %s", contents.c_str(), filename.c_str()); + return OEMCrypto_SUCCESS; +} + struct LevelSession { FunctionPointers* fcn; OEMCrypto_SESSION session; @@ -816,6 +870,7 @@ class Adapter { level1_.BuildInformation()); } } else { + level1_failed_ = true; FallBackToLevel3(); } return result; @@ -837,7 +892,6 @@ class Adapter { Level1Terminate(); level1_ = FunctionPointers(); // revert to all null pointers. level1_valid_ = false; - level1_failed_ = true; // Note: if the function pointers are bad, we do not close the library and // try again later. Instead, we permanently fall back to L3. This is a // debatable choice: I decided the risk of a dlclose resource leak out @@ -1162,23 +1216,32 @@ class Adapter { return result; } + // Check the system ID of the keybox. This should only be called if the device + // uses provisioning 2.0. + bool UsingTestKeybox() { + uint8_t key_data[256]; + size_t key_data_len = sizeof(key_data); + OEMCryptoResult sts = OEMCrypto_GetKeyData(key_data, &key_data_len); + if (sts != OEMCrypto_SUCCESS) return true; + uint32_t* data = reinterpret_cast(key_data); + uint32_t system_id = htonl(data[1]); + return system_id == 7912; + } + // 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() { + // then return an error code. The caller should fall back to L3. + OEMCryptoResult ValidateOrInstallL1KeyboxOrCert() { 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; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; } if (!level1_.IsKeyboxOrOEMCertValid) { // TODO(b/189989043): add metrics. LOGE("L1 invalid function pointers. Falling back to L3"); - FallBackToLevel3(); - return level3_.IsKeyboxOrOEMCertValid ? level3_.IsKeyboxOrOEMCertValid() - : OEMCrypto_ERROR_NOT_IMPLEMENTED; + return 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. @@ -1189,6 +1252,19 @@ class Adapter { const OEMCrypto_ProvisioningMethod provisioning_method = level1_.GetProvisioningMethod ? level1_.GetProvisioningMethod() : OEMCrypto_Keybox; + // For production systems, we do wish to use a test keybox. We do not force + // a fallback to L3 at this point, because this can be overridden by test + // code that requires a test keybox. + if ((rot_valid == OEMCrypto_SUCCESS) && + (provisioning_method == OEMCrypto_Keybox) && UsingTestKeybox()) { + if (GetAllowTestKeybox()) { + LOGW("Allowing device with test keybox installed."); + } else { + LOGW("Device has test keybox installed."); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + } + 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. @@ -1226,9 +1302,7 @@ class Adapter { wvcdm::metrics:: OEMCrypto_INITIALIZED_USING_L3_COULD_NOT_INSTALL_KEYBOX); } - FallBackToLevel3(); - return level3_.IsKeyboxOrOEMCertValid ? level3_.IsKeyboxOrOEMCertValid() - : OEMCrypto_ERROR_NOT_IMPLEMENTED; + return file_attempt; } bool IsOTAKeyboxSupported() { @@ -1342,7 +1416,7 @@ OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( // continue on. if (status != OEMCrypto_SUCCESS) return status; const OEMCryptoResult keybox_status = - gAdapter->ValidateOrInstallKeyboxOrCert(); + gAdapter->ValidateOrInstallL1KeyboxOrCert(); uint32_t ignore_count = GetDebugIgnoreKeyboxCount(); if (keybox_status == OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING || ignore_count > 0) { @@ -1359,7 +1433,13 @@ OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( return OEMCrypto_SUCCESS; } } - return keybox_status; + if (keybox_status == OEMCrypto_SUCCESS) { + return OEMCrypto_SUCCESS; + } + LOGW("Keybox error: %d. Falling back to L3.", keybox_status); + gAdapter->FallBackToLevel3(); + // Return success if the L3 keybox or cert is valid. + return OEMCrypto_IsKeyboxOrOEMCertValid(); } OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session, @@ -1643,6 +1723,9 @@ OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(uint8_t* public_cert, OEMCryptoResult OEMCrypto_SetDebugIgnoreKeyboxCount(uint32_t count) { return SetDebugIgnoreKeyboxCount(count); } +OEMCryptoResult OEMCrypto_SetAllowTestKeybox(bool allow) { + return SetAllowTestKeybox(allow); +} } // namespace wvcdm extern "C" OEMCryptoResult OEMCrypto_SetSandbox(const uint8_t* sandbox_id, diff --git a/libwvdrmengine/cdm/core/test/test_base.cpp b/libwvdrmengine/cdm/core/test/test_base.cpp index 0487e9a0..d3e622f1 100644 --- a/libwvdrmengine/cdm/core/test/test_base.cpp +++ b/libwvdrmengine/cdm/core/test/test_base.cpp @@ -217,16 +217,8 @@ TestCryptoSession::TestCryptoSession(metrics::CryptoMetrics* crypto_metrics) // The first CryptoSession should have initialized OEMCrypto. This is right // after that, so we should tell oemcrypto to use a test keybox. if (session_count() == 1) { - OverrideNeedKeyboxForTesting(false); - // However, if the device does not have a keybox, initialization would have - // failed. In that case we should try again. - if (!initialized()) { - // Give up if we cannot initialize at all. - if (OEMCrypto_SUCCESS != OEMCrypto_Initialize()) return; - set_initialized(true); - // This was skipped in Init because initialization failed. - CacheVersion(); - } + CryptoSession::SetAllowTestKeybox(true); + ReinitializeForTest(); WvCdmTestBase::InstallTestRootOfTrust(); } } diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index e87d7890..0d91b3bb 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -506,8 +506,7 @@ CdmResponseType WvContentDecryptionModule::SetDebugIgnoreKeyboxCount( } CdmResponseType WvContentDecryptionModule::SetAllowTestKeybox(bool allow) { - // TODO(210807585) add functionality in next CL. - return NO_ERROR; + return CdmEngine::SetAllowTestKeybox(allow); } CdmResponseType WvContentDecryptionModule::SetDecryptHash(