Move system ID extraction outside of CryptoSession.

[ Merge of http://go/wvgerrit/151391 ]

This CL moves the logic for extracting the system ID from keybox or
OEM certificate (from OEMCrypto or device files) to a dedicated
SystemIdExtractor.

Before Provisioning 4.0, the system ID could only be found from data
returned by OEMCrypto.  However, with provisioning 4.0, the system ID
can now be found in the OEM certificate that is stored on the device
files.

Bug: 232020319
Test: system_id_extractor_unittest
Test: Forest L37800000954493485
Change-Id: Ie1b7987906e2e4fef015cd659a947b6dbb7594b1
This commit is contained in:
Alex Dale
2022-05-09 16:51:39 -07:00
parent 8ac7ca3f46
commit 9d169a00bb
12 changed files with 945 additions and 431 deletions

View File

@@ -25,6 +25,7 @@
#include "ota_keybox_provisioner.h"
#include "properties.h"
#include "string_conversions.h"
#include "system_id_extractor.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
@@ -839,6 +840,17 @@ CdmResponseType CdmEngine::QueryStatus(RequestedSecurityLevel security_level,
}
return NO_ERROR;
}
if (query_token == QUERY_KEY_SYSTEM_ID) {
SystemIdExtractor extractor(security_level, crypto_session.get(),
file_system_);
uint32_t system_id;
if (!extractor.ExtractSystemId(&system_id)) {
LOGW("ExtractSystemId failed");
return UNKNOWN_ERROR;
}
*query_response = std::to_string(system_id);
return NO_ERROR;
}
CdmResponseType status;
M_TIME(status = crypto_session->Open(security_level),
@@ -857,16 +869,6 @@ CdmResponseType CdmEngine::QueryStatus(RequestedSecurityLevel security_level,
*query_response = device_id;
return NO_ERROR;
}
if (query_token == QUERY_KEY_SYSTEM_ID) {
uint32_t system_id;
const bool got_id = crypto_session->GetSystemId(&system_id);
if (!got_id) {
LOGW("QUERY_KEY_SYSTEM_ID unknown failure");
return UNKNOWN_ERROR;
}
*query_response = std::to_string(system_id);
return NO_ERROR;
}
if (query_token == QUERY_KEY_PROVISIONING_ID) {
std::string provisioning_id;
status = crypto_session->GetProvisioningId(&provisioning_id);

View File

@@ -72,8 +72,6 @@ constexpr uint32_t kRsaSignatureLength = 256;
constexpr size_t kEstimatedInitialUsageTableHeader = 40;
const size_t kAes128BlockSize = 16;
// Constants and utility objects relating to OEM Certificates
constexpr const char* kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
constexpr int kMaxTerminateCountDown = 5;
const std::string kStringNotAvailable = "NA";
@@ -100,11 +98,6 @@ static_assert(wvutil::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<uint32_t>(std::numeric_limits<int>::max());
constexpr size_t kMaxExternalDeviceIdLength = 64;
// This maps a few common OEMCryptoResult to CdmResponseType. Many mappings
@@ -269,7 +262,7 @@ OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) {
CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics)
: metrics_(metrics),
system_id_(-1),
system_id_(NULL_SYSTEM_ID),
open_(false),
pre_provision_token_type_(kClientTokenUninitialized),
update_usage_table_after_close_session_(false),
@@ -525,76 +518,102 @@ bool CryptoSession::SetUpUsageTableHeader(
return true;
}
CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) {
CdmResponseType CryptoSession::GetTokenFromKeybox(
RequestedSecurityLevel requested_security_level, std::string* key_data) {
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<uint8_t*>(&temp_buffer[0]);
RETURN_IF_NULL(key_data, PARAMETER_NULL);
LOGV("requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
// Devices with an invalid L1 keybox which support OTA keybox
// provisioning don't have keybox data.
const bool keybox_provisioning_required = WithStaticFieldReadLock(
"GetTokenFromKeybox - keybox_provisioning_required", [&] {
if (requested_security_level_ != kLevelDefault) return false;
return needs_keybox_provisioning_;
});
if (keybox_provisioning_required) return NEED_PROVISIONING;
size_t key_data_length = KEYBOX_KEY_DATA_SIZE;
key_data->assign(key_data_length, '\0');
OEMCryptoResult status;
WithOecReadLock("GetTokenFromKeybox", [&] {
M_TIME(status =
OEMCrypto_GetKeyData(buf, &buf_size, requested_security_level_),
M_TIME(status = OEMCrypto_GetKeyData(
reinterpret_cast<uint8_t*>(&key_data->front()), &key_data_length,
requested_security_level),
metrics_, oemcrypto_get_key_data_, status,
metrics::Pow2Bucket(buf_size));
metrics::Pow2Bucket(key_data_length));
});
if (OEMCrypto_SUCCESS == status) {
token->swap(temp_buffer);
key_data->resize(key_data_length);
return NO_ERROR;
}
key_data->clear();
return MapOEMCryptoResult(status, GET_TOKEN_FROM_KEYBOX_ERROR,
"GetTokenFromKeybox");
}
CdmResponseType CryptoSession::GetTokenFromOemCert(std::string* token) {
CdmResponseType CryptoSession::GetTokenFromOemCert(
RequestedSecurityLevel requested_security_level, std::string* oem_cert) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(token, PARAMETER_NULL);
RETURN_IF_NULL(oem_cert, PARAMETER_NULL);
LOGV("requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
OEMCryptoResult status;
if (!oem_token_.empty()) {
token->assign(oem_token_);
return NO_ERROR;
}
const bool cache_success =
WithOecSessionLock("GetTokenFromOemCert - check cached", [&] {
if (oem_token_.empty()) {
return false;
}
oem_cert->assign(oem_token_);
return true;
});
if (cache_success) return NO_ERROR;
std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0');
bool retrying = false;
while (true) {
size_t buf_size = temp_buffer.size();
uint8_t* buf = reinterpret_cast<uint8_t*>(&temp_buffer[0]);
WithOecSessionLock("GetTokenFromOemCert", [&] {
status = OEMCrypto_GetOEMPublicCertificate(buf, &buf_size,
requested_security_level_);
size_t oem_cert_length = CERTIFICATE_DATA_SIZE;
oem_cert->assign(oem_cert_length, '\0');
OEMCryptoResult status =
WithOecReadLock("GetTokenFromOemCert - attempt 1", [&] {
return OEMCrypto_GetOEMPublicCertificate(
reinterpret_cast<uint8_t*>(&oem_cert->front()), &oem_cert_length,
requested_security_level);
});
metrics_->oemcrypto_get_oem_public_certificate_.Increment(status);
if (status == OEMCrypto_ERROR_SHORT_BUFFER) {
oem_cert->assign(oem_cert_length, '\0');
status = WithOecReadLock("GetTokenFromOemCert - attempt 2", [&] {
return OEMCrypto_GetOEMPublicCertificate(
reinterpret_cast<uint8_t*>(&oem_cert->front()), &oem_cert_length,
requested_security_level);
});
metrics_->oemcrypto_get_oem_public_certificate_.Increment(status);
if (OEMCrypto_SUCCESS == status) {
temp_buffer.resize(buf_size);
oem_token_.assign(temp_buffer);
token->assign(temp_buffer);
return NO_ERROR;
}
if (status == OEMCrypto_ERROR_SHORT_BUFFER && !retrying) {
temp_buffer.resize(buf_size);
retrying = true;
continue;
}
return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR,
"GetTokenFromOemCert");
}
if (status == OEMCrypto_SUCCESS) {
oem_cert->resize(oem_cert_length);
WithOecSessionLock("GetTokenFromOemCert - set cache",
[&] { oem_token_ = *oem_cert; });
return NO_ERROR;
}
oem_cert->clear();
return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR,
"GetTokenFromOemCert");
}
CdmResponseType CryptoSession::GetProvisioningToken(
std::string* token, std::string* additional_token) {
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
return GetProvisioningToken(requested_security_level_, token,
additional_token);
}
CdmResponseType CryptoSession::GetProvisioningToken(
RequestedSecurityLevel requested_security_level, std::string* token,
std::string* additional_token) {
if (token == nullptr || additional_token == nullptr) {
metrics_->crypto_session_get_token_.Increment(PARAMETER_NULL);
RETURN_IF_NULL(token, PARAMETER_NULL);
RETURN_IF_NULL(additional_token, PARAMETER_NULL);
}
if (!IsInitialized()) {
metrics_->crypto_session_get_token_.Increment(
CRYPTO_SESSION_NOT_INITIALIZED);
@@ -603,11 +622,12 @@ CdmResponseType CryptoSession::GetProvisioningToken(
CdmResponseType status = UNKNOWN_CLIENT_TOKEN_TYPE;
if (pre_provision_token_type_ == kClientTokenKeybox) {
status = GetTokenFromKeybox(token);
status = GetTokenFromKeybox(requested_security_level, token);
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
status = GetTokenFromOemCert(token);
status = GetTokenFromOemCert(requested_security_level, token);
} else if (pre_provision_token_type_ == kClientTokenBootCertChain) {
status = GetBootCertificateChain(token, additional_token);
status = GetBootCertificateChain(requested_security_level, token,
additional_token);
}
metrics_->crypto_session_get_token_.Increment(status);
return status;
@@ -689,7 +709,7 @@ CdmResponseType CryptoSession::GetInternalDeviceUniqueId(
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
pre_provision_token_type_ == kClientTokenOemCert) {
return GetTokenFromOemCert(device_id);
return GetTokenFromOemCert(requested_security_level_, device_id);
}
const bool use_null_device_id = WithStaticFieldReadLock(
@@ -772,84 +792,23 @@ bool CryptoSession::GetApiMinorVersion(RequestedSecurityLevel security_level,
return true;
}
bool CryptoSession::GetSystemId(uint32_t* system_id) {
bool CryptoSession::GetCachedSystemId(uint32_t* system_id) {
RETURN_IF_NULL(system_id, false);
RETURN_IF_UNINITIALIZED(false);
RETURN_IF_NOT_OPEN(false);
if (system_id_ == NULL_SYSTEM_ID) return false;
*system_id = system_id_;
return true;
}
// This method gets the system id from the keybox key data.
// This method assumes that OEMCrypto has been initialized before making this
// call.
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;
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;
}
// Decode 32-bit int encoded as network-byte-order byte array starting at
// index 4.
const uint32_t* id = reinterpret_cast<const uint32_t*>(&token[4]);
*system_id = ntohl(*id);
return NO_ERROR;
}
if (pre_provision_token_type_ == kClientTokenOemCert) {
// Get the OEM Cert
std::string oem_cert;
CdmResponseType status = GetTokenFromOemCert(&oem_cert);
if (status != NO_ERROR) return status;
if (!ExtractSystemIdFromOemCert(oem_cert, system_id))
return EXTRACT_SYSTEM_ID_FROM_OEM_CERT_ERROR;
return NO_ERROR;
}
if (pre_provision_token_type_ == kClientTokenDrmCert) {
// TODO(blueeyes): Support loading the system id from a pre-provisioned
// Drm certificate.
return NO_ERROR;
}
if (pre_provision_token_type_ == kClientTokenBootCertChain) {
// A system id can not be inferred from BCC. If the provisioning process has
// come to the second stage, we may read system id from the stored OEM cert.
return NO_ERROR;
}
LOGE("Unsupported pre-provision token type: %d",
static_cast<int>(pre_provision_token_type_));
return UNKNOWN_CLIENT_TOKEN_TYPE;
}
bool CryptoSession::ExtractSystemIdFromOemCert(const std::string& oem_cert,
uint32_t* system_id) {
return ExtractExtensionValueFromCertificate(
oem_cert, kWidevineSystemIdExtensionOid, /* cert_index */ 1, system_id);
void CryptoSession::SetSystemId(uint32_t system_id) {
if (!IsOpen()) return; // Ignore silently.
system_id_ = system_id;
metrics_->crypto_session_system_id_.Record(system_id_);
}
CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) {
RETURN_IF_NULL(provisioning_id, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
if (pre_provision_token_type_ == kClientTokenOemCert) {
// OEM Cert devices have no provisioning-unique ID embedded in them, so we
@@ -868,7 +827,8 @@ CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) {
}
if (pre_provision_token_type_ == kClientTokenKeybox) {
std::string token;
CdmResponseType status = GetTokenFromKeybox(&token);
CdmResponseType status =
GetTokenFromKeybox(requested_security_level_, &token);
if (status != NO_ERROR) return status;
@@ -940,16 +900,6 @@ CdmResponseType CryptoSession::Open(
LOGV("Opened session: id = %u", oec_session_id_);
open_ = true;
// Get System ID and save it.
result = GetSystemIdInternal(&system_id_);
if (result == NO_ERROR) {
metrics_->crypto_session_system_id_.Record(system_id_);
} else {
LOGE("Failed to fetch system ID");
metrics_->crypto_session_system_id_.SetError(result);
return result;
}
// Set up request ID
uint64_t request_id_base;
OEMCryptoResult random_sts;
@@ -989,6 +939,8 @@ void CryptoSession::Close() {
// Clear cached values.
has_usage_info_support_ = kBooleanUnset;
oem_token_.clear();
system_id_ = NULL_SYSTEM_ID;
pre_provision_token_type_ = kClientTokenUninitialized;
if (close_sts != OEMCrypto_SUCCESS) {
LOGW("OEMCrypto_CloseSession failed: status = %d",
@@ -1412,13 +1364,26 @@ CdmResponseType CryptoSession::LoadCertificatePrivateKey(
CdmResponseType CryptoSession::GetBootCertificateChain(
std::string* bcc, std::string* additional_signature) {
RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN);
return GetBootCertificateChain(requested_security_level_, bcc,
additional_signature);
}
CdmResponseType CryptoSession::GetBootCertificateChain(
RequestedSecurityLevel requested_security_level, std::string* bcc,
std::string* additional_signature) {
RETURN_IF_NULL(bcc, PARAMETER_NULL);
RETURN_IF_NULL(additional_signature, PARAMETER_NULL);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
LOGV("GetBootCertificateChain");
LOGV("requested_security_level = %s",
RequestedSecurityLevelToString(requested_security_level));
if (pre_provision_token_type_ != kClientTokenBootCertChain) {
return PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR;
}
if (requested_security_level != kLevelDefault) {
LOGE("CDM only supports L1 BCC");
return NOT_IMPLEMENTED_ERROR;
}
size_t bcc_length = 0;
size_t additional_signature_length = 0;
@@ -3346,12 +3311,6 @@ CdmResponseType CryptoSession::LoadOtaProvisioning(
WithOecWriteLock("LoadOtaProvisioning",
[&] { needs_keybox_provisioning_ = false; });
}
CdmResponseType result = GetSystemIdInternal(&system_id_);
if (result == NO_ERROR) {
LOGD("New system id is %d", system_id_);
} else {
LOGE("Failed to fetch system ID");
}
return MapOEMCryptoResult(status, UNKNOWN_ERROR, "LoadOtaProvisioning");
}

View File

@@ -0,0 +1,190 @@
// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "system_id_extractor.h"
#include <assert.h>
#include "crypto_session.h"
#include "device_files.h"
#include "privacy_crypto.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
namespace wvcdm {
namespace {
// Offset within the keybox data where the device ID is stored.
constexpr size_t kKeyboxSystemIdOffset = 4;
// Index of certificate within cerificate chain which contains the
// system ID (0 = leaf/device cert, 1 = intermediate/device family cert).
constexpr size_t kOemCertSystemIdIndex = 1;
// OID of X.509 certificate extension containing the Widevine system ID.
const std::string kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
constexpr size_t kSystemIdLength = sizeof(uint32_t);
constexpr bool IsSupportedSecurityLevel(CdmSecurityLevel security_level) {
return security_level == kSecurityLevelL1 ||
security_level == kSecurityLevelL2 ||
security_level == kSecurityLevelL3;
}
} // namespace
SystemIdExtractor::SystemIdExtractor(RequestedSecurityLevel security_level,
CryptoSession* crypto_session,
wvutil::FileSystem* fs)
: security_level_(security_level),
crypto_session_(crypto_session),
fs_(fs) {
assert(crypto_session != nullptr);
assert(fs != nullptr);
}
bool SystemIdExtractor::ExtractSystemId(uint32_t* system_id) {
if (system_id == nullptr) {
LOGE("Output |system_id| is null");
return false;
}
if (crypto_session_->GetCachedSystemId(system_id)) {
return true;
}
CdmClientTokenType type = kClientTokenUninitialized;
const CdmResponseType status =
crypto_session_->GetProvisioningMethod(security_level_, &type);
if (status != NO_ERROR) {
LOGE("Failed to get provisioning method: security_level = %s, status = %d",
RequestedSecurityLevelToString(security_level_),
static_cast<int>(status));
return false;
}
bool success = false;
switch (type) {
case kClientTokenDrmCert:
LOGE("Cannot get a system ID from a DRM certificate");
return false;
case kClientTokenKeybox:
success = ExtractSystemIdProv20(system_id);
break;
case kClientTokenOemCert:
success = ExtractSystemIdProv30(system_id);
break;
case kClientTokenBootCertChain:
success = ExtractSystemIdProv40(system_id);
break;
case kClientTokenUninitialized:
default:
LOGE("Unexpected token type: %d", type);
return false;
}
if (success && *system_id != NULL_SYSTEM_ID) {
crypto_session_->SetSystemId(*system_id);
}
return success;
}
// static
bool SystemIdExtractor::ExtractSystemIdFromKeyboxData(
const std::string& key_data, uint32_t* system_id) {
if (system_id == nullptr) return false;
if (key_data.size() < (kKeyboxSystemIdOffset + kSystemIdLength)) {
LOGE("Keybox data does not contain system ID: key_data_size = %zu",
key_data.size());
return false;
}
uint32_t system_id_nbo = 0;
memcpy(&system_id_nbo, &key_data[kKeyboxSystemIdOffset],
sizeof(system_id_nbo));
*system_id = ntohl(system_id_nbo);
return true;
}
// static
bool SystemIdExtractor::ExtractSystemIdFromOemCert(const std::string& oem_cert,
uint32_t* system_id) {
if (system_id == nullptr) return false;
return ExtractExtensionValueFromCertificate(oem_cert,
kWidevineSystemIdExtensionOid,
kOemCertSystemIdIndex, system_id);
}
bool SystemIdExtractor::ExtractSystemIdProv20(uint32_t* system_id) {
std::string key_data;
const CdmResponseType status =
crypto_session_->GetTokenFromKeybox(security_level_, &key_data);
if (status == NEED_PROVISIONING) {
LOGD("Keybox provisioning required, using null system ID");
*system_id = NULL_SYSTEM_ID;
return true;
}
if (status != NO_ERROR) {
LOGE("Failed to get keybox data: security_level = %s, status = %d",
RequestedSecurityLevelToString(security_level_),
static_cast<int>(status));
return false;
}
if (!ExtractSystemIdFromKeyboxData(key_data, system_id)) {
LOGE("Failed to extract system ID from keybox data");
return false;
}
return true;
}
bool SystemIdExtractor::ExtractSystemIdProv30(uint32_t* system_id) {
std::string oem_cert;
const CdmResponseType status =
crypto_session_->GetTokenFromOemCert(security_level_, &oem_cert);
if (status != NO_ERROR) {
LOGE("Failed to get OEM certificate: security_level = %s, status = %d",
RequestedSecurityLevelToString(security_level_),
static_cast<int>(status));
return false;
}
if (!ExtractSystemIdFromOemCert(oem_cert, system_id)) {
LOGE("Failed to extract system ID from OEM certificate");
return false;
}
return true;
}
bool SystemIdExtractor::ExtractSystemIdProv40(uint32_t* system_id) {
const CdmSecurityLevel security_level =
crypto_session_->GetSecurityLevel(security_level_);
if (!IsSupportedSecurityLevel(security_level)) {
LOGE("Unsupported security level: %s",
CdmSecurityLevelToString(security_level));
return false;
}
DeviceFiles real_device_files(fs_);
// Mock DeviceFiles for testing.
DeviceFiles& device_files =
(test_device_files_ ? *test_device_files_ : real_device_files);
if (!device_files.Init(security_level)) {
LOGE("Failed to initialize device files: security_level = %s",
CdmSecurityLevelToString(security_level));
return false;
}
std::string oem_cert;
CryptoWrappedKey wrapped_private_key_unused;
const DeviceFiles::CertificateState cert_state =
device_files.RetrieveOemCertificate(&oem_cert,
&wrapped_private_key_unused);
if (cert_state == DeviceFiles::kCertificateNotFound) {
LOGD("No OEM certificate available, using null system ID");
*system_id = NULL_SYSTEM_ID;
return true;
}
if (cert_state != DeviceFiles::kCertificateValid) {
LOGE(
"Failed to retrieve OEM certificate: "
"security_level = %s, cert_state = %s",
CdmSecurityLevelToString(security_level),
DeviceFiles::CertificateStateToString(cert_state));
return false;
}
if (!ExtractSystemIdFromOemCert(oem_cert, system_id)) {
LOGE("Failed to extract system ID from OEM certificate");
return false;
}
return true;
}
} // namespace wvcdm