Fix System ID problem for devices with no keybox am: f6d682b182

Original change: https://googleplex-android-review.googlesource.com/c/platform/vendor/widevine/+/16408945

Change-Id: I26c5d75affe116c56665de4a038e008ba34bd0d7
This commit is contained in:
Fred Gylys-Colwell
2022-01-07 03:48:24 +00:00
committed by Automerger Merge Worker
5 changed files with 146 additions and 106 deletions

View File

@@ -293,7 +293,7 @@ class CryptoSession {
// OTA Provisioning // 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 // This tells the OEMCrypto adapter to ignore the next |count| keyboxes and
// report that it needs provisioning instead. // report that it needs provisioning instead.

View File

@@ -121,41 +121,50 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
} }
LOGD("forced_session_id = %s", IdPtrToString(forced_session_id)); 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; bool forced_level3 = false;
if (OkpCheck()) { if (requested_security_level == kLevelDefault) {
bool okp_provisioned = false; if (OkpCheck()) {
bool fallback = false; bool okp_provisioned = false;
{ bool fallback = false;
std::unique_lock<std::mutex> lock(okp_mutex_); {
if (!okp_provisioner_) { std::unique_lock<std::mutex> lock(okp_mutex_);
// Very rare race condition. Possible if two calls to OpenSession if (!okp_provisioner_) {
// occur the same time. Cleanup would have been performed. // Very rare race condition. Possible if two calls to OpenSession
if (okp_fallback_) { // occur the same time. Cleanup would have been performed.
fallback = true; if (okp_fallback_) {
} else { fallback = true;
} else {
okp_provisioned = true;
}
} else if (okp_provisioner_->IsProvisioned()) {
okp_provisioned = true; 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) {
if (okp_provisioned) { // OKP not required, engine may assume normal operations.
// OKP not required, engine may assume normal operations. OkpCleanUp();
OkpCleanUp(); } else if (fallback) {
} else if (fallback) { LOGD("Engine is falling back to L3");
LOGD("Engine is falling back to L3"); OkpTriggerFallback();
OkpTriggerFallback(); forced_level3 = true;
forced_level3 = true; } else {
// OKP is required.
return NEED_PROVISIONING;
}
} else { } else {
// OKP is required. std::unique_lock<std::mutex> lock(okp_mutex_);
return NEED_PROVISIONING; // |okp_fallback_| would have been set previously if required.
if (okp_fallback_) forced_level3 = true;
} }
} else {
std::unique_lock<std::mutex> lock(okp_mutex_);
// |okp_fallback_| would have been set previously if required.
if (okp_fallback_) forced_level3 = true;
} }
CloseExpiredReleaseSessions(); CloseExpiredReleaseSessions();

View File

@@ -90,6 +90,13 @@ static_assert(ArraySize(kMaxSubsampleRegionSizes) ==
constexpr size_t kDefaultMaxSubsampleRegionSize = kMaxSubsampleRegionSizes[0]; 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<uint32_t>(std::numeric_limits<int>::max());
constexpr size_t kMaxExternalDeviceIdLength = 64;
// This maps a few common OEMCryptoResult to CdmResponseType. Many mappings // This maps a few common OEMCryptoResult to CdmResponseType. Many mappings
// are not universal but are OEMCrypto method specific. Those will be // are not universal but are OEMCrypto method specific. Those will be
// specified in the CryptoSession method rather than here. // specified in the CryptoSession method rather than here.
@@ -477,7 +484,6 @@ bool CryptoSession::SetUpUsageTableHeader(
CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) { CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(token, PARAMETER_NULL); RETURN_IF_NULL(token, PARAMETER_NULL);
std::string temp_buffer(KEYBOX_KEY_DATA_SIZE, '\0'); std::string temp_buffer(KEYBOX_KEY_DATA_SIZE, '\0');
size_t buf_size = temp_buffer.size(); size_t buf_size = temp_buffer.size();
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]); uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
@@ -613,68 +619,72 @@ CdmResponseType CryptoSession::GetInternalDeviceUniqueId(
RETURN_IF_NULL(device_id, PARAMETER_NULL); RETURN_IF_NULL(device_id, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
std::vector<uint8_t> id; size_t device_id_length = 64;
size_t id_length = 32; device_id->assign(device_id_length, '\0');
id.resize(id_length);
OEMCryptoResult sts; OEMCryptoResult sts =
WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] { WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] {
sts = OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_); return OEMCrypto_GetDeviceID(
}); reinterpret_cast<uint8_t*>(&device_id->front()), &device_id_length,
// Increment the count of times this method was called. requested_security_level_);
});
metrics_->oemcrypto_get_device_id_.Increment(sts); metrics_->oemcrypto_get_device_id_.Increment(sts);
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
id.resize(id_length); device_id->resize(device_id_length, '\0');
WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] { sts = WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] {
sts = return OEMCrypto_GetDeviceID(
OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_); reinterpret_cast<uint8_t*>(&device_id->front()), &device_id_length,
requested_security_level_);
}); });
metrics_->oemcrypto_get_device_id_.Increment(sts); 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 && if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
pre_provision_token_type_ == kClientTokenOemCert) { pre_provision_token_type_ == kClientTokenOemCert) {
return GetTokenFromOemCert(device_id); 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<char*>(&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( CdmResponseType CryptoSession::GetExternalDeviceUniqueId(
std::string* device_id) { std::string* device_id) {
RETURN_IF_NULL(device_id, PARAMETER_NULL); RETURN_IF_NULL(device_id, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
std::string temp; const CdmResponseType status = GetInternalDeviceUniqueId(device_id);
CdmResponseType status = GetInternalDeviceUniqueId(&temp);
if (status != NO_ERROR) return status; if (status != NO_ERROR) return status;
if (device_id->size() > kMaxExternalDeviceIdLength) {
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) {
// To keep the size of the value passed back to the application down, hash // To keep the size of the value passed back to the application down, hash
// the large OEM Public Cert to a smaller value. // the large OEM Public Cert to a smaller value.
temp = Sha256Hash(temp); *device_id = Sha256Hash(*device_id);
} }
*device_id = temp;
return NO_ERROR; return NO_ERROR;
} }
@@ -736,11 +746,22 @@ CdmResponseType CryptoSession::GetSystemIdInternal(uint32_t* system_id) {
RETURN_IF_NULL(system_id, PARAMETER_NULL); RETURN_IF_NULL(system_id, PARAMETER_NULL);
if (pre_provision_token_type_ == kClientTokenKeybox) { 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; std::string token;
CdmResponseType status = GetTokenFromKeybox(&token); const CdmResponseType status = GetTokenFromKeybox(&token);
if (status != NO_ERROR) return status; if (status != NO_ERROR) return status;
if (token.size() < 2 * sizeof(uint32_t)) { if (token.size() < 2 * sizeof(uint32_t)) {
LOGE("Keybox token size too small: token_size = %zu", token.size()); LOGE("Keybox token size too small: token_size = %zu", token.size());
return KEYBOX_TOKEN_TOO_SHORT; 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 // Decode 32-bit int encoded as network-byte-order byte array starting at
// index 4. // index 4.
uint32_t* id = reinterpret_cast<uint32_t*>(&token[4]); const uint32_t* id = reinterpret_cast<const uint32_t*>(&token[4]);
*system_id = ntohl(*id); *system_id = ntohl(*id);
return NO_ERROR; return NO_ERROR;
} }

View File

@@ -742,11 +742,7 @@ class Adapter {
public: public:
using map_iterator = std::map<OEMCrypto_SESSION, LevelSession>::iterator; using map_iterator = std::map<OEMCrypto_SESSION, LevelSession>::iterator;
Adapter() Adapter() {}
: level1_valid_(false),
level1_initialized_(false),
level1_failed_(false),
level1_library_(nullptr) {}
// The adapter is only destroyed on certain errors, or when the process // The adapter is only destroyed on certain errors, or when the process
// dies. It is NOT deleted after each OEMCrypto_Terminate. // dies. It is NOT deleted after each OEMCrypto_Terminate.
@@ -1236,24 +1232,25 @@ class Adapter {
} }
bool IsOTAKeyboxSupported() { 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_valid_) return false;
if (!level1_.GenerateOTARequest) return false; if (!level1_.GenerateOTARequest) return false;
size_t buffer_size = 500; // a large buffer. size_t buffer_size = 500; // a large buffer.
std::vector<uint8_t> buffer(buffer_size); std::vector<uint8_t> buffer(buffer_size);
return level1_.GenerateOTARequest(0, buffer.data(), &buffer_size, 0) != OEMCryptoResult result =
OEMCrypto_ERROR_NOT_IMPLEMENTED; 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: private:
bool level1_valid_; bool level1_valid_ = false;
bool level1_initialized_; bool level1_initialized_ = false;
// If the level 1 fails to initialize once, we don't try again. // If the level 1 fails to initialize once, we don't try again.
bool level1_failed_; bool level1_failed_ = false;
void* level1_library_; void* level1_library_ = nullptr;
struct FunctionPointers level1_; struct FunctionPointers level1_;
struct FunctionPointers level3_; struct FunctionPointers level3_;
std::map<OEMCrypto_SESSION, LevelSession> session_map_; std::map<OEMCrypto_SESSION, LevelSession> session_map_;

View File

@@ -25,7 +25,6 @@ using ::testing::Ge;
using ::testing::Le; using ::testing::Le;
namespace { namespace {
const uint8_t kOemCert[] = { const uint8_t kOemCert[] = {
0x30, 0x82, 0x09, 0xf7, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x30, 0x82, 0x09, 0xf7, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
0x01, 0x07, 0x02, 0xa0, 0x82, 0x09, 0xe8, 0x30, 0x82, 0x09, 0xe4, 0x02, 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; const uint32_t kOemCertSystemId = 7346;
constexpr uint32_t kNullSystemId =
static_cast<uint32_t>(std::numeric_limits<int>::max());
} // namespace } // namespace
namespace wvcdm { namespace wvcdm {
@@ -274,14 +275,16 @@ TEST(CryptoSessionTest, CanExtractSystemIdFromOemCertificate) {
class CryptoSessionMetricsTest : public WvCdmTestBase { class CryptoSessionMetricsTest : public WvCdmTestBase {
protected: protected:
uint32_t FindKeyboxSystemID() { uint32_t FindKeyboxSystemID() {
OEMCryptoResult sts; if (CryptoSession::needs_keybox_provisioning()) {
return kNullSystemId;
}
uint8_t key_data[256]; uint8_t key_data[256];
size_t key_data_len = sizeof(key_data); size_t key_data_len = sizeof(key_data);
sts = OEMCrypto_GetKeyData(key_data, &key_data_len, kLevelDefault); const OEMCryptoResult sts =
if (sts != OEMCrypto_SUCCESS) return 0; OEMCrypto_GetKeyData(key_data, &key_data_len, kLevelDefault);
uint32_t* data = reinterpret_cast<uint32_t*>(key_data); if (sts != OEMCrypto_SUCCESS) return kNullSystemId;
uint32_t system_id = htonl(data[1]); const uint32_t* data = reinterpret_cast<uint32_t*>(key_data);
return system_id; return htonl(data[1]);
} }
}; };
@@ -318,18 +321,24 @@ TEST_F(CryptoSessionMetricsTest, OpenSessionValidMetrics) {
CdmClientTokenType token_type = session->GetPreProvisionTokenType(); CdmClientTokenType token_type = session->GetPreProvisionTokenType();
if (token_type == kClientTokenKeybox) { if (token_type == kClientTokenKeybox) {
uint32_t system_id = FindKeyboxSystemID(); const uint32_t expected_system_id = FindKeyboxSystemID();
EXPECT_EQ(system_id, metrics_proto.crypto_session_system_id().int_value()); 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, EXPECT_EQ(OEMCrypto_Keybox,
metrics_proto.oemcrypto_provisioning_method().int_value()); 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) { } else if (token_type == kClientTokenOemCert) {
// Recent devices all have a system id between 1k and 6 or 7k. Errors // 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 // 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 // garbage. These errors will most likely be outside the range of 1000
// to 2^16. // to 2^16.
EXPECT_LE(1000, metrics_proto.crypto_session_system_id().int_value()); 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, EXPECT_EQ(OEMCrypto_OEMCertificate,
metrics_proto.oemcrypto_provisioning_method().int_value()); 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()); ASSERT_EQ(1, metrics_proto.crypto_session_get_token().size());
EXPECT_EQ(1, metrics_proto.crypto_session_get_token(0).count()); EXPECT_EQ(1, metrics_proto.crypto_session_get_token(0).count());
uint32_t system_id = FindKeyboxSystemID(); const uint32_t expected_system_id = FindKeyboxSystemID();
EXPECT_EQ(system_id, metrics_proto.crypto_session_system_id().int_value()); const uint32_t recorded_system_id =
EXPECT_EQ(1, metrics_proto.oemcrypto_get_key_data_time_us().size()); 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) { } else if (token_type == kClientTokenOemCert) {
// Recent devices all have a system id between 1k and 6 or 7k. Errors // 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 // we are trying to catch are 0, byte swapped 32 bit numbers, or