Added state to CertificateProvisioning.

[ Cherry-pick of v19 http://go/wvgerrit/219310 ]
[ Merge of http://go/wvgerrit/219453 ]

To enable the CDM to determine between OEM vs DRM responses,
a state variable was needed in CertificateProvisioning.
Previously, the presence/absence of the OEM certificate in the
file system was used; however, if two apps (or single app with
multiple origins) attempts provisioning simultaneously, the
later response would trigger unexpected failures.

The main functional changes this provides is that a more informative
error will be returned to the app if they provide a provisioning
response without ever creating a provisioning request; and that
if multiple clients attempted first-stage provisioning simultaneously,
fewer errors will occur.

Bug: 391469176
Test: run_prov40_tests
Change-Id: I51a118ce73aa809bad6ecee640139a92d8518575
This commit is contained in:
Alex Dale
2025-04-21 16:43:37 -07:00
parent 961faf0729
commit b0fa978058
6 changed files with 128 additions and 21 deletions

View File

@@ -98,6 +98,25 @@ using video_widevine::PublicKeyToCertify;
using video_widevine::SignedDrmCertificate;
using video_widevine::SignedProvisioningMessage;
// static
const char* CertificateProvisioning::StateToString(State state) {
switch (state) {
case kUninitialized:
return "Uninitialized";
case kInitialized:
return "Initialized";
case kDrmRequestSent:
return "DrmRequestSent";
case kDrmResponseReceived:
return "DrmResponseReceived";
case kOemRequestSent:
return "OemRequestSent";
case kOemResponseReceived:
return "OemResponseReceived";
}
return "<unknown>";
}
// static
void CertificateProvisioning::GetProvisioningServerUrl(
std::string* default_url) {
@@ -114,7 +133,11 @@ CdmResponseType CertificateProvisioning::Init(
service_certificate.empty()
? wvutil::a2bs_hex(kCpProductionServiceCertificate)
: service_certificate;
return service_certificate_->Init(certificate);
const CdmResponseType result = service_certificate_->Init(certificate);
if (result == NO_ERROR) {
state_ = kInitialized;
}
return result;
}
// Fill in the appropriate SPOID (Stable Per-Origin IDentifier) option.
@@ -206,11 +229,18 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
default_url->assign(kProvisioningServerUrl);
if (state_ != kInitialized) {
LOGD("Overriding old request: state = %s", StateToString(state_));
// Once the previous session is closed, there is no way to complete
// an in-flight request.
state_ = kInitialized;
}
CloseSession();
CdmResponseType status = crypto_session_->Open(requested_security_level);
if (NO_ERROR != status) {
LOGE("Failed to create a crypto session: status = %d",
static_cast<int>(status));
LOGE("Failed to create a crypto session: status = %s",
status.ToString().c_str());
return status;
}
@@ -299,6 +329,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
} else {
*request = std::move(serialized_request);
}
state_ = kDrmRequestSent;
return CdmResponseType(NO_ERROR);
}
@@ -341,6 +372,7 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
}
}
}
const bool is_oem_prov_request = stored_oem_cert.empty();
// Retrieve the Spoid, but put it to the client identification instead, so it
// is encrypted.
@@ -363,7 +395,7 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
provisioning_request.clear_stable_id();
}
if (stored_oem_cert.empty()) {
if (is_oem_prov_request) {
// This is the first stage provisioning.
default_url->assign(std::string(kProvisioningServerUrl) +
kProv40FirstStageServerUrlSuffix);
@@ -377,8 +409,8 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
// Since |stored_oem_cert| is empty, the client identification token will be
// retrieved from OEMCrypto, which is the BCC in this case.
status = FillEncryptedClientId(stored_oem_cert, provisioning_request,
wv_service_cert);
status = FillEncryptedClientId(/* client_token = */ std::string(),
provisioning_request, wv_service_cert);
if (status != NO_ERROR) return status;
} else {
// This is the second stage provisioning.
@@ -490,6 +522,8 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
*request = std::move(serialized_request);
}
request_ = std::move(serialized_message);
state_ = is_oem_prov_request ? kOemRequestSent : kDrmRequestSent;
return CdmResponseType(NO_ERROR);
}
@@ -552,6 +586,18 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
return CdmResponseType(PROVISIONING_4_RESPONSE_HAS_ERROR_STATUS);
}
}
if (state_ == kOemResponseReceived || state_ == kDrmResponseReceived) {
// A response has already been received (successfully), this
// response can be silently dropped.
LOGW("Response already received: state = %s", StateToString(state_));
return CdmResponseType(NO_ERROR);
}
if (state_ != kOemRequestSent && state_ != kDrmRequestSent) {
LOGE("Not expecting a response: state = %s", StateToString(state_));
return CdmResponseType(PROVISIONING_UNEXPECTED_RESPONSE_ERROR);
}
LOGD("Handling response: state = %s", StateToString(state_));
const bool is_oem_prov_response = (state_ == kOemRequestSent);
const std::string& device_certificate =
provisioning_response.device_certificate();
@@ -593,6 +639,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
*cert = device_certificate;
*wrapped_key = cast_cert_private_key.key();
state_ = is_oem_prov_response ? kOemResponseReceived : kDrmResponseReceived;
return CdmResponseType(NO_ERROR);
}
@@ -607,28 +654,36 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
// Check the stage of the provisioning by checking if an OEM cert is already
// stored in the file system.
if (!global_file_handle.HasOemCertificate()) {
if (is_oem_prov_response) {
if (global_file_handle.HasOemCertificate()) {
// Possible that concurrent apps were generated provisioning
// requests, and this one arrived after an other one.
LOGI("CDM has already received an OEM certificate");
state_ = kOemResponseReceived;
return CdmResponseType(NO_ERROR);
}
// No OEM cert already stored => the response is expected to be an OEM cert.
if (!global_file_handle.StoreOemCertificate(device_certificate,
private_key)) {
LOGE("Failed to store provisioning 4 OEM certificate");
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_OEM_CERTIFICATE);
}
} else {
// The response is assumed to be an DRM cert.
DeviceFiles per_origin_file_handle(file_system);
if (!per_origin_file_handle.Init(security_level)) {
LOGE("Failed to initialize per-origin DeviceFiles");
return CdmResponseType(
PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_3);
}
if (!per_origin_file_handle.StoreCertificate(device_certificate,
private_key)) {
LOGE("Failed to store provisioning 4 DRM certificate");
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_DRM_CERTIFICATE);
}
state_ = kOemResponseReceived;
return CdmResponseType(NO_ERROR);
}
// The response is assumed to be a DRM cert.
DeviceFiles per_origin_file_handle(file_system);
if (!per_origin_file_handle.Init(security_level)) {
LOGE("Failed to initialize per-origin DeviceFiles");
return CdmResponseType(PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_3);
}
if (!per_origin_file_handle.StoreCertificate(device_certificate,
private_key)) {
LOGE("Failed to store provisioning 4 DRM certificate");
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_DRM_CERTIFICATE);
}
state_ = kDrmResponseReceived;
return CdmResponseType(NO_ERROR);
}
@@ -678,6 +733,15 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
wrapped_key);
}
if (state_ == kDrmResponseReceived) {
LOGD("Response already received");
return CdmResponseType(NO_ERROR);
}
if (state_ != kDrmRequestSent) {
LOGE("Not expecting a response: state = %s", StateToString(state_));
return CdmResponseType(PROVISIONING_UNEXPECTED_RESPONSE_ERROR);
}
bool error = false;
if (!signed_response.has_signature()) {
LOGE("Signed response does not have signature");
@@ -753,6 +817,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
if (cert_type_ == kCertificateX509) {
*cert = device_cert_data;
*wrapped_key = private_key.key();
state_ = kDrmResponseReceived;
return CdmResponseType(NO_ERROR);
}
@@ -799,6 +864,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_8);
}
state_ = kDrmResponseReceived;
return CdmResponseType(NO_ERROR);
}

View File

@@ -879,6 +879,8 @@ const char* CdmResponseEnumToString(CdmResponseEnum cdm_response_enum) {
return "SESSION_NOT_FOUND_GENERIC_CRYPTO";
case SESSION_NOT_FOUND_24:
return "SESSION_NOT_FOUND_24";
case PROVISIONING_UNEXPECTED_RESPONSE_ERROR:
return "PROVISIONING_UNEXPECTED_RESPONSE_ERROR";
}
return UnknownValueRep(cdm_response_enum);
}