Wrapped OKP info into several classes.

[ Cherry pick of http://ag/15836995 ]
[ 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
Change-Id: Ia572a66a7b73479355758aa3d0c682691eaca0fc
This commit is contained in:
Rahul Frias
2021-09-15 05:00:19 -07:00
committed by Alex Dale
parent 3acc64a478
commit 6afcbab5cf
30 changed files with 2010 additions and 330 deletions

View File

@@ -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