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:
@@ -72,6 +72,28 @@ class CertificateProvisioning {
|
||||
// |default_url| by GetProvisioningRequest().
|
||||
static void GetProvisioningServerUrl(std::string* default_url);
|
||||
|
||||
enum State {
|
||||
// Freshly created, not yet initialized.
|
||||
kUninitialized,
|
||||
// A successful call to Init() has been made.
|
||||
kInitialized,
|
||||
// Has generated a DRM request; apps are allowed generate
|
||||
// another one even if a response has not been received.
|
||||
kDrmRequestSent,
|
||||
// Has received (and successfully loaded) a DRM response.
|
||||
kDrmResponseReceived,
|
||||
// Has generated an OEM (Prov 4.0) request; apps are allowed
|
||||
// generate another one even if a response has not been
|
||||
// received.
|
||||
kOemRequestSent,
|
||||
// Has received (and successfully loaded) an OEM response.
|
||||
kOemResponseReceived,
|
||||
};
|
||||
static const char* StateToString(State state);
|
||||
|
||||
// State setter for testing only.
|
||||
void SetStateForTesting(State state) { state_ = state; }
|
||||
|
||||
private:
|
||||
#if defined(UNIT_TEST)
|
||||
friend class CertificateProvisioningTest;
|
||||
@@ -122,6 +144,9 @@ class CertificateProvisioning {
|
||||
CdmResponseType CloseSessionOnError(CdmResponseType status);
|
||||
void CloseSession();
|
||||
|
||||
// Tracks the state of CertificateProvisioning.
|
||||
State state_ = kUninitialized;
|
||||
|
||||
std::unique_ptr<CryptoSession> crypto_session_;
|
||||
CdmCertificateType cert_type_;
|
||||
std::unique_ptr<ServiceCertificate> service_certificate_;
|
||||
|
||||
@@ -465,6 +465,7 @@ enum CdmResponseEnum : int32_t {
|
||||
GET_DEVICE_INFORMATION_ERROR = 398,
|
||||
GET_DEVICE_SIGNED_CSR_PAYLOAD_ERROR = 399,
|
||||
GET_TOKEN_FROM_EMBEDDED_CERT_ERROR = 400,
|
||||
PROVISIONING_UNEXPECTED_RESPONSE_ERROR = 402,
|
||||
// Don't forget to add new values to
|
||||
// * core/src/wv_cdm_types.cpp
|
||||
// * android/include/mapErrors-inl.h
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -413,6 +413,9 @@ TEST_P(CertificateProvisioningTest, ProvisioningRequestFailsEmptySignature) {
|
||||
TEST_P(CertificateProvisioningTest,
|
||||
ProvisioningResponseFailsWithEmptyResponse) {
|
||||
certificate_provisioning_->Init("");
|
||||
// Must set state if not generating request.
|
||||
certificate_provisioning_->SetStateForTesting(
|
||||
CertificateProvisioning::kDrmRequestSent);
|
||||
|
||||
MockFileSystem file_system;
|
||||
std::string certificate;
|
||||
@@ -425,6 +428,9 @@ TEST_P(CertificateProvisioningTest,
|
||||
TEST_P(CertificateProvisioningTest,
|
||||
ProvisioningResponseFailsIfDeviceIsRevoked) {
|
||||
certificate_provisioning_->Init("");
|
||||
// Must set state if not generating request.
|
||||
certificate_provisioning_->SetStateForTesting(
|
||||
CertificateProvisioning::kDrmRequestSent);
|
||||
|
||||
MockFileSystem file_system;
|
||||
std::string response_certificate;
|
||||
@@ -445,6 +451,10 @@ TEST_P(CertificateProvisioningTest,
|
||||
|
||||
TEST_P(CertificateProvisioningTest, ProvisioningResponseSuccess) {
|
||||
certificate_provisioning_->Init("");
|
||||
// Must set state if not generating request.
|
||||
certificate_provisioning_->SetStateForTesting(
|
||||
CertificateProvisioning::kDrmRequestSent);
|
||||
|
||||
std::string expected_certificate;
|
||||
std::string response;
|
||||
ASSERT_TRUE(MakeSignedDrmCertificate(kFakePublicKey, kSerialNumber, kSystemId,
|
||||
|
||||
Reference in New Issue
Block a user