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