Allow CE CDM to Create Sessions Without a Service Certificate
(This is a merge of http://go/wvgerrit/84510) When the CE CDM 3.5 behavior around service certificates was originally implemented, it allowed sessions to be created if a service certificate had not yet been installed, in keeping with the EME spec. However, the service certificate in use at session creation time was cached, and so there was a bug where any sessions open before a service certificate was installed would never be updated with any future service certificates. The code also caused problems for Android. When it was merged to master, it was fixed to simply not allow session creation on CE CDM without a service certificate. However, this created an impedance mismatch between the CE CDM and EME that has caused pain for Shaka Player Embedded, Chrome, Chromecast, Fuchsia, and likely every partner that is trying to implement a fully-compliant EME stack on top of CE CDM. Removing the code that blocks session creation without a service certificate is easy. Fixing the bug that motivated it is not. Removing the caching is not possible because Android needs it for certain behavior on its end. So instead, the CE CDM will have to iterate over all open sessions and update their service certificates if the installed service certificate changes. Test: CE CDM Unit Tests Test: Android Unit Tests Bug: 111766009 Change-Id: I1bd70553e2209b823a6acdc221c0497a5f3181b2
This commit is contained in:
@@ -137,6 +137,10 @@ class CdmEngine {
|
|||||||
virtual CdmResponseType RenewKey(const CdmSessionId& session_id,
|
virtual CdmResponseType RenewKey(const CdmSessionId& session_id,
|
||||||
const CdmKeyResponse& key_data);
|
const CdmKeyResponse& key_data);
|
||||||
|
|
||||||
|
// Change the service certificate installed in a given session.
|
||||||
|
virtual CdmResponseType SetSessionServiceCertificate(
|
||||||
|
const CdmSessionId& session_id, const std::string& service_certificate);
|
||||||
|
|
||||||
// Query system information
|
// Query system information
|
||||||
virtual CdmResponseType QueryStatus(SecurityLevel security_level,
|
virtual CdmResponseType QueryStatus(SecurityLevel security_level,
|
||||||
const std::string& query_token,
|
const std::string& query_token,
|
||||||
|
|||||||
@@ -43,19 +43,23 @@ class CdmSession {
|
|||||||
bool IsClosed() { return closed_; }
|
bool IsClosed() { return closed_; }
|
||||||
|
|
||||||
// Initializes this instance of CdmSession with the given property set.
|
// Initializes this instance of CdmSession with the given property set.
|
||||||
// |cdm_client_property_set| MAY be null, is owned by the caller,
|
//
|
||||||
// and must remain in scope throughout the scope of this session.
|
// |cdm_client_property_set| is caller owned, may be null, but must be in
|
||||||
|
// scope as long as the session is in scope. The service certificate field is
|
||||||
|
// cached at the time Init() is called.
|
||||||
virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set);
|
virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set);
|
||||||
|
|
||||||
// Initializes this instance of CdmSession with the given parmeters.
|
// Initializes this instance of CdmSession with the given parmeters.
|
||||||
// All parameters are owned by the caller.
|
// All parameters are owned by the caller.
|
||||||
// |service_certificate| is caller owned, cannot be null, and must be in
|
//
|
||||||
// scope as long as the session is in scope.
|
// |cdm_client_property_set| is caller owned, may be null, but must be in
|
||||||
// |cdm_client_property_set| is caller owned, may be null, but must be
|
// scope as long as the session is in scope. The service certificate field is
|
||||||
// in scope as long as the session is in scope.
|
// cached at the time Init() is called.
|
||||||
|
//
|
||||||
// |forced_session_id| is caller owned and may be null.
|
// |forced_session_id| is caller owned and may be null.
|
||||||
// |event_listener| is caller owned, may be null, but must be in scope
|
//
|
||||||
// as long as the session is in scope.
|
// |event_listener| is caller owned, may be null, but must be in scope as long
|
||||||
|
// as the session is in scope.
|
||||||
virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set,
|
virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set,
|
||||||
const CdmSessionId* forced_session_id,
|
const CdmSessionId* forced_session_id,
|
||||||
WvCdmEventListener* event_listener);
|
WvCdmEventListener* event_listener);
|
||||||
@@ -84,6 +88,11 @@ class CdmSession {
|
|||||||
// AddKey() - Accept license response and extract key info.
|
// AddKey() - Accept license response and extract key info.
|
||||||
virtual CdmResponseType AddKey(const CdmKeyResponse& key_response);
|
virtual CdmResponseType AddKey(const CdmKeyResponse& key_response);
|
||||||
|
|
||||||
|
// Override the currently-installed service certificate with a new service
|
||||||
|
// certificate.
|
||||||
|
virtual CdmResponseType SetServiceCertificate(
|
||||||
|
const std::string& service_certificate);
|
||||||
|
|
||||||
// Query session status
|
// Query session status
|
||||||
virtual CdmResponseType QueryStatus(CdmQueryMap* query_response);
|
virtual CdmResponseType QueryStatus(CdmQueryMap* query_response);
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ class CdmLicense {
|
|||||||
const std::string& signed_service_certificate,
|
const std::string& signed_service_certificate,
|
||||||
CryptoSession* session, PolicyEngine* policy_engine);
|
CryptoSession* session, PolicyEngine* policy_engine);
|
||||||
|
|
||||||
|
// Override the currently-installed service certificate with a new service
|
||||||
|
// certificate.
|
||||||
|
virtual CdmResponseType SetServiceCertificate(
|
||||||
|
const std::string& signed_service_certificate);
|
||||||
|
|
||||||
virtual CdmResponseType PrepareKeyRequest(
|
virtual CdmResponseType PrepareKeyRequest(
|
||||||
const InitializationData& init_data, CdmLicenseType license_type,
|
const InitializationData& init_data, CdmLicenseType license_type,
|
||||||
const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request,
|
const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request,
|
||||||
|
|||||||
@@ -399,7 +399,11 @@ enum CdmResponseType {
|
|||||||
REWRAP_DEVICE_RSA_KEY_30_ERROR = 345,
|
REWRAP_DEVICE_RSA_KEY_30_ERROR = 345,
|
||||||
INVALID_SRM_LIST = 346,
|
INVALID_SRM_LIST = 346,
|
||||||
KEYSET_ID_NOT_FOUND_4 = 347,
|
KEYSET_ID_NOT_FOUND_4 = 347,
|
||||||
// Don't forget to add new values to ../test/test_printers.cpp.
|
SESSION_NOT_FOUND_22 = 348,
|
||||||
|
// Don't forget to add new values to
|
||||||
|
// * core/test/test_printers.cpp.
|
||||||
|
// * android/include/mapErrors-inl.h
|
||||||
|
// * android/include_hidl/mapErrors-inl.h
|
||||||
};
|
};
|
||||||
|
|
||||||
enum CdmKeyStatus {
|
enum CdmKeyStatus {
|
||||||
|
|||||||
@@ -507,6 +507,19 @@ CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id,
|
|||||||
return KEY_ADDED;
|
return KEY_ADDED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CdmResponseType CdmEngine::SetSessionServiceCertificate(
|
||||||
|
const CdmSessionId& session_id, const std::string& service_certificate) {
|
||||||
|
LOGI("Setting service certificate: session_id = %s", session_id.c_str());
|
||||||
|
|
||||||
|
std::shared_ptr<CdmSession> session;
|
||||||
|
if (!session_map_.FindSession(session_id, &session)) {
|
||||||
|
LOGE("Session ID not found: %s", session_id.c_str());
|
||||||
|
return SESSION_NOT_FOUND_22;
|
||||||
|
}
|
||||||
|
|
||||||
|
return session->SetServiceCertificate(service_certificate);
|
||||||
|
}
|
||||||
|
|
||||||
CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
|
CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
|
||||||
const std::string& query_token,
|
const std::string& query_token,
|
||||||
std::string* query_response) {
|
std::string* query_response) {
|
||||||
|
|||||||
@@ -586,6 +586,11 @@ CdmResponseType CdmSession::QueryStatus(CdmQueryMap* query_response) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CdmResponseType CdmSession::SetServiceCertificate(
|
||||||
|
const std::string& service_certificate) {
|
||||||
|
return license_parser_->SetServiceCertificate(service_certificate);
|
||||||
|
}
|
||||||
|
|
||||||
CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* query_response) {
|
CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* query_response) {
|
||||||
return policy_engine_->Query(query_response);
|
return policy_engine_->Query(query_response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,17 +226,10 @@ bool CdmLicense::Init(const std::string& client_token,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (use_privacy_mode) {
|
if (use_privacy_mode && !signed_service_certificate.empty() &&
|
||||||
if (!signed_service_certificate.empty()) {
|
service_certificate_.Init(signed_service_certificate) != NO_ERROR) {
|
||||||
if (service_certificate_.Init(signed_service_certificate) != NO_ERROR)
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!service_certificate_.has_certificate() &&
|
|
||||||
!Properties::allow_service_certificate_requests()) {
|
|
||||||
LOGE("Required service certificate not provided");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client_token_ = client_token;
|
client_token_ = client_token;
|
||||||
client_token_type_ = client_token_type;
|
client_token_type_ = client_token_type;
|
||||||
@@ -248,6 +241,11 @@ bool CdmLicense::Init(const std::string& client_token,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CdmResponseType CdmLicense::SetServiceCertificate(
|
||||||
|
const std::string& signed_service_certificate) {
|
||||||
|
return service_certificate_.Init(signed_service_certificate);
|
||||||
|
}
|
||||||
|
|
||||||
CdmResponseType CdmLicense::PrepareKeyRequest(
|
CdmResponseType CdmLicense::PrepareKeyRequest(
|
||||||
const InitializationData& init_data, CdmLicenseType license_type,
|
const InitializationData& init_data, CdmLicenseType license_type,
|
||||||
const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request,
|
const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request,
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ const std::string kWebmMimeType = "video/webm";
|
|||||||
const std::string kEmptyString;
|
const std::string kEmptyString;
|
||||||
const std::string kComma = ",";
|
const std::string kComma = ",";
|
||||||
|
|
||||||
|
const std::string kFakeSessionId = "TotallyARealSession123456789";
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class WvCdmEnginePreProvTest : public WvCdmTestBase {
|
class WvCdmEnginePreProvTest : public WvCdmTestBase {
|
||||||
@@ -317,6 +319,27 @@ TEST_F(WvCdmEnginePreProvTestUat, ProvisioningServiceCertificateInvalidTest) {
|
|||||||
ASSERT_NE(cdm_engine_.ValidateServiceCertificate(certificate), NO_ERROR);
|
ASSERT_NE(cdm_engine_.ValidateServiceCertificate(certificate), NO_ERROR);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TEST_F(WvCdmEngineTest, SetLicensingServiceValidCertificate) {
|
||||||
|
ASSERT_EQ(cdm_engine_.SetSessionServiceCertificate(
|
||||||
|
session_id_, config_.license_service_certificate()),
|
||||||
|
NO_ERROR);
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(WvCdmEngineTest, SetLicensingServiceCertificateUnknownSession) {
|
||||||
|
ASSERT_EQ(cdm_engine_.SetSessionServiceCertificate(
|
||||||
|
kFakeSessionId, config_.license_service_certificate()),
|
||||||
|
SESSION_NOT_FOUND_22);
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(WvCdmEngineTest, SetLicensingServiceInvalidCertificate) {
|
||||||
|
std::string certificate = config_.license_service_certificate();
|
||||||
|
// Add four nulls to the beginning of the cert to invalidate it
|
||||||
|
certificate.insert(0, 4, '\0');
|
||||||
|
|
||||||
|
ASSERT_NE(cdm_engine_.SetSessionServiceCertificate(session_id_, certificate),
|
||||||
|
NO_ERROR);
|
||||||
|
};
|
||||||
|
|
||||||
TEST_F(WvCdmEnginePreProvTestStaging, ProvisioningTest) { Provision(); }
|
TEST_F(WvCdmEnginePreProvTestStaging, ProvisioningTest) { Provision(); }
|
||||||
|
|
||||||
TEST_F(WvCdmEnginePreProvTestUatBinary, ProvisioningTest) {
|
TEST_F(WvCdmEnginePreProvTestUatBinary, ProvisioningTest) {
|
||||||
|
|||||||
@@ -282,10 +282,9 @@ TEST_F(CdmLicenseTest, InitWithEmptyServiceCert) {
|
|||||||
EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true));
|
EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true));
|
||||||
|
|
||||||
CreateCdmLicense();
|
CreateCdmLicense();
|
||||||
EXPECT_EQ(cdm_license_->Init(kToken, kClientTokenDrmCert, "", true,
|
EXPECT_TRUE(cdm_license_->Init(kToken, kClientTokenDrmCert, "", true,
|
||||||
kEmptyServiceCertificate, crypto_session_,
|
kEmptyServiceCertificate, crypto_session_,
|
||||||
policy_engine_),
|
policy_engine_));
|
||||||
Properties::allow_service_certificate_requests());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CdmLicenseTest, InitWithInvalidServiceCert) {
|
TEST_F(CdmLicenseTest, InitWithInvalidServiceCert) {
|
||||||
|
|||||||
@@ -781,6 +781,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
|
|||||||
case SESSION_NOT_FOUND_21:
|
case SESSION_NOT_FOUND_21:
|
||||||
*os << "SESSION_NOT_FOUND_21";
|
*os << "SESSION_NOT_FOUND_21";
|
||||||
break;
|
break;
|
||||||
|
case SESSION_NOT_FOUND_22:
|
||||||
|
*os << "SESSION_NOT_FOUND_22";
|
||||||
|
break;
|
||||||
case INVALID_DECRYPT_HASH_FORMAT:
|
case INVALID_DECRYPT_HASH_FORMAT:
|
||||||
*os << "INVALID_DECRYPT_HASH_FORMAT";
|
*os << "INVALID_DECRYPT_HASH_FORMAT";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) {
|
|||||||
case wvcdm::SESSION_NOT_FOUND_19:
|
case wvcdm::SESSION_NOT_FOUND_19:
|
||||||
case wvcdm::SESSION_NOT_FOUND_20:
|
case wvcdm::SESSION_NOT_FOUND_20:
|
||||||
case wvcdm::SESSION_NOT_FOUND_21:
|
case wvcdm::SESSION_NOT_FOUND_21:
|
||||||
|
case wvcdm::SESSION_NOT_FOUND_22:
|
||||||
return android::ERROR_DRM_SESSION_NOT_OPENED;
|
return android::ERROR_DRM_SESSION_NOT_OPENED;
|
||||||
case wvcdm::SESSION_KEYS_NOT_FOUND:
|
case wvcdm::SESSION_KEYS_NOT_FOUND:
|
||||||
return kSessionKeysNotFound;
|
return kSessionKeysNotFound;
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) {
|
|||||||
case wvcdm::SESSION_NOT_FOUND_19:
|
case wvcdm::SESSION_NOT_FOUND_19:
|
||||||
case wvcdm::SESSION_NOT_FOUND_20:
|
case wvcdm::SESSION_NOT_FOUND_20:
|
||||||
case wvcdm::SESSION_NOT_FOUND_21:
|
case wvcdm::SESSION_NOT_FOUND_21:
|
||||||
|
case wvcdm::SESSION_NOT_FOUND_22:
|
||||||
return Status::ERROR_DRM_SESSION_NOT_OPENED;
|
return Status::ERROR_DRM_SESSION_NOT_OPENED;
|
||||||
|
|
||||||
case wvcdm::DECRYPT_ERROR:
|
case wvcdm::DECRYPT_ERROR:
|
||||||
|
|||||||
Reference in New Issue
Block a user