diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index 23ed6a0c..4a1bead7 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -293,7 +293,7 @@ class CryptoSession { // OTA Provisioning - bool needs_keybox_provisioning() const { return needs_keybox_provisioning_; } + static bool needs_keybox_provisioning() { return needs_keybox_provisioning_; } // This tells the OEMCrypto adapter to ignore the next |count| keyboxes and // report that it needs provisioning instead. diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index fd5234f3..35fefd92 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -121,41 +121,50 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, } LOGD("forced_session_id = %s", IdPtrToString(forced_session_id)); } + + SecurityLevel requested_security_level = kLevelDefault; + if (property_set && + property_set->security_level() == QUERY_VALUE_SECURITY_LEVEL_L3) { + requested_security_level = kLevel3; + } + 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 { + if (requested_security_level == kLevelDefault) { + 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; } - } 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; + 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 { - // OKP is required. - return NEED_PROVISIONING; + std::unique_lock lock(okp_mutex_); + // |okp_fallback_| would have been set previously if required. + if (okp_fallback_) forced_level3 = true; } - } else { - std::unique_lock lock(okp_mutex_); - // |okp_fallback_| would have been set previously if required. - if (okp_fallback_) forced_level3 = true; } CloseExpiredReleaseSessions(); diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 018f38df..2e19ba11 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -90,6 +90,13 @@ static_assert(ArraySize(kMaxSubsampleRegionSizes) == constexpr size_t kDefaultMaxSubsampleRegionSize = kMaxSubsampleRegionSizes[0]; +// Not a valid system ID. Used as a placeholder for systems without an ID. +// Will not be accepted for DRM provisioning requests or license requests. +constexpr uint32_t kNullSystemId = + static_cast(std::numeric_limits::max()); + +constexpr size_t kMaxExternalDeviceIdLength = 64; + // This maps a few common OEMCryptoResult to CdmResponseType. Many mappings // are not universal but are OEMCrypto method specific. Those will be // specified in the CryptoSession method rather than here. @@ -477,7 +484,6 @@ bool CryptoSession::SetUpUsageTableHeader( CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) { RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_NULL(token, PARAMETER_NULL); - std::string temp_buffer(KEYBOX_KEY_DATA_SIZE, '\0'); size_t buf_size = temp_buffer.size(); uint8_t* buf = reinterpret_cast(&temp_buffer[0]); @@ -613,68 +619,72 @@ CdmResponseType CryptoSession::GetInternalDeviceUniqueId( RETURN_IF_NULL(device_id, PARAMETER_NULL); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); - std::vector id; - size_t id_length = 32; - id.resize(id_length); + size_t device_id_length = 64; + device_id->assign(device_id_length, '\0'); - OEMCryptoResult sts; - WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] { - sts = OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_); - }); - // Increment the count of times this method was called. + OEMCryptoResult sts = + WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] { + return OEMCrypto_GetDeviceID( + reinterpret_cast(&device_id->front()), &device_id_length, + requested_security_level_); + }); metrics_->oemcrypto_get_device_id_.Increment(sts); + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { - id.resize(id_length); - WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] { - sts = - OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_); + device_id->resize(device_id_length, '\0'); + sts = WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] { + return OEMCrypto_GetDeviceID( + reinterpret_cast(&device_id->front()), &device_id_length, + requested_security_level_); }); metrics_->oemcrypto_get_device_id_.Increment(sts); } + // Either the authentication root is a keybox or the device has transitioned + // to using OEMCerts. + // OEMCryptos, like the Level 3, that transition from Provisioning 2.0 to + // 3.0 would have a new device ID, which would affect SPOID calculation. + // In order to resolve this, we use OEMCrypto_GetDeviceID if it is + // implemented, so the OEMCrypto can continue to report the same device ID. + if (sts == OEMCrypto_SUCCESS) { + device_id->resize(device_id_length); + return NO_ERROR; + } + device_id->clear(); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED && pre_provision_token_type_ == kClientTokenOemCert) { return GetTokenFromOemCert(device_id); - } else { - // Either the authentication root is a keybox or the device has transitioned - // to using OEMCerts. - // OEMCryptos, like the Level 3, that transition from Provisioning 2.0 to - // 3.0 would have a new device ID, which would affect SPOID calculation. - // In order to resolve this, we use OEMCrypto_GetDeviceID if it is - // implemented, so the OEMCrypto can continue to report the same device ID. - if (sts == OEMCrypto_SUCCESS) { - device_id->assign(reinterpret_cast(&id[0]), id_length); - } - - return MapOEMCryptoResult(sts, GET_DEVICE_ID_ERROR, - "GetInternalDeviceUniqueId"); } + + const bool use_null_device_id = WithStaticFieldReadLock( + "GetInternalDeviceUniqueId() use_null_device_id", [&] { + if (requested_security_level_ != kLevelDefault) return false; + return sts == OEMCrypto_ERROR_KEYBOX_INVALID && + needs_keybox_provisioning_; + }); + if (use_null_device_id) { + LOGD("Using null device ID"); + constexpr size_t kKeyboxDeviceIdLength = 32; + device_id->assign(kKeyboxDeviceIdLength, '\0'); + return NO_ERROR; + } + + return MapOEMCryptoResult(sts, GET_DEVICE_ID_ERROR, + "GetInternalDeviceUniqueId"); } CdmResponseType CryptoSession::GetExternalDeviceUniqueId( std::string* device_id) { RETURN_IF_NULL(device_id, PARAMETER_NULL); - - std::string temp; - CdmResponseType status = GetInternalDeviceUniqueId(&temp); - + RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); + const CdmResponseType status = GetInternalDeviceUniqueId(device_id); if (status != NO_ERROR) return status; - - size_t id_length = 0; - OEMCryptoResult sts; - WithOecReadLock("GetExternalDeviceUniqueId", [&] { - sts = OEMCrypto_GetDeviceID(nullptr, &id_length, requested_security_level_); - }); - metrics_->oemcrypto_get_device_id_.Increment(sts); - - if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED && - pre_provision_token_type_ == kClientTokenOemCert) { + if (device_id->size() > kMaxExternalDeviceIdLength) { // To keep the size of the value passed back to the application down, hash // the large OEM Public Cert to a smaller value. - temp = Sha256Hash(temp); + *device_id = Sha256Hash(*device_id); } - - *device_id = temp; return NO_ERROR; } @@ -736,11 +746,22 @@ CdmResponseType CryptoSession::GetSystemIdInternal(uint32_t* system_id) { RETURN_IF_NULL(system_id, PARAMETER_NULL); if (pre_provision_token_type_ == kClientTokenKeybox) { + const bool use_null_system_id = WithStaticFieldReadLock( + "GetSystemIdInternal() use_null_system_id", [&] { + // Devices with an invalid L1 keybox which support OTA keybox + // provisioning require a placeholder system ID while waiting for + // keybox. + if (requested_security_level_ != kLevelDefault) return false; + return needs_keybox_provisioning_; + }); + if (use_null_system_id) { + LOGD("Using null system ID"); + *system_id = kNullSystemId; + return NO_ERROR; + } std::string token; - CdmResponseType status = GetTokenFromKeybox(&token); - + const CdmResponseType status = GetTokenFromKeybox(&token); if (status != NO_ERROR) return status; - if (token.size() < 2 * sizeof(uint32_t)) { LOGE("Keybox token size too small: token_size = %zu", token.size()); return KEYBOX_TOKEN_TOO_SHORT; @@ -748,7 +769,7 @@ CdmResponseType CryptoSession::GetSystemIdInternal(uint32_t* system_id) { // Decode 32-bit int encoded as network-byte-order byte array starting at // index 4. - uint32_t* id = reinterpret_cast(&token[4]); + const uint32_t* id = reinterpret_cast(&token[4]); *system_id = ntohl(*id); return NO_ERROR; } diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp index 1636cd20..283040b0 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp @@ -742,11 +742,7 @@ class Adapter { public: using map_iterator = std::map::iterator; - Adapter() - : level1_valid_(false), - level1_initialized_(false), - level1_failed_(false), - level1_library_(nullptr) {} + Adapter() {} // The adapter is only destroyed on certain errors, or when the process // dies. It is NOT deleted after each OEMCrypto_Terminate. @@ -1236,24 +1232,25 @@ class Adapter { } bool IsOTAKeyboxSupported() { - // TODO(b/206570220): work around for failing Keybox reprovisioning is - // to fall back to Level 3 if the keybox is not found. - // Put this back when we can: *needs_keybox_provisioning = true; - return false; if (!level1_valid_) return false; if (!level1_.GenerateOTARequest) return false; size_t buffer_size = 500; // a large buffer. std::vector buffer(buffer_size); - return level1_.GenerateOTARequest(0, buffer.data(), &buffer_size, 0) != - OEMCrypto_ERROR_NOT_IMPLEMENTED; + OEMCryptoResult result = + level1_.GenerateOTARequest(0, buffer.data(), &buffer_size, 0); + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + buffer.resize(buffer_size); + result = level1_.GenerateOTARequest(0, buffer.data(), &buffer_size, 0); + } + return result == OEMCrypto_SUCCESS; } private: - bool level1_valid_; - bool level1_initialized_; + bool level1_valid_ = false; + bool level1_initialized_ = false; // If the level 1 fails to initialize once, we don't try again. - bool level1_failed_; - void* level1_library_; + bool level1_failed_ = false; + void* level1_library_ = nullptr; struct FunctionPointers level1_; struct FunctionPointers level3_; std::map session_map_; diff --git a/libwvdrmengine/cdm/core/test/crypto_session_unittest.cpp b/libwvdrmengine/cdm/core/test/crypto_session_unittest.cpp index 324b97e7..8551b146 100644 --- a/libwvdrmengine/cdm/core/test/crypto_session_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/crypto_session_unittest.cpp @@ -25,7 +25,6 @@ using ::testing::Ge; using ::testing::Le; namespace { - const uint8_t kOemCert[] = { 0x30, 0x82, 0x09, 0xf7, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, 0x82, 0x09, 0xe8, 0x30, 0x82, 0x09, 0xe4, 0x02, @@ -243,6 +242,8 @@ const uint8_t kOemCert[] = { const uint32_t kOemCertSystemId = 7346; +constexpr uint32_t kNullSystemId = + static_cast(std::numeric_limits::max()); } // namespace namespace wvcdm { @@ -274,14 +275,16 @@ TEST(CryptoSessionTest, CanExtractSystemIdFromOemCertificate) { class CryptoSessionMetricsTest : public WvCdmTestBase { protected: uint32_t FindKeyboxSystemID() { - OEMCryptoResult sts; + if (CryptoSession::needs_keybox_provisioning()) { + return kNullSystemId; + } uint8_t key_data[256]; size_t key_data_len = sizeof(key_data); - sts = OEMCrypto_GetKeyData(key_data, &key_data_len, kLevelDefault); - if (sts != OEMCrypto_SUCCESS) return 0; - uint32_t* data = reinterpret_cast(key_data); - uint32_t system_id = htonl(data[1]); - return system_id; + const OEMCryptoResult sts = + OEMCrypto_GetKeyData(key_data, &key_data_len, kLevelDefault); + if (sts != OEMCrypto_SUCCESS) return kNullSystemId; + const uint32_t* data = reinterpret_cast(key_data); + return htonl(data[1]); } }; @@ -318,18 +321,24 @@ TEST_F(CryptoSessionMetricsTest, OpenSessionValidMetrics) { CdmClientTokenType token_type = session->GetPreProvisionTokenType(); if (token_type == kClientTokenKeybox) { - uint32_t system_id = FindKeyboxSystemID(); - EXPECT_EQ(system_id, metrics_proto.crypto_session_system_id().int_value()); + const uint32_t expected_system_id = FindKeyboxSystemID(); + const uint32_t recorded_system_id = + metrics_proto.crypto_session_system_id().int_value(); + EXPECT_EQ(expected_system_id, recorded_system_id); EXPECT_EQ(OEMCrypto_Keybox, metrics_proto.oemcrypto_provisioning_method().int_value()); - EXPECT_EQ(1, metrics_proto.oemcrypto_get_key_data_time_us().size()); + if (recorded_system_id != kNullSystemId) { + // Devices with a null system ID don't actually call into the + // TEE for the keybox. + EXPECT_EQ(1, metrics_proto.oemcrypto_get_key_data_time_us().size()); + } } else if (token_type == kClientTokenOemCert) { // Recent devices all have a system id between 1k and 6 or 7k. Errors // we are trying to catch are 0, byte swapped 32 bit numbers, or // garbage. These errors will most likely be outside the range of 1000 // to 2^16. EXPECT_LE(1000, metrics_proto.crypto_session_system_id().int_value()); - EXPECT_GT(0X10000, metrics_proto.crypto_session_system_id().int_value()); + EXPECT_GT(0x10000, metrics_proto.crypto_session_system_id().int_value()); EXPECT_EQ(OEMCrypto_OEMCertificate, metrics_proto.oemcrypto_provisioning_method().int_value()); @@ -368,9 +377,13 @@ TEST_F(CryptoSessionMetricsTest, GetProvisioningTokenValidMetrics) { ASSERT_EQ(1, metrics_proto.crypto_session_get_token().size()); EXPECT_EQ(1, metrics_proto.crypto_session_get_token(0).count()); - uint32_t system_id = FindKeyboxSystemID(); - EXPECT_EQ(system_id, metrics_proto.crypto_session_system_id().int_value()); - EXPECT_EQ(1, metrics_proto.oemcrypto_get_key_data_time_us().size()); + const uint32_t expected_system_id = FindKeyboxSystemID(); + const uint32_t recorded_system_id = + metrics_proto.crypto_session_system_id().int_value(); + EXPECT_EQ(expected_system_id, recorded_system_id); + if (recorded_system_id != kNullSystemId) { + EXPECT_EQ(1, metrics_proto.oemcrypto_get_key_data_time_us().size()); + } } else if (token_type == kClientTokenOemCert) { // Recent devices all have a system id between 1k and 6 or 7k. Errors // we are trying to catch are 0, byte swapped 32 bit numbers, or