Wrapped OKP info into several classes.
[ Merge of http://go/wvgerrit/133744 ] This changes adds several small classes which contain and manage system and engine information related to OTA keybox provisioning. These classes closely map to the OKP device file messages. Bug: 189232882 Test: Linux unit tests Change-Id: Ia9334c38f9d7ea89b30d9ad05f0595570bb38658 Storing and loading OKP info. [ Merge of http://go/wvgerrit/133763 and http://go/ag/15645333 ] This change extends the DeviceFiles module to be able to store and load OKP info. Mild data validation is performed when storing and loading the information. Bug: 189232882 Test: Android unit tests Change-Id: I077de3234157252f2255a4389bf82a8d5344a355 System OKP fallback policy. [ Merge of http://go/wvgerrit/133783 and http://go/ag/15645334 ] SystemFallbackPolicy provides a thread-safe interface for accessing and modifying OKP info. Bug: 189232882 Test: Android unit tests Change-Id: I4e43e3bc047ed5fb6cb517b53e4094e812b70e1e Engine OKP provisioner. [ Merge of http://go/wvgerrit/133803 and http://go/ag/15645335 ] The OtaKeyboxProvisioner provides a CdmEngine-specific context for performing OTA keybox provisioning. Utilizes the system-wide SystemFallbackPolicy to relay provisioning status between engines. The provisioner will handle message wrapping and unwrapping of the raw OTA keybox request / response into the SignedProvisioningMessage which is sent to/received from the provisioning server. [ Partial merge of http://go/wvgerrit/125844 ] Note: Includes partial CryptoSession changes from various CLs. CryptoSession functionality has been stripped to reduce impact of this CL. Bug: 189232882 Test: Android unit tests Change-Id: I282bf7d1887daefb2250af1bd595c4dc3dfcfb29 Integrated OKP into CDM Engine [ Merge of http://go/wvgerrit/133804 and http://go/ag/15646376 ] Extended the functionality of the CdmEngine to check if the device requires OKP and to initialize OKP resources if required. The functionality of OpenSession() and GetProvisioningRequest() have been the most affected. If OKP is required, these methods will signal to the app that provisioning is required and will return an OKP request. Once a device is provisioned, the OKP data is cleared away and the CdmEngine will resume normal operation. Engines created after a device is provisioned will immediately enter normal operations. The exception is for CdmEngines which failed to perform OKP for some reason and are still running. Those apps will need to restart before gaining access to L1 operations. Bug: 187646550 Test: Android integration tests Merged-In: Ia572a66a7b73479355758aa3d0c682691eaca0fc Change-Id: Ia572a66a7b73479355758aa3d0c682691eaca0fc
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
#include "device_files.h"
|
||||
#include "file_store.h"
|
||||
#include "log.h"
|
||||
#include "ota_keybox_provisioner.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
@@ -119,16 +120,56 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
|
||||
}
|
||||
LOGD("forced_session_id = %s", IdPtrToString(forced_session_id));
|
||||
}
|
||||
bool forced_level3 = false;
|
||||
if (OkpCheck()) {
|
||||
bool okp_provisioned = false;
|
||||
bool fallback = false;
|
||||
{
|
||||
std::unique_lock<std::mutex> 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;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
std::unique_lock<std::mutex> lock(okp_mutex_);
|
||||
// |okp_fallback_| would have been set previously if required.
|
||||
if (okp_fallback_) forced_level3 = true;
|
||||
}
|
||||
|
||||
CloseExpiredReleaseSessions();
|
||||
|
||||
std::unique_ptr<CdmSession> new_session(
|
||||
new CdmSession(file_system_, metrics_->AddSession()));
|
||||
const CdmResponseType sts =
|
||||
new_session->Init(property_set, forced_session_id, event_listener);
|
||||
if (sts == NEED_PROVISIONING) {
|
||||
// Reserve a session ID so the CDM can return success.
|
||||
if (session_id) *session_id = new_session->GenerateSessionId();
|
||||
const CdmResponseType sts = new_session->Init(property_set, forced_session_id,
|
||||
event_listener, forced_level3);
|
||||
if (sts != NO_ERROR) {
|
||||
if (sts == NEED_PROVISIONING) {
|
||||
// Reserve a session ID so the CDM can return success.
|
||||
if (session_id) *session_id = new_session->GenerateSessionId();
|
||||
} else {
|
||||
LOGE("Bad session init: status = %d", static_cast<int>(sts));
|
||||
}
|
||||
return sts;
|
||||
}
|
||||
if (sts != NO_ERROR) {
|
||||
@@ -499,6 +540,14 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
|
||||
std::unique_ptr<CryptoSession> crypto_session(
|
||||
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
|
||||
|
||||
// Force OKP check on CryptoSession. Only concerned if engine
|
||||
// has fallen back to L3.
|
||||
if (security_level == kLevelDefault && OkpIsInFallbackMode()) {
|
||||
LOGD("Engine is falling back to L3 for query: token = %s",
|
||||
query_token.c_str());
|
||||
security_level = kLevel3;
|
||||
}
|
||||
|
||||
// Add queries here, that can be answered before a session is opened
|
||||
if (query_token == QUERY_KEY_SECURITY_LEVEL) {
|
||||
const CdmSecurityLevel found_security_level =
|
||||
@@ -877,6 +926,7 @@ CdmResponseType CdmEngine::QueryOemCryptoSessionId(
|
||||
return session->QueryOemCryptoSessionId(query_response);
|
||||
}
|
||||
|
||||
// static
|
||||
bool CdmEngine::IsSecurityLevelSupported(CdmSecurityLevel level) {
|
||||
LOGI("level = %s", CdmSecurityLevelToString(level));
|
||||
metrics::CryptoMetrics alternate_crypto_metrics;
|
||||
@@ -914,6 +964,42 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
|
||||
return INVALID_PROVISIONING_REQUEST_PARAM_2;
|
||||
}
|
||||
|
||||
if (requested_security_level == kLevelDefault) {
|
||||
if (OkpCheck()) {
|
||||
if (okp_provisioner_->IsProvisioned()) {
|
||||
// OKP not required, engine may assume normal operations.
|
||||
OkpCleanUp();
|
||||
} else if (okp_provisioner_->IsInFallbackMode()) {
|
||||
LOGD("Engine is falling back to L3");
|
||||
OkpTriggerFallback();
|
||||
requested_security_level = kLevel3;
|
||||
} else {
|
||||
// OKP is required.
|
||||
const CdmResponseType status =
|
||||
okp_provisioner_->GetProvisioningRequest(request, default_url);
|
||||
if (status == NO_ERROR) return NO_ERROR;
|
||||
if (status == NOT_IMPLEMENTED_ERROR) {
|
||||
LOGW("OKP not supoprted, falling back to L3");
|
||||
OkpTriggerFallback();
|
||||
requested_security_level = kLevel3;
|
||||
} else if (status == OKP_ALREADY_PROVISIONED) {
|
||||
LOGD("OKP already completed, continuing in normal operation");
|
||||
OkpCleanUp();
|
||||
// Continue with normal provisioning request.
|
||||
} else {
|
||||
LOGE("Failed to generate OKP request: status = %d",
|
||||
static_cast<int>(status));
|
||||
return status;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::unique_lock<std::mutex> lock(okp_mutex_);
|
||||
if (okp_fallback_) {
|
||||
requested_security_level = kLevel3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/141705730): Remove usage entries on provisioning.
|
||||
if (!cert_provisioning_) {
|
||||
cert_provisioning_.reset(
|
||||
@@ -959,6 +1045,32 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
|
||||
cert_provisioning_.reset();
|
||||
return INVALID_PROVISIONING_PARAMETERS_2;
|
||||
}
|
||||
|
||||
if (requested_security_level == kLevelDefault) {
|
||||
bool use_okp = false;
|
||||
CdmResponseType okp_res = UNKNOWN_ERROR;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(okp_mutex_);
|
||||
if (okp_provisioner_) {
|
||||
use_okp = true;
|
||||
// If the engine initiated OKP previously, it must complete it
|
||||
// regardless of whether the device has fallen back to L3.
|
||||
okp_res = okp_provisioner_->HandleProvisioningResponse(response);
|
||||
} else if (okp_fallback_) {
|
||||
requested_security_level = kLevel3;
|
||||
}
|
||||
}
|
||||
if (use_okp) {
|
||||
// Cannot hold lock when calling OkpCleanUp() or OkpTriggerFallback().
|
||||
if (okp_res == NO_ERROR) {
|
||||
OkpCleanUp();
|
||||
} else {
|
||||
OkpTriggerFallback();
|
||||
}
|
||||
return okp_res;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cert_provisioning_) {
|
||||
// Certificate provisioning object has been released. Check if a concurrent
|
||||
// provisioning attempt has succeeded before declaring failure.
|
||||
@@ -991,6 +1103,11 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
|
||||
}
|
||||
|
||||
bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) {
|
||||
LOGI("security_level = %d", static_cast<int>(security_level));
|
||||
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
|
||||
LOGD("OKP fallback to L3");
|
||||
security_level = kSecurityLevelL3;
|
||||
}
|
||||
// To validate whether the given security level is provisioned, we attempt to
|
||||
// initialize a CdmSession. This verifies the existence of a certificate and
|
||||
// attempts to load it. If this fails, initialization will return an error.
|
||||
@@ -1008,6 +1125,10 @@ bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) {
|
||||
|
||||
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
|
||||
LOGI("security_level = %s", CdmSecurityLevelToString(security_level));
|
||||
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
|
||||
LOGD("OKP fallback to L3");
|
||||
security_level = kSecurityLevelL3;
|
||||
}
|
||||
// Devices with baked-in DRM certs cannot be reprovisioned and therefore must
|
||||
// not be unprovisioned.
|
||||
std::unique_ptr<CryptoSession> crypto_session(
|
||||
@@ -1046,11 +1167,15 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
|
||||
|
||||
CdmResponseType CdmEngine::ListStoredLicenses(
|
||||
CdmSecurityLevel security_level, std::vector<std::string>* key_set_ids) {
|
||||
DeviceFiles handle(file_system_);
|
||||
if (!key_set_ids) {
|
||||
LOGE("Output |key_set_ids| is null");
|
||||
return INVALID_PARAMETERS_ENG_22;
|
||||
}
|
||||
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
|
||||
LOGD("OKP fallback to L3");
|
||||
security_level = kSecurityLevelL3;
|
||||
}
|
||||
DeviceFiles handle(file_system_);
|
||||
if (!handle.Init(security_level)) {
|
||||
LOGE("Unable to initialize device files");
|
||||
return LIST_LICENSE_ERROR_1;
|
||||
@@ -1066,11 +1191,15 @@ CdmResponseType CdmEngine::ListUsageIds(
|
||||
const std::string& app_id, CdmSecurityLevel security_level,
|
||||
std::vector<std::string>* ksids,
|
||||
std::vector<std::string>* provider_session_tokens) {
|
||||
DeviceFiles handle(file_system_);
|
||||
if (!ksids && !provider_session_tokens) {
|
||||
LOGE("Outputs |ksids| and |provider_session_tokens| are null");
|
||||
return INVALID_PARAMETERS_ENG_23;
|
||||
}
|
||||
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
|
||||
LOGD("OKP fallback to L3");
|
||||
security_level = kSecurityLevelL3;
|
||||
}
|
||||
DeviceFiles handle(file_system_);
|
||||
if (!handle.Init(security_level)) {
|
||||
LOGE("Unable to initialize device files");
|
||||
return LIST_USAGE_ERROR_1;
|
||||
@@ -1088,6 +1217,10 @@ CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id,
|
||||
const std::string& key_set_id) {
|
||||
LOGI("app_id = %s, key_set_id = %s", IdToString(app_id),
|
||||
IdToString(key_set_id));
|
||||
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
|
||||
LOGD("OKP fallback to L3");
|
||||
security_level = kSecurityLevelL3;
|
||||
}
|
||||
DeviceFiles handle(file_system_);
|
||||
if (!handle.Init(security_level)) {
|
||||
LOGE("Unable to initialize device files");
|
||||
@@ -1105,12 +1238,15 @@ CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id,
|
||||
CdmResponseType CdmEngine::GetOfflineLicenseState(
|
||||
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level,
|
||||
CdmOfflineLicenseState* license_state) {
|
||||
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
|
||||
LOGD("OKP fallback to L3");
|
||||
security_level = kSecurityLevelL3;
|
||||
}
|
||||
DeviceFiles handle(file_system_);
|
||||
if (!handle.Init(security_level)) {
|
||||
LOGE("Cannot initialize device files");
|
||||
return GET_OFFLINE_LICENSE_STATE_ERROR_1;
|
||||
}
|
||||
|
||||
DeviceFiles::CdmLicenseData license_data;
|
||||
DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError;
|
||||
if (!handle.RetrieveLicense(key_set_id, &license_data, &sub_error_code)) {
|
||||
@@ -1124,6 +1260,10 @@ CdmResponseType CdmEngine::GetOfflineLicenseState(
|
||||
|
||||
CdmResponseType CdmEngine::RemoveOfflineLicense(
|
||||
const CdmKeySetId& key_set_id, CdmSecurityLevel security_level) {
|
||||
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
|
||||
LOGD("OKP fallback to L3");
|
||||
security_level = kSecurityLevelL3;
|
||||
}
|
||||
UsagePropertySet property_set;
|
||||
property_set.set_security_level(
|
||||
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault);
|
||||
@@ -1289,7 +1429,10 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
|
||||
LOGE("Output |usage_info| is null");
|
||||
return PARAMETER_NULL;
|
||||
}
|
||||
|
||||
if (requested_security_level == kLevelDefault && OkpIsInFallbackMode()) {
|
||||
LOGD("OKP fallback to L3");
|
||||
requested_security_level = kLevel3;
|
||||
}
|
||||
if (!usage_property_set_) {
|
||||
usage_property_set_.reset(new UsagePropertySet());
|
||||
}
|
||||
@@ -1939,4 +2082,76 @@ void CdmEngine::CloseExpiredReleaseSessions() {
|
||||
CloseSession(*iter);
|
||||
}
|
||||
}
|
||||
|
||||
bool CdmEngine::OkpCheck() {
|
||||
std::unique_lock<std::mutex> lock(okp_mutex_);
|
||||
if (okp_initialized_) {
|
||||
return static_cast<bool>(okp_provisioner_);
|
||||
}
|
||||
okp_initialized_ = true;
|
||||
// Creating a CryptoSession will initialize OEMCrypto and flag the need
|
||||
// for OKP.
|
||||
std::unique_ptr<CryptoSession> crypto_session(
|
||||
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
|
||||
if (!crypto_session->needs_keybox_provisioning()) {
|
||||
// System does not require OKP provisioning.
|
||||
return false;
|
||||
}
|
||||
okp_provisioner_ = OtaKeyboxProvisioner::Create(metrics_->GetCryptoMetrics());
|
||||
if (!okp_provisioner_) {
|
||||
LOGE("Failed to create engine OKP handler, falling back to L3");
|
||||
okp_fallback_ = true;
|
||||
return false;
|
||||
}
|
||||
if (okp_provisioner_->IsProvisioned()) {
|
||||
// This should have been caught by call to needs_keybox_provisioning(),
|
||||
// but possible with simultaneous apps.
|
||||
okp_provisioner_.reset();
|
||||
return false;
|
||||
}
|
||||
if (okp_provisioner_->IsInFallbackMode()) {
|
||||
LOGD("Engine is in OKP fallback mode");
|
||||
okp_fallback_ = true;
|
||||
okp_provisioner_.reset();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmEngine::OkpIsInFallbackMode() {
|
||||
const bool check = OkpCheck();
|
||||
std::unique_lock<std::mutex> lock(okp_mutex_);
|
||||
if (!check || !okp_provisioner_ || okp_fallback_) {
|
||||
return okp_fallback_;
|
||||
}
|
||||
if (!okp_provisioner_->IsInFallbackMode()) {
|
||||
return false;
|
||||
}
|
||||
// Trigger fallback.
|
||||
LOGD("Engine is entering OKP fallback mode");
|
||||
okp_provisioner_.reset();
|
||||
okp_fallback_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CdmEngine::OkpTriggerFallback() {
|
||||
std::unique_lock<std::mutex> lock(okp_mutex_);
|
||||
if (!okp_initialized_) {
|
||||
LOGD("Call to OKP fallback before OKP setup");
|
||||
return;
|
||||
}
|
||||
if (okp_fallback_) return;
|
||||
LOGD("Engine is entering OKP fallback mode");
|
||||
okp_provisioner_.reset();
|
||||
okp_fallback_ = true;
|
||||
}
|
||||
|
||||
void CdmEngine::OkpCleanUp() {
|
||||
std::unique_lock<std::mutex> lock(okp_mutex_);
|
||||
if (!okp_initialized_) {
|
||||
LOGD("Call to OKP fallback before OKP setup");
|
||||
return;
|
||||
}
|
||||
okp_provisioner_.reset();
|
||||
}
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -111,19 +111,21 @@ CdmSession::~CdmSession() {
|
||||
|
||||
CdmResponseType CdmSession::Init(
|
||||
CdmClientPropertySet* cdm_client_property_set) {
|
||||
return Init(cdm_client_property_set, nullptr, nullptr);
|
||||
return Init(cdm_client_property_set, nullptr, nullptr, false);
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set,
|
||||
const CdmSessionId* forced_session_id,
|
||||
WvCdmEventListener* event_listener) {
|
||||
WvCdmEventListener* event_listener,
|
||||
bool forced_level3) {
|
||||
if (initialized_) {
|
||||
LOGE("Failed due to previous initialization");
|
||||
return REINIT_ERROR;
|
||||
}
|
||||
|
||||
if (cdm_client_property_set && cdm_client_property_set->security_level() ==
|
||||
QUERY_VALUE_SECURITY_LEVEL_L3) {
|
||||
if ((cdm_client_property_set && cdm_client_property_set->security_level() ==
|
||||
QUERY_VALUE_SECURITY_LEVEL_L3) ||
|
||||
forced_level3) {
|
||||
requested_security_level_ = kLevel3;
|
||||
security_level_ = kSecurityLevelL3;
|
||||
}
|
||||
|
||||
@@ -51,59 +51,6 @@ const std::string kCpProductionServiceCertificate = wvcdm::a2bs_hex(
|
||||
"26e0c050f3fd3ebe68cef9903ef6405b25fc6e31f93559fcff05657662b3653a"
|
||||
"8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb"
|
||||
"1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28");
|
||||
|
||||
/*
|
||||
* Provisioning response is a base64-encoded protobuf, optionally within a
|
||||
* JSON wrapper. If the JSON wrapper is present, extract the embedded response
|
||||
* message. Then perform the base64 decode and return the result.
|
||||
*
|
||||
* If an error occurs during the parse or the decode, return an empty string.
|
||||
*/
|
||||
bool ExtractAndDecodeSignedMessage(const std::string& provisioning_response,
|
||||
std::string* result) {
|
||||
const std::string json_start_substr("\"signedResponse\": \"");
|
||||
const std::string json_end_substr("\"");
|
||||
std::string message_string;
|
||||
|
||||
if (result == nullptr) {
|
||||
LOGE("Output parameter |result| is not provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t start = provisioning_response.find(json_start_substr);
|
||||
|
||||
if (start == provisioning_response.npos) {
|
||||
// Message is not properly wrapped - reject it.
|
||||
LOGE("Cannot locate start substring");
|
||||
result->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Appears to be JSON-wrapped protobuf - find end of protobuf portion.
|
||||
const size_t end = provisioning_response.find(
|
||||
json_end_substr, start + json_start_substr.length());
|
||||
if (end == provisioning_response.npos) {
|
||||
LOGE("Cannot locate end substring");
|
||||
result->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t b64_string_size = end - start - json_start_substr.length();
|
||||
message_string.assign(provisioning_response,
|
||||
start + json_start_substr.length(), b64_string_size);
|
||||
|
||||
if (message_string.empty()) {
|
||||
LOGE("CDM provisioning response is empty");
|
||||
result->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode the base64-encoded message.
|
||||
const std::vector<uint8_t> decoded_message =
|
||||
wvcdm::Base64SafeDecode(message_string);
|
||||
result->assign(decoded_message.begin(), decoded_message.end());
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
// Protobuf generated classes.
|
||||
using video_widevine::ClientIdentification_ClientCapabilities;
|
||||
@@ -497,10 +444,56 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// Static
|
||||
bool CertificateProvisioning::ExtractAndDecodeSignedMessageForTesting(
|
||||
// Provisioning response is a base64-encoded protobuf, optionally within a
|
||||
// JSON wrapper. If the JSON wrapper is present, extract the embedded response
|
||||
// message. Then perform the base64 decode and return the result.
|
||||
//
|
||||
// If an error occurs during the parse or the decode, return an empty string.
|
||||
// static
|
||||
bool CertificateProvisioning::ExtractAndDecodeSignedMessage(
|
||||
const std::string& provisioning_response, std::string* result) {
|
||||
return ExtractAndDecodeSignedMessage(provisioning_response, result);
|
||||
const std::string json_start_substr("\"signedResponse\": \"");
|
||||
const std::string json_end_substr("\"");
|
||||
std::string message_string;
|
||||
|
||||
if (result == nullptr) {
|
||||
LOGE("Output parameter |result| is not provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t start = provisioning_response.find(json_start_substr);
|
||||
|
||||
if (start == provisioning_response.npos) {
|
||||
// Message is not properly wrapped - reject it.
|
||||
LOGE("Cannot locate start substring");
|
||||
result->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Appears to be JSON-wrapped protobuf - find end of protobuf portion.
|
||||
const size_t end = provisioning_response.find(
|
||||
json_end_substr, start + json_start_substr.length());
|
||||
if (end == provisioning_response.npos) {
|
||||
LOGE("Cannot locate end substring");
|
||||
result->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t b64_string_size = end - start - json_start_substr.length();
|
||||
message_string.assign(provisioning_response,
|
||||
start + json_start_substr.length(), b64_string_size);
|
||||
|
||||
if (message_string.empty()) {
|
||||
LOGE("CDM provisioning response is empty");
|
||||
result->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode the base64-encoded message.
|
||||
const std::vector<uint8_t> decoded_message =
|
||||
wvcdm::Base64SafeDecode(message_string);
|
||||
result->assign(decoded_message.begin(), decoded_message.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CertificateProvisioning::ExtractDeviceInfo(
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "crypto_key.h"
|
||||
#include "entitlement_key_session.h"
|
||||
#include "log.h"
|
||||
#include "ota_keybox_provisioner.h"
|
||||
#include "okp_fallback_policy.h"
|
||||
#include "platform.h"
|
||||
#include "privacy_crypto.h"
|
||||
#include "properties.h"
|
||||
@@ -179,7 +179,8 @@ std::unique_ptr<UsageTableHeader> CryptoSession::usage_table_header_l1_;
|
||||
std::unique_ptr<UsageTableHeader> CryptoSession::usage_table_header_l3_;
|
||||
std::recursive_mutex CryptoSession::usage_table_mutex_;
|
||||
std::atomic<uint64_t> CryptoSession::request_id_index_source_(0);
|
||||
std::unique_ptr<OtaKeyboxProvisioner> CryptoSession::ota_keybox_provisioner_l1_;
|
||||
std::unique_ptr<okp::SystemFallbackPolicy>
|
||||
CryptoSession::okp_fallback_policy_l1_;
|
||||
|
||||
size_t GetOffset(std::string message, std::string field) {
|
||||
size_t pos = message.find(field);
|
||||
@@ -362,13 +363,15 @@ void CryptoSession::Init() {
|
||||
LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(),
|
||||
api_minor_version.c_str());
|
||||
if (needs_keybox_provisioning_) {
|
||||
WithStaticFieldWriteLock("OtaKeyboxProvisioner", [&] {
|
||||
if (!ota_keybox_provisioner_l1_) {
|
||||
WithStaticFieldWriteLock("SystemFallbackPolicy", [&] {
|
||||
if (!okp_fallback_policy_l1_) {
|
||||
LOGD("OEMCrypto needs keybox provisioning");
|
||||
// Only create once. Possible that OEMCrypto is initialized
|
||||
// and terminated many times over the life cycle of the OTA
|
||||
// keybox provisioning process.
|
||||
ota_keybox_provisioner_l1_ = OtaKeyboxProvisioner::Create();
|
||||
okp_fallback_policy_l1_ = okp::SystemFallbackPolicy::Create();
|
||||
if (okp_fallback_policy_l1_)
|
||||
okp_fallback_policy_l1_->MarkNeedsProvisioning();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -3023,16 +3026,16 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks(
|
||||
return sts;
|
||||
}
|
||||
|
||||
OtaKeyboxProvisioner* CryptoSession::GetOtaKeyboxProvisioner() {
|
||||
const auto getter = [&]() -> OtaKeyboxProvisioner* {
|
||||
okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() {
|
||||
const auto getter = [&]() -> okp::SystemFallbackPolicy* {
|
||||
// If not set, then OTA keybox provisioning is not supported or
|
||||
// not needed.
|
||||
if (!ota_keybox_provisioner_l1_) return nullptr;
|
||||
if (!okp_fallback_policy_l1_) return nullptr;
|
||||
// May have already been initialized.
|
||||
if (ota_keybox_provisioner_l1_->IsProvisioned()) return nullptr;
|
||||
return ota_keybox_provisioner_l1_.get();
|
||||
if (okp_fallback_policy_l1_->IsProvisioned()) return nullptr;
|
||||
return okp_fallback_policy_l1_.get();
|
||||
};
|
||||
return WithStaticFieldReadLock("GetOtaKeyboxProvisioner", getter);
|
||||
return WithStaticFieldReadLock("GetOkpFallbackPolicy", getter);
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::PrepareOtaProvisioningRequest(
|
||||
|
||||
@@ -95,6 +95,7 @@ const char kTrue[] = "true";
|
||||
const char kUsageInfoFileNameExt[] = ".bin";
|
||||
const char kUsageInfoFileNamePrefix[] = "usage";
|
||||
const char kUsageTableFileName[] = "usgtable.bin";
|
||||
const char kOkpInfoFileName[] = "okp.bin";
|
||||
const char kWildcard[] = "*";
|
||||
// TODO(b/192430982): Renable expiration of legacy DRM certificates
|
||||
// constexpr int64_t kFourMonthsInSeconds = (2 * 30 + 2 * 31) * 24 * 60 * 60;
|
||||
@@ -1627,6 +1628,164 @@ bool DeviceFiles::HasCertificate(CertificateType certificate_type) {
|
||||
return FileExists(certificate_file_name);
|
||||
}
|
||||
|
||||
bool DeviceFiles::StoreOkpInfo(const okp::SystemFallbackInfo& info) {
|
||||
using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo;
|
||||
using okp::SystemState;
|
||||
RETURN_FALSE_IF_UNINITIALIZED();
|
||||
if (security_level_ != kSecurityLevelL1) {
|
||||
LOGE("OKP info is only supported by L1: level = %d",
|
||||
static_cast<int>(security_level_));
|
||||
return false;
|
||||
}
|
||||
video_widevine_client::sdk::File file;
|
||||
file.set_type(video_widevine_client::sdk::File::OKP_INFO);
|
||||
file.set_version(video_widevine_client::sdk::File::VERSION_1);
|
||||
StoredOkpInfo* stored_info = file.mutable_okp_info();
|
||||
switch (info.state()) {
|
||||
case SystemState::kNeedsProvisioning:
|
||||
stored_info->set_state(StoredOkpInfo::OKP_NEEDS_PROVISIONING);
|
||||
break;
|
||||
case SystemState::kFallbackMode:
|
||||
stored_info->set_state(StoredOkpInfo::OKP_FALLBACK_MODE);
|
||||
break;
|
||||
case SystemState::kProvisioned:
|
||||
stored_info->set_state(StoredOkpInfo::OKP_PROVISIONED);
|
||||
break;
|
||||
case SystemState::kUnknown:
|
||||
default:
|
||||
LOGE("Unexpected OKP state: state = %d", static_cast<int>(info.state()));
|
||||
return false;
|
||||
}
|
||||
if (info.first_checked_time() <= 0) {
|
||||
LOGE("OKP first checked time is missing");
|
||||
return false;
|
||||
}
|
||||
stored_info->set_first_checked_time(info.first_checked_time());
|
||||
|
||||
if (info.state() == SystemState::kProvisioned) {
|
||||
if (!info.HasProvisioningTime()) {
|
||||
LOGE("OKP set as provisioned, but missing provisioning time");
|
||||
return false;
|
||||
}
|
||||
stored_info->set_provisioning_time(info.provisioning_time());
|
||||
} else if (info.state() == SystemState::kFallbackMode) {
|
||||
if (!info.HasBackoffStartTime() || !info.HasBackoffDuration()) {
|
||||
LOGE("OKP fallback information is missing ");
|
||||
return false;
|
||||
}
|
||||
stored_info->set_backoff_start_time(info.backoff_start_time());
|
||||
stored_info->set_backoff_duration(info.backoff_duration());
|
||||
} else {
|
||||
if (info.HasBackoffDuration()) {
|
||||
// Store backoff duration from before.
|
||||
stored_info->set_backoff_duration(info.backoff_duration());
|
||||
}
|
||||
}
|
||||
|
||||
std::string serialized_file;
|
||||
file.SerializeToString(&serialized_file);
|
||||
return StoreFileWithHash(GetOkpInfoFileName(), serialized_file) == kNoError;
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveOkpInfo(okp::SystemFallbackInfo* info) {
|
||||
using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo;
|
||||
using okp::SystemState;
|
||||
RETURN_FALSE_IF_UNINITIALIZED();
|
||||
RETURN_FALSE_IF_NULL(info);
|
||||
info->Clear();
|
||||
if (security_level_ != kSecurityLevelL1) {
|
||||
LOGE("OKP info is only supported by L1: level = %d",
|
||||
static_cast<int>(security_level_));
|
||||
return false;
|
||||
}
|
||||
// File meta-data validation.
|
||||
video_widevine_client::sdk::File file;
|
||||
if (RetrieveHashedFile(GetOkpInfoFileName(), &file) != kNoError) {
|
||||
LOGE("Unable to retrieve OKP info file");
|
||||
return false;
|
||||
}
|
||||
if (file.type() != video_widevine_client::sdk::File::OKP_INFO) {
|
||||
LOGE("Incorrect file type: type = %d, expected_type = %d",
|
||||
static_cast<int>(file.type()),
|
||||
static_cast<int>(video_widevine_client::sdk::File::OKP_INFO));
|
||||
return false;
|
||||
}
|
||||
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
|
||||
LOGE("Incorrect file version: version = %d, expected_version = %d",
|
||||
static_cast<int>(file.version()),
|
||||
static_cast<int>(video_widevine_client::sdk::File::VERSION_1));
|
||||
return false;
|
||||
}
|
||||
if (!file.has_okp_info()) {
|
||||
// OKP info is only stored if at least 1 field is non-empty. This
|
||||
// must be an error.
|
||||
LOGD("OKP info is not present in file");
|
||||
return false;
|
||||
}
|
||||
|
||||
const StoredOkpInfo& stored_info = file.okp_info();
|
||||
switch (stored_info.state()) {
|
||||
case StoredOkpInfo::OKP_NEEDS_PROVISIONING:
|
||||
info->SetState(SystemState::kNeedsProvisioning);
|
||||
break;
|
||||
case StoredOkpInfo::OKP_FALLBACK_MODE:
|
||||
info->SetState(SystemState::kFallbackMode);
|
||||
break;
|
||||
case StoredOkpInfo::OKP_PROVISIONED:
|
||||
info->SetState(SystemState::kProvisioned);
|
||||
break;
|
||||
case StoredOkpInfo::OKP_UNKNOWN:
|
||||
default:
|
||||
LOGE("Unexpected OKP state: stored_state = %d",
|
||||
static_cast<int>(stored_info.state()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stored_info.first_checked_time() <= 0) {
|
||||
LOGE("OKP first check time not present");
|
||||
info->Clear();
|
||||
return false;
|
||||
}
|
||||
info->SetFirstCheckedTime(stored_info.first_checked_time());
|
||||
|
||||
if (info->state() == SystemState::kProvisioned) {
|
||||
if (stored_info.provisioning_time() <= 0) {
|
||||
LOGE("OKP set as provisioned, but missing provisioning time");
|
||||
info->Clear();
|
||||
return false;
|
||||
}
|
||||
info->SetProvisioningTime(stored_info.provisioning_time());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (info->state() == SystemState::kFallbackMode) {
|
||||
if (stored_info.backoff_start_time() <= 0 ||
|
||||
stored_info.backoff_duration() <= 0) {
|
||||
LOGE("OKP backoff information is missing");
|
||||
info->Clear();
|
||||
return false;
|
||||
}
|
||||
info->SetBackoffStartTime(stored_info.backoff_start_time());
|
||||
info->SetBackoffDuration(stored_info.backoff_duration());
|
||||
return true;
|
||||
}
|
||||
// Provisioned.
|
||||
if (stored_info.backoff_duration() > 0) {
|
||||
info->SetBackoffDuration(stored_info.backoff_duration());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::DeleteOkpInfo() {
|
||||
RETURN_FALSE_IF_UNINITIALIZED();
|
||||
if (security_level_ != kSecurityLevelL1) {
|
||||
LOGE("OKP info is only supported by L1: level = %d",
|
||||
static_cast<int>(security_level_));
|
||||
return false;
|
||||
}
|
||||
return RemoveFile(GetOkpInfoFileName());
|
||||
}
|
||||
|
||||
DeviceFiles::ResponseType DeviceFiles::StoreFileWithHash(
|
||||
const std::string& name, const std::string& serialized_file) {
|
||||
std::string hash = Sha256Hash(serialized_file);
|
||||
@@ -1845,6 +2004,8 @@ std::string DeviceFiles::GetUsageInfoFileName(const std::string& app_id) {
|
||||
return kUsageInfoFileNamePrefix + hash + kUsageInfoFileNameExt;
|
||||
}
|
||||
|
||||
std::string DeviceFiles::GetOkpInfoFileName() { return kOkpInfoFileName; }
|
||||
|
||||
std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) {
|
||||
return Base64SafeEncode(Md5Hash(input));
|
||||
}
|
||||
|
||||
@@ -130,42 +130,6 @@ message UsageTableInfo {
|
||||
// OKP should create this file. Otherwise, this information is not
|
||||
// needed.
|
||||
message OtaKeyboxProvisioningInfo {
|
||||
// Engine-specific information about OKP.
|
||||
message OkpEngineInfo {
|
||||
// Engine identifier.
|
||||
optional bytes app_id = 1;
|
||||
optional bytes origin = 2;
|
||||
reserved 3 to 5; // Reserved for future engine composite keys.
|
||||
// Counters for engine-specific OKP events.
|
||||
// These counters are reset after a certain amount of time
|
||||
// (OKP period) since the last event.
|
||||
// Number of calls to openSession() where it is recommended
|
||||
// to the app to try keybox provisioning.
|
||||
optional uint32 try_okp_counter = 6;
|
||||
// Number of calls to getProvisionRequest().
|
||||
optional uint32 generate_request_counter = 7;
|
||||
// Number of failed calls to provideProvisionRequest().
|
||||
optional uint32 failed_response_counter = 8;
|
||||
|
||||
// The value of |last_event_time| and |backoff_start_time| are set
|
||||
// using the system's wall-clock in epoch seconds. A value of
|
||||
// zero indicates it's not set.
|
||||
|
||||
// Time of the last engine OKP event (change of the above counters;
|
||||
// the beginning of the current OKP period).
|
||||
// Zero indicates no event has yet occurred.
|
||||
optional int64 last_event_time = 9;
|
||||
// Beginning of an app/origin backoff period.
|
||||
// Zero indicates that engine is not in a backoff state.
|
||||
optional int64 backoff_start_time = 10;
|
||||
// Intended length of “backoff period”. This will be assigned a
|
||||
// random duration initially, then double each time an engine
|
||||
// enters a backoff state. This is base on Google's recommended
|
||||
// exponential backoff rules.
|
||||
// Value of 0 indicates that backoff has not yet occurred.
|
||||
optional int64 backoff_duration = 11;
|
||||
}
|
||||
|
||||
enum OkpDeviceState {
|
||||
// Not yet checked for provisioning state. This should be a
|
||||
// transitory state only. Device which do not need OTA Keybox
|
||||
@@ -175,19 +139,30 @@ message OtaKeyboxProvisioningInfo {
|
||||
// that the device supports OKP. Device may or may not be in the
|
||||
// process of performing provisioning.
|
||||
OKP_NEEDS_PROVISIONING = 1;
|
||||
// Device still needs provisioning, but has reached a condition
|
||||
// where it should backoff from attempting OKP for a period of
|
||||
// time.
|
||||
OKP_FALLBACK_MODE = 2;
|
||||
// The device has successfully provisioned its keybox.
|
||||
OKP_PROVISIONED = 2;
|
||||
OKP_PROVISIONED = 3;
|
||||
}
|
||||
// Device-wide OKP state.
|
||||
optional OkpDeviceState state = 1;
|
||||
// Time when the CDM service first discovers that it needs to
|
||||
// provision the L1 keybox.
|
||||
optional int64 first_checked_time = 2;
|
||||
// Beginning of a backoff period.
|
||||
// Zero indicates that engine is not in a backoff state.
|
||||
optional int64 backoff_start_time = 3;
|
||||
// Intended length of “backoff period”. This will be assigned a
|
||||
// random duration initially, then double each time an engine enters
|
||||
// a backoff state. This is based on Google's recommended exponential
|
||||
// backoff rules.
|
||||
// Value of 0 indicates that backoff has not yet occurred.
|
||||
optional int64 backoff_duration = 4;
|
||||
// System time of when a successful provisioning request has been
|
||||
// received. Only relevant if |state| is OKP_PROVISIONED.
|
||||
optional int64 provisioning_time = 3;
|
||||
// A list of all records for each identifiable engine.
|
||||
repeated OkpEngineInfo engine_infos = 4;
|
||||
optional int64 provisioning_time = 5;
|
||||
}
|
||||
|
||||
message File {
|
||||
|
||||
208
libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp
Normal file
208
libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
#include "okp_fallback_policy.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "cdm_random.h"
|
||||
#include "device_files.h"
|
||||
#include "file_store.h"
|
||||
#include "log.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
namespace okp {
|
||||
using UniqueLock = std::unique_lock<std::mutex>;
|
||||
namespace {
|
||||
constexpr int64_t kErrorTime = -1;
|
||||
|
||||
int64_t GenerateInitialBackoffDuration() {
|
||||
return static_cast<int64_t>(CdmRandom::RandomInRange(
|
||||
kMinInitialBackoffDuration, kMaxInitialBackoffDuration));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::unique_ptr<SystemFallbackPolicy> SystemFallbackPolicy::Create() {
|
||||
std::unique_ptr<FileSystem> fs(new FileSystem());
|
||||
std::unique_ptr<DeviceFiles> device_files(new DeviceFiles(fs.get()));
|
||||
if (!device_files->Init(kSecurityLevelL1)) {
|
||||
LOGE("Failed to initialize device files");
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<SystemFallbackPolicy> policy(new SystemFallbackPolicy());
|
||||
policy->fs_ = std::move(fs);
|
||||
policy->device_files_ = std::move(device_files);
|
||||
policy->TryRestore();
|
||||
return policy;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<SystemFallbackPolicy> SystemFallbackPolicy::CreateForTesting(
|
||||
Clock* clock) {
|
||||
std::unique_ptr<SystemFallbackPolicy> policy(new SystemFallbackPolicy());
|
||||
if (clock != nullptr) {
|
||||
policy->SetClockForTesting(clock);
|
||||
}
|
||||
// Device files are not supported for test instances.
|
||||
return policy;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<SystemFallbackPolicy> SystemFallbackPolicy::CreateForTesting(
|
||||
const SystemFallbackInfo& info, Clock* clock) {
|
||||
std::unique_ptr<SystemFallbackPolicy> policy(new SystemFallbackPolicy());
|
||||
if (clock != nullptr) {
|
||||
policy->SetClockForTesting(clock);
|
||||
}
|
||||
policy->info_ = info;
|
||||
// Device files are not supported for test instances.
|
||||
return policy;
|
||||
}
|
||||
|
||||
SystemFallbackPolicy::SystemFallbackPolicy() : clock_(), clock_ref_(&clock_) {}
|
||||
|
||||
SystemFallbackPolicy::~SystemFallbackPolicy() { StoreInfo(); }
|
||||
|
||||
void SystemFallbackPolicy::TryRestore() {
|
||||
if (!device_files_->RetrieveOkpInfo(&info_)) {
|
||||
info_.Clear();
|
||||
return;
|
||||
}
|
||||
LOGI("Restored OKP info: state = %s",
|
||||
okp::SystemStateToString(info_.state()));
|
||||
// Calling will end fallback mode if the backoff period has elapsed.
|
||||
IsInFallbackMode();
|
||||
}
|
||||
|
||||
void SystemFallbackPolicy::StoreInfo() {
|
||||
if (IsTestMode()) {
|
||||
// Testing instances may not set |device_files_|.
|
||||
LOGV("Test instance, not storing");
|
||||
return;
|
||||
}
|
||||
device_files_->StoreOkpInfo(info_);
|
||||
}
|
||||
|
||||
// Can enter kNeedsProvisioning state from any other state other than
|
||||
// kFallbackMode.
|
||||
void SystemFallbackPolicy::MarkNeedsProvisioning() {
|
||||
UniqueLock lock(mutex_);
|
||||
switch (state()) {
|
||||
case SystemState::kNeedsProvisioning:
|
||||
case SystemState::kFallbackMode:
|
||||
return; // Nothing to do.
|
||||
case SystemState::kProvisioned:
|
||||
case SystemState::kUnknown:
|
||||
break;
|
||||
default:
|
||||
// Should not happen, but if it does continue anyways.
|
||||
LOGW("Unknown state: %d", static_cast<int>(state()));
|
||||
}
|
||||
info_.Clear();
|
||||
info_.SetFirstCheckedTime(GetCurrentTime());
|
||||
info_.SetState(SystemState::kNeedsProvisioning);
|
||||
StoreInfo();
|
||||
}
|
||||
|
||||
// Can only enter kFallbackMode if in state kNeedsProvisioning
|
||||
void SystemFallbackPolicy::TriggerFallback() {
|
||||
UniqueLock lock(mutex_);
|
||||
switch (state()) {
|
||||
case SystemState::kNeedsProvisioning:
|
||||
case SystemState::kUnknown: // Should not happen.
|
||||
break; // OK to fallback
|
||||
case SystemState::kFallbackMode:
|
||||
// Already in fallback mode. Expected if there are multiple
|
||||
// engines are attempting OKP and fail for the same reason.
|
||||
return;
|
||||
case SystemState::kProvisioned: {
|
||||
LOGW("Cannot fallback, already provisioned");
|
||||
return;
|
||||
}
|
||||
default:
|
||||
LOGE("Unexpected state: %d", static_cast<int>(state()));
|
||||
return;
|
||||
}
|
||||
info_.SetState(SystemState::kFallbackMode);
|
||||
const int64_t current_time = GetCurrentTime();
|
||||
if (!info_.HasFirstCheckedTime()) {
|
||||
info_.SetFirstCheckedTime(current_time);
|
||||
}
|
||||
info_.SetBackoffStartTime(GetCurrentTime());
|
||||
if (info_.HasBackoffDuration()) {
|
||||
// Doubling backoff duration for exponential backoff.
|
||||
info_.DoubleBackoffDuration();
|
||||
} else {
|
||||
// Use a random backoff period to avoid server spam across all devices.
|
||||
info_.SetBackoffDuration(GenerateInitialBackoffDuration());
|
||||
}
|
||||
StoreInfo();
|
||||
}
|
||||
|
||||
void SystemFallbackPolicy::MarkProvisioned() {
|
||||
UniqueLock lock(mutex_);
|
||||
if (state() == SystemState::kProvisioned) return;
|
||||
info_.SetState(SystemState::kProvisioned);
|
||||
const int64_t current_time = GetCurrentTime();
|
||||
if (!info_.HasFirstCheckedTime()) {
|
||||
info_.SetFirstCheckedTime(current_time);
|
||||
}
|
||||
info_.SetProvisioningTime(current_time);
|
||||
info_.ClearBackoffStartTime();
|
||||
StoreInfo();
|
||||
}
|
||||
|
||||
bool SystemFallbackPolicy::IsProvisioned() {
|
||||
UniqueLock lock(mutex_);
|
||||
return state() == SystemState::kProvisioned;
|
||||
}
|
||||
|
||||
bool SystemFallbackPolicy::IsInFallbackMode() {
|
||||
UniqueLock lock(mutex_);
|
||||
if (state() != SystemState::kFallbackMode) return false;
|
||||
// Check if fallback period has ended.
|
||||
const int64_t backoff_duration = info_.backoff_duration();
|
||||
const int64_t current_backoff_length = GetSecondsSinceBackoffStart();
|
||||
if (backoff_duration == 0 || current_backoff_length == kErrorTime) {
|
||||
// Possible error condition if device files and/or system clock is
|
||||
// modified.
|
||||
LOGE(
|
||||
"Unexpected backoff state, ending backoff: backoff_duration = "
|
||||
"%" PRId64 ", current_backoff_length = %" PRId64,
|
||||
backoff_duration, current_backoff_length);
|
||||
EndBackoffPeriod();
|
||||
} else if (current_backoff_length < backoff_duration) {
|
||||
// Still in backoff period, resume fallback mode.
|
||||
return true;
|
||||
} else {
|
||||
// Backoff period has ended, may attempt OKP again.
|
||||
LOGD("Backoff period has ended");
|
||||
EndBackoffPeriod();
|
||||
}
|
||||
StoreInfo();
|
||||
return false; // Only stored if previously in fallback and has ended.
|
||||
}
|
||||
|
||||
int64_t SystemFallbackPolicy::GetSecondsSinceBackoffStart() const {
|
||||
if (!info_.HasBackoffStartTime()) return 0;
|
||||
const int64_t backoff_start_time = info_.backoff_start_time();
|
||||
const int64_t current_time = GetCurrentTime();
|
||||
if (current_time < backoff_start_time) {
|
||||
LOGE("Current time is less than start of backoff");
|
||||
return kErrorTime;
|
||||
}
|
||||
return current_time - backoff_start_time;
|
||||
}
|
||||
|
||||
void SystemFallbackPolicy::EndBackoffPeriod() {
|
||||
info_.SetState(SystemState::kNeedsProvisioning);
|
||||
info_.ClearBackoffStartTime();
|
||||
}
|
||||
|
||||
bool SystemFallbackPolicy::IsTestMode() const {
|
||||
return !static_cast<bool>(device_files_);
|
||||
}
|
||||
} // namespace okp
|
||||
} // namespace wvcdm
|
||||
32
libwvdrmengine/cdm/core/src/okp_info.cpp
Normal file
32
libwvdrmengine/cdm/core/src/okp_info.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
#include "okp_info.h"
|
||||
|
||||
namespace wvcdm {
|
||||
namespace okp {
|
||||
const char* SystemStateToString(SystemState state) {
|
||||
switch (state) {
|
||||
case SystemState::kNeedsProvisioning:
|
||||
return "NeedsProvisioning";
|
||||
case SystemState::kFallbackMode:
|
||||
return "FallbackMode";
|
||||
case SystemState::kProvisioned:
|
||||
return "Provisioned";
|
||||
case SystemState::kUnknown:
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
bool SystemFallbackInfo::operator==(const SystemFallbackInfo& other) const {
|
||||
if (this == &other) return true;
|
||||
if (state_ != other.state_) return false;
|
||||
if (first_checked_time_ != other.first_checked_time_) return false;
|
||||
if (backoff_start_time_ != other.backoff_start_time_) return false;
|
||||
if (backoff_duration_ != other.backoff_duration_) return false;
|
||||
if (provisioning_time_ != other.provisioning_time_) return false;
|
||||
return true;
|
||||
}
|
||||
} // namespace okp
|
||||
} // namespace wvcdm
|
||||
@@ -3,81 +3,272 @@
|
||||
// Agreement.
|
||||
#include "ota_keybox_provisioner.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "certificate_provisioning.h"
|
||||
#include "crypto_session.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "log.h"
|
||||
#include "okp_fallback_policy.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvcdm {
|
||||
using UniqueLock = std::unique_lock<std::mutex>;
|
||||
using video_widevine::ProvisioningRequest;
|
||||
using video_widevine::ProvisioningResponse;
|
||||
using video_widevine::SignedProvisioningMessage;
|
||||
using OtaRequest = ProvisioningRequest::AndroidAttestationOtaKeyboxRequest;
|
||||
using OtaResponse = ProvisioningResponse::AndroidAttestationOtaKeyboxResponse;
|
||||
namespace {
|
||||
// Indicates not to use the test keybox for OTA provisioning.
|
||||
constexpr bool kProductionKeybox = false;
|
||||
const CdmAppParameterMap kEmptyAppParameters;
|
||||
const std::string kEmptyString;
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::unique_ptr<OtaKeyboxProvisioner> OtaKeyboxProvisioner::Create() {
|
||||
return std::unique_ptr<OtaKeyboxProvisioner>(new OtaKeyboxProvisioner());
|
||||
std::unique_ptr<OtaKeyboxProvisioner> OtaKeyboxProvisioner::Create(
|
||||
metrics::CryptoMetrics* crypto_metrics) {
|
||||
if (crypto_metrics == nullptr) {
|
||||
LOGE("Input |crypto_metrics| is null");
|
||||
return nullptr;
|
||||
}
|
||||
// Get system fallback policy.
|
||||
okp::SystemFallbackPolicy* fallback_policy =
|
||||
CryptoSession::GetOkpFallbackPolicy();
|
||||
if (fallback_policy == nullptr) {
|
||||
LOGE("No system fallback policy");
|
||||
return nullptr;
|
||||
}
|
||||
// Setup crypto session.
|
||||
std::unique_ptr<CryptoSession> crypto_session(
|
||||
CryptoSession::MakeCryptoSession(crypto_metrics));
|
||||
crypto_session->Open(kLevelDefault);
|
||||
if (!crypto_session->IsOpen()) {
|
||||
LOGE("Failed to open crypto session for OKP provisioner");
|
||||
return nullptr;
|
||||
}
|
||||
const CdmSecurityLevel security_level = crypto_session->GetSecurityLevel();
|
||||
if (security_level != kSecurityLevelL1) {
|
||||
LOGE("Failed to open L1 crypto session: security_level = %d",
|
||||
static_cast<int>(security_level));
|
||||
crypto_session.reset();
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<OtaKeyboxProvisioner> engine_provisioner(
|
||||
new OtaKeyboxProvisioner(std::move(crypto_session), fallback_policy));
|
||||
if (!engine_provisioner->Init()) {
|
||||
LOGE("Failed to initialize OKP provisioner");
|
||||
return nullptr;
|
||||
}
|
||||
return engine_provisioner;
|
||||
}
|
||||
|
||||
OtaKeyboxProvisioner::OtaKeyboxProvisioner() : mutex_() {}
|
||||
|
||||
OtaKeyboxProvisioner::~OtaKeyboxProvisioner() {}
|
||||
|
||||
CdmResponseType OtaKeyboxProvisioner::GenerateProvisioningRequest(
|
||||
CryptoSession* crypto_session, std::string* request) {
|
||||
if (crypto_session == nullptr) {
|
||||
LOGE("Input |crypto_session| is null");
|
||||
return PARAMETER_NULL;
|
||||
// static
|
||||
std::unique_ptr<OtaKeyboxProvisioner> OtaKeyboxProvisioner::CreateForTesting(
|
||||
std::unique_ptr<CryptoSession>&& crypto_session,
|
||||
okp::SystemFallbackPolicy* fallback_policy) {
|
||||
if (!crypto_session) {
|
||||
LOGE("Input |crypto_metrics| is null");
|
||||
return nullptr;
|
||||
}
|
||||
if (fallback_policy == nullptr) {
|
||||
LOGE("Input |fallback_policy| is null");
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<OtaKeyboxProvisioner> engine_provisioner(
|
||||
new OtaKeyboxProvisioner(std::move(crypto_session), fallback_policy));
|
||||
if (!engine_provisioner->Init()) {
|
||||
LOGE("Failed to initialize OKP provisioner");
|
||||
return nullptr;
|
||||
}
|
||||
return engine_provisioner;
|
||||
}
|
||||
|
||||
OtaKeyboxProvisioner::OtaKeyboxProvisioner(
|
||||
std::unique_ptr<CryptoSession>&& crypto_session,
|
||||
okp::SystemFallbackPolicy* fallback_policy)
|
||||
: crypto_session_(std::move(crypto_session)),
|
||||
fallback_policy_(fallback_policy) {
|
||||
assert(static_cast<bool>(crypto_session_));
|
||||
assert(fallback_policy != nullptr);
|
||||
}
|
||||
|
||||
OtaKeyboxProvisioner::~OtaKeyboxProvisioner() {
|
||||
crypto_session_.reset();
|
||||
fallback_policy_ = nullptr;
|
||||
}
|
||||
|
||||
bool OtaKeyboxProvisioner::Init() {
|
||||
const CdmResponseType result =
|
||||
client_id_.InitForOtaKeyboxProvisioning(crypto_session_.get());
|
||||
if (result != NO_ERROR) {
|
||||
LOGE("Failed to initialize OKP client ID");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OtaKeyboxProvisioner::IsProvisioned() const {
|
||||
return fallback_policy_->IsProvisioned();
|
||||
}
|
||||
|
||||
bool OtaKeyboxProvisioner::IsInFallbackMode() const {
|
||||
return fallback_policy_->IsInFallbackMode();
|
||||
}
|
||||
|
||||
CdmResponseType OtaKeyboxProvisioner::GetProvisioningRequest(
|
||||
std::string* request, std::string* default_url) {
|
||||
if (request == nullptr) {
|
||||
LOGE("Output |request| is null");
|
||||
return PARAMETER_NULL;
|
||||
}
|
||||
UniqueLock lock(mutex_);
|
||||
// Do not generate new requests if already provisioned.
|
||||
if (default_url == nullptr) {
|
||||
LOGE("Output |default_url| is null");
|
||||
return PARAMETER_NULL;
|
||||
}
|
||||
if (IsProvisioned()) {
|
||||
LOGW("Already provisioned");
|
||||
// TODO(sigquit): Use a response code that will indicate to the
|
||||
// caller that the system is already provisioned.
|
||||
CleanUp();
|
||||
return OKP_ALREADY_PROVISIONED;
|
||||
}
|
||||
if (!crypto_session_) {
|
||||
LOGE("Crypto session has been released, OKP unavailable");
|
||||
// Caller should not reuse provisioner after failure.
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
const CdmResponseType result =
|
||||
crypto_session->PrepareOtaProvisioningRequest(kProductionKeybox, request);
|
||||
if (result != NO_ERROR) {
|
||||
// Step 1: Generate raw request from OEMCrypto.
|
||||
std::string ota_request_data;
|
||||
CdmResponseType result = crypto_session_->PrepareOtaProvisioningRequest(
|
||||
kProductionKeybox, &ota_request_data);
|
||||
if (result == NOT_IMPLEMENTED_ERROR) {
|
||||
LOGW("OKP is not supported by OEMCrypto");
|
||||
fallback_policy_->TriggerFallback();
|
||||
CleanUp();
|
||||
return result;
|
||||
} else if (result != NO_ERROR) {
|
||||
LOGE("Failed to generate OKP request: status = %d",
|
||||
static_cast<int>(result));
|
||||
return result;
|
||||
}
|
||||
LOGV("OTA request generated: request = %s", b2a_hex(*request).c_str());
|
||||
request_count_++;
|
||||
// Step 2: Wrap in ProvisioningRequest.
|
||||
ProvisioningRequest prov_request;
|
||||
auto* client_id = prov_request.mutable_client_id();
|
||||
result = client_id_.Prepare(kEmptyAppParameters, kEmptyString, client_id);
|
||||
if (result != NO_ERROR) {
|
||||
LOGW("Failed to prepare client ID, continuing without: result = %d",
|
||||
static_cast<int>(result));
|
||||
client_id->Clear();
|
||||
}
|
||||
OtaRequest* ota_request = prov_request.mutable_android_ota_keybox_request();
|
||||
ota_request->set_ota_request(ota_request_data);
|
||||
|
||||
// Step 3: Wrap in SignedProvisioningMessage.
|
||||
SignedProvisioningMessage signed_request;
|
||||
std::string message;
|
||||
prov_request.SerializeToString(&message);
|
||||
signed_request.set_message(message);
|
||||
signed_request.set_provisioning_type(
|
||||
SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA);
|
||||
signed_request.SerializeToString(request);
|
||||
|
||||
if (!wvcdm::Properties::provisioning_messages_are_binary()) {
|
||||
*request = Base64SafeEncodeNoPad(
|
||||
std::vector<uint8_t>(request->begin(), request->end()));
|
||||
}
|
||||
|
||||
request_generated_ = true;
|
||||
CertificateProvisioning::GetProvisioningServerUrl(default_url);
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType OtaKeyboxProvisioner::HandleProvisioningResponse(
|
||||
CryptoSession* crypto_session, const std::string& response) {
|
||||
if (crypto_session == nullptr) {
|
||||
LOGE("Input |crypto_session| is null");
|
||||
return PARAMETER_NULL;
|
||||
}
|
||||
const std::string& response) {
|
||||
if (response.empty()) {
|
||||
LOGE("OTA provisioning response is empty");
|
||||
LOGE("Signed provisioning message is empty");
|
||||
return EMPTY_PROVISIONING_RESPONSE;
|
||||
}
|
||||
UniqueLock lock(mutex_);
|
||||
if (IsProvisioned()) {
|
||||
// Response already received, silently dropping.
|
||||
response_count_++;
|
||||
LOGD("Already provisioned");
|
||||
response_received_ = true;
|
||||
CleanUp();
|
||||
return NO_ERROR;
|
||||
}
|
||||
const CdmResponseType result =
|
||||
crypto_session->LoadOtaProvisioning(kProductionKeybox, response);
|
||||
if (result != NO_ERROR) {
|
||||
return result;
|
||||
if (!request_generated_) {
|
||||
LOGE("Received response without generating request");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
if (!crypto_session_) {
|
||||
LOGE("Crypto session has been released, OKP unavailable");
|
||||
// Caller should not reuse provisioner after failure.
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
std::string decoded_response;
|
||||
if (!wvcdm::Properties::provisioning_messages_are_binary()) {
|
||||
if (!CertificateProvisioning::ExtractAndDecodeSignedMessage(
|
||||
response, &decoded_response)) {
|
||||
LOGE("Failed to extract OKP provisioning response");
|
||||
return PARSE_OKP_RESPONSE_ERROR;
|
||||
}
|
||||
} else {
|
||||
decoded_response = response;
|
||||
}
|
||||
// Step 1: Unwrap from SignedProvisioningMessage;
|
||||
SignedProvisioningMessage signed_response;
|
||||
if (!signed_response.ParseFromString(decoded_response)) {
|
||||
LOGE("Failed to parse SignedProvisioningMessage");
|
||||
return PARSE_OKP_RESPONSE_ERROR;
|
||||
}
|
||||
if (!signed_response.has_message()) {
|
||||
LOGE("Signed response is missing message");
|
||||
return PARSE_OKP_RESPONSE_ERROR;
|
||||
}
|
||||
if (signed_response.provisioning_type() !=
|
||||
SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA) {
|
||||
LOGE("Unexpected protocol type/version: protocol_type = %d",
|
||||
static_cast<int>(signed_response.provisioning_type()));
|
||||
return PARSE_OKP_RESPONSE_ERROR;
|
||||
}
|
||||
// Step 2: Unwrap from ProvisioningResponse.
|
||||
ProvisioningResponse prov_response;
|
||||
if (!prov_response.ParseFromString(signed_response.message())) {
|
||||
LOGE("Failed to parse ProvisioningResponse");
|
||||
return PARSE_OKP_RESPONSE_ERROR;
|
||||
}
|
||||
if (!prov_response.has_android_ota_keybox_response()) {
|
||||
LOGE("Missing OTA keybox response message");
|
||||
return PARSE_OKP_RESPONSE_ERROR;
|
||||
}
|
||||
const OtaResponse& ota_response = prov_response.android_ota_keybox_response();
|
||||
if (!ota_response.has_ota_response()) {
|
||||
LOGE("Missing OTA keybox response data");
|
||||
return PARSE_OKP_RESPONSE_ERROR;
|
||||
}
|
||||
// Step 3: Load response.
|
||||
const std::string ota_response_data = ota_response.ota_response();
|
||||
if (ota_response_data.empty()) {
|
||||
LOGE("Raw OTA response is empty");
|
||||
return PARSE_OKP_RESPONSE_ERROR;
|
||||
}
|
||||
const CdmResponseType result = crypto_session_->LoadOtaProvisioning(
|
||||
kProductionKeybox, ota_response_data);
|
||||
if (result == NO_ERROR) {
|
||||
LOGV("OTA response successfully processed: ota_response_data = %s",
|
||||
b2a_hex(ota_response_data).c_str());
|
||||
fallback_policy_->MarkProvisioned();
|
||||
response_received_ = true;
|
||||
} else {
|
||||
fallback_policy_->TriggerFallback();
|
||||
}
|
||||
CleanUp();
|
||||
return result;
|
||||
}
|
||||
|
||||
void OtaKeyboxProvisioner::CleanUp() {
|
||||
if (crypto_session_) {
|
||||
crypto_session_.reset();
|
||||
}
|
||||
LOGD("OTA response successfully processed: response = %s",
|
||||
b2a_hex(response).c_str());
|
||||
is_provisioned_ = true;
|
||||
response_count_ = 1;
|
||||
return NO_ERROR;
|
||||
}
|
||||
} // namespace wvcdm
|
||||
|
||||
Reference in New Issue
Block a user