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().
|
// |default_url| by GetProvisioningRequest().
|
||||||
static void GetProvisioningServerUrl(std::string* default_url);
|
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:
|
private:
|
||||||
#if defined(UNIT_TEST)
|
#if defined(UNIT_TEST)
|
||||||
friend class CertificateProvisioningTest;
|
friend class CertificateProvisioningTest;
|
||||||
@@ -122,6 +144,9 @@ class CertificateProvisioning {
|
|||||||
CdmResponseType CloseSessionOnError(CdmResponseType status);
|
CdmResponseType CloseSessionOnError(CdmResponseType status);
|
||||||
void CloseSession();
|
void CloseSession();
|
||||||
|
|
||||||
|
// Tracks the state of CertificateProvisioning.
|
||||||
|
State state_ = kUninitialized;
|
||||||
|
|
||||||
std::unique_ptr<CryptoSession> crypto_session_;
|
std::unique_ptr<CryptoSession> crypto_session_;
|
||||||
CdmCertificateType cert_type_;
|
CdmCertificateType cert_type_;
|
||||||
std::unique_ptr<ServiceCertificate> service_certificate_;
|
std::unique_ptr<ServiceCertificate> service_certificate_;
|
||||||
|
|||||||
@@ -465,6 +465,7 @@ enum CdmResponseEnum : int32_t {
|
|||||||
GET_DEVICE_INFORMATION_ERROR = 398,
|
GET_DEVICE_INFORMATION_ERROR = 398,
|
||||||
GET_DEVICE_SIGNED_CSR_PAYLOAD_ERROR = 399,
|
GET_DEVICE_SIGNED_CSR_PAYLOAD_ERROR = 399,
|
||||||
GET_TOKEN_FROM_EMBEDDED_CERT_ERROR = 400,
|
GET_TOKEN_FROM_EMBEDDED_CERT_ERROR = 400,
|
||||||
|
PROVISIONING_UNEXPECTED_RESPONSE_ERROR = 402,
|
||||||
// Don't forget to add new values to
|
// Don't forget to add new values to
|
||||||
// * core/src/wv_cdm_types.cpp
|
// * core/src/wv_cdm_types.cpp
|
||||||
// * android/include/mapErrors-inl.h
|
// * android/include/mapErrors-inl.h
|
||||||
|
|||||||
@@ -98,6 +98,25 @@ using video_widevine::PublicKeyToCertify;
|
|||||||
using video_widevine::SignedDrmCertificate;
|
using video_widevine::SignedDrmCertificate;
|
||||||
using video_widevine::SignedProvisioningMessage;
|
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
|
// static
|
||||||
void CertificateProvisioning::GetProvisioningServerUrl(
|
void CertificateProvisioning::GetProvisioningServerUrl(
|
||||||
std::string* default_url) {
|
std::string* default_url) {
|
||||||
@@ -114,7 +133,11 @@ CdmResponseType CertificateProvisioning::Init(
|
|||||||
service_certificate.empty()
|
service_certificate.empty()
|
||||||
? wvutil::a2bs_hex(kCpProductionServiceCertificate)
|
? wvutil::a2bs_hex(kCpProductionServiceCertificate)
|
||||||
: service_certificate;
|
: 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.
|
// Fill in the appropriate SPOID (Stable Per-Origin IDentifier) option.
|
||||||
@@ -206,11 +229,18 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
|
|||||||
|
|
||||||
default_url->assign(kProvisioningServerUrl);
|
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();
|
CloseSession();
|
||||||
CdmResponseType status = crypto_session_->Open(requested_security_level);
|
CdmResponseType status = crypto_session_->Open(requested_security_level);
|
||||||
if (NO_ERROR != status) {
|
if (NO_ERROR != status) {
|
||||||
LOGE("Failed to create a crypto session: status = %d",
|
LOGE("Failed to create a crypto session: status = %s",
|
||||||
static_cast<int>(status));
|
status.ToString().c_str());
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +329,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
|
|||||||
} else {
|
} else {
|
||||||
*request = std::move(serialized_request);
|
*request = std::move(serialized_request);
|
||||||
}
|
}
|
||||||
|
state_ = kDrmRequestSent;
|
||||||
return CdmResponseType(NO_ERROR);
|
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
|
// Retrieve the Spoid, but put it to the client identification instead, so it
|
||||||
// is encrypted.
|
// is encrypted.
|
||||||
@@ -363,7 +395,7 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
|||||||
provisioning_request.clear_stable_id();
|
provisioning_request.clear_stable_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stored_oem_cert.empty()) {
|
if (is_oem_prov_request) {
|
||||||
// This is the first stage provisioning.
|
// This is the first stage provisioning.
|
||||||
default_url->assign(std::string(kProvisioningServerUrl) +
|
default_url->assign(std::string(kProvisioningServerUrl) +
|
||||||
kProv40FirstStageServerUrlSuffix);
|
kProv40FirstStageServerUrlSuffix);
|
||||||
@@ -377,8 +409,8 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
|||||||
|
|
||||||
// Since |stored_oem_cert| is empty, the client identification token will be
|
// Since |stored_oem_cert| is empty, the client identification token will be
|
||||||
// retrieved from OEMCrypto, which is the BCC in this case.
|
// retrieved from OEMCrypto, which is the BCC in this case.
|
||||||
status = FillEncryptedClientId(stored_oem_cert, provisioning_request,
|
status = FillEncryptedClientId(/* client_token = */ std::string(),
|
||||||
wv_service_cert);
|
provisioning_request, wv_service_cert);
|
||||||
if (status != NO_ERROR) return status;
|
if (status != NO_ERROR) return status;
|
||||||
} else {
|
} else {
|
||||||
// This is the second stage provisioning.
|
// This is the second stage provisioning.
|
||||||
@@ -490,6 +522,8 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
|||||||
*request = std::move(serialized_request);
|
*request = std::move(serialized_request);
|
||||||
}
|
}
|
||||||
request_ = std::move(serialized_message);
|
request_ = std::move(serialized_message);
|
||||||
|
|
||||||
|
state_ = is_oem_prov_request ? kOemRequestSent : kDrmRequestSent;
|
||||||
return CdmResponseType(NO_ERROR);
|
return CdmResponseType(NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,6 +586,18 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
|
|||||||
return CdmResponseType(PROVISIONING_4_RESPONSE_HAS_ERROR_STATUS);
|
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 =
|
const std::string& device_certificate =
|
||||||
provisioning_response.device_certificate();
|
provisioning_response.device_certificate();
|
||||||
@@ -593,6 +639,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
|
|||||||
|
|
||||||
*cert = device_certificate;
|
*cert = device_certificate;
|
||||||
*wrapped_key = cast_cert_private_key.key();
|
*wrapped_key = cast_cert_private_key.key();
|
||||||
|
state_ = is_oem_prov_response ? kOemResponseReceived : kDrmResponseReceived;
|
||||||
return CdmResponseType(NO_ERROR);
|
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
|
// Check the stage of the provisioning by checking if an OEM cert is already
|
||||||
// stored in the file system.
|
// 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.
|
// No OEM cert already stored => the response is expected to be an OEM cert.
|
||||||
if (!global_file_handle.StoreOemCertificate(device_certificate,
|
if (!global_file_handle.StoreOemCertificate(device_certificate,
|
||||||
private_key)) {
|
private_key)) {
|
||||||
LOGE("Failed to store provisioning 4 OEM certificate");
|
LOGE("Failed to store provisioning 4 OEM certificate");
|
||||||
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_OEM_CERTIFICATE);
|
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_OEM_CERTIFICATE);
|
||||||
}
|
}
|
||||||
} else {
|
state_ = kOemResponseReceived;
|
||||||
// The response is assumed to be an DRM cert.
|
return CdmResponseType(NO_ERROR);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 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);
|
return CdmResponseType(NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,6 +733,15 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
|||||||
wrapped_key);
|
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;
|
bool error = false;
|
||||||
if (!signed_response.has_signature()) {
|
if (!signed_response.has_signature()) {
|
||||||
LOGE("Signed response does not have signature");
|
LOGE("Signed response does not have signature");
|
||||||
@@ -753,6 +817,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
|||||||
if (cert_type_ == kCertificateX509) {
|
if (cert_type_ == kCertificateX509) {
|
||||||
*cert = device_cert_data;
|
*cert = device_cert_data;
|
||||||
*wrapped_key = private_key.key();
|
*wrapped_key = private_key.key();
|
||||||
|
state_ = kDrmResponseReceived;
|
||||||
return CdmResponseType(NO_ERROR);
|
return CdmResponseType(NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -799,6 +864,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
|||||||
return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_8);
|
return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state_ = kDrmResponseReceived;
|
||||||
return CdmResponseType(NO_ERROR);
|
return CdmResponseType(NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -879,6 +879,8 @@ const char* CdmResponseEnumToString(CdmResponseEnum cdm_response_enum) {
|
|||||||
return "SESSION_NOT_FOUND_GENERIC_CRYPTO";
|
return "SESSION_NOT_FOUND_GENERIC_CRYPTO";
|
||||||
case SESSION_NOT_FOUND_24:
|
case SESSION_NOT_FOUND_24:
|
||||||
return "SESSION_NOT_FOUND_24";
|
return "SESSION_NOT_FOUND_24";
|
||||||
|
case PROVISIONING_UNEXPECTED_RESPONSE_ERROR:
|
||||||
|
return "PROVISIONING_UNEXPECTED_RESPONSE_ERROR";
|
||||||
}
|
}
|
||||||
return UnknownValueRep(cdm_response_enum);
|
return UnknownValueRep(cdm_response_enum);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,6 +413,9 @@ TEST_P(CertificateProvisioningTest, ProvisioningRequestFailsEmptySignature) {
|
|||||||
TEST_P(CertificateProvisioningTest,
|
TEST_P(CertificateProvisioningTest,
|
||||||
ProvisioningResponseFailsWithEmptyResponse) {
|
ProvisioningResponseFailsWithEmptyResponse) {
|
||||||
certificate_provisioning_->Init("");
|
certificate_provisioning_->Init("");
|
||||||
|
// Must set state if not generating request.
|
||||||
|
certificate_provisioning_->SetStateForTesting(
|
||||||
|
CertificateProvisioning::kDrmRequestSent);
|
||||||
|
|
||||||
MockFileSystem file_system;
|
MockFileSystem file_system;
|
||||||
std::string certificate;
|
std::string certificate;
|
||||||
@@ -425,6 +428,9 @@ TEST_P(CertificateProvisioningTest,
|
|||||||
TEST_P(CertificateProvisioningTest,
|
TEST_P(CertificateProvisioningTest,
|
||||||
ProvisioningResponseFailsIfDeviceIsRevoked) {
|
ProvisioningResponseFailsIfDeviceIsRevoked) {
|
||||||
certificate_provisioning_->Init("");
|
certificate_provisioning_->Init("");
|
||||||
|
// Must set state if not generating request.
|
||||||
|
certificate_provisioning_->SetStateForTesting(
|
||||||
|
CertificateProvisioning::kDrmRequestSent);
|
||||||
|
|
||||||
MockFileSystem file_system;
|
MockFileSystem file_system;
|
||||||
std::string response_certificate;
|
std::string response_certificate;
|
||||||
@@ -445,6 +451,10 @@ TEST_P(CertificateProvisioningTest,
|
|||||||
|
|
||||||
TEST_P(CertificateProvisioningTest, ProvisioningResponseSuccess) {
|
TEST_P(CertificateProvisioningTest, ProvisioningResponseSuccess) {
|
||||||
certificate_provisioning_->Init("");
|
certificate_provisioning_->Init("");
|
||||||
|
// Must set state if not generating request.
|
||||||
|
certificate_provisioning_->SetStateForTesting(
|
||||||
|
CertificateProvisioning::kDrmRequestSent);
|
||||||
|
|
||||||
std::string expected_certificate;
|
std::string expected_certificate;
|
||||||
std::string response;
|
std::string response;
|
||||||
ASSERT_TRUE(MakeSignedDrmCertificate(kFakePublicKey, kSerialNumber, kSystemId,
|
ASSERT_TRUE(MakeSignedDrmCertificate(kFakePublicKey, kSerialNumber, kSystemId,
|
||||||
|
|||||||
@@ -299,6 +299,9 @@ static inline WvStatus mapCdmResponseType(wvcdm::CdmResponseType res) {
|
|||||||
case wvcdm::INVALID_QUERY_KEY:
|
case wvcdm::INVALID_QUERY_KEY:
|
||||||
case wvcdm::KEY_NOT_FOUND_1:
|
case wvcdm::KEY_NOT_FOUND_1:
|
||||||
case wvcdm::SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH:
|
case wvcdm::SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH:
|
||||||
|
// Client provided a provisioning response without
|
||||||
|
// generating a provisioning request.
|
||||||
|
case wvcdm::PROVISIONING_UNEXPECTED_RESPONSE_ERROR:
|
||||||
err = Status::BAD_VALUE;
|
err = Status::BAD_VALUE;
|
||||||
break;
|
break;
|
||||||
case wvcdm::KEY_NOT_FOUND_3:
|
case wvcdm::KEY_NOT_FOUND_3:
|
||||||
|
|||||||
Reference in New Issue
Block a user