Merge "Allows sharing of keys between sessions" into klp-dev

This commit is contained in:
Jeff Tinker
2013-10-11 17:02:00 +00:00
committed by Android (Google) Code Review
14 changed files with 308 additions and 34 deletions

View File

@@ -16,6 +16,9 @@ class CdmClientPropertySet {
virtual std::string security_level() const = 0;
virtual bool use_privacy_mode() const = 0;
virtual std::vector<uint8_t> service_certificate() const = 0;
virtual bool is_session_sharing_enabled() const = 0;
virtual uint32_t session_sharing_id() const = 0;
virtual void set_session_sharing_id(uint32_t id) = 0;
};
} // namespace wvcdm

View File

@@ -93,6 +93,7 @@ class CdmEngine : public TimerHandler {
// Is the key known to any session?
virtual bool IsKeyValid(const KeyId& key_id);
virtual bool FindSessionForKey(const KeyId& key_id, CdmSessionId* sessionId);
// Event listener related methods
virtual bool AttachEventListener(const CdmSessionId& session_id,

View File

@@ -3,6 +3,8 @@
#ifndef CDM_BASE_LICENSE_H_
#define CDM_BASE_LICENSE_H_
#include <set>
#include "wv_cdm_types.h"
namespace video_widevine_server {
@@ -41,6 +43,7 @@ class CdmLicense {
CdmKeyResponse& license_response,
CdmKeyResponse& license_renewal_response);
bool HasInitData() { return !init_data_.empty(); }
bool IsKeyLoaded(const KeyId& key_id);
private:
bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request,
@@ -58,6 +61,7 @@ class CdmLicense {
std::string service_certificate_;
std::string init_data_;
bool initialized_;
std::set<KeyId> loaded_keys_;
// Used for certificate based licensing
CdmKeyMessage key_request_;

View File

@@ -66,6 +66,7 @@ class Properties {
static const std::vector<uint8_t> GetServiceCertificate(
const CdmSessionId& session_id);
static bool UsePrivacyMode(const CdmSessionId& session_id);
static uint32_t GetSessionSharingId(const CdmSessionId& session_id);
static bool AddSessionPropertySet(
const CdmSessionId& session_id,

View File

@@ -525,6 +525,29 @@ bool CdmEngine::IsKeyValid(const KeyId& key_id) {
return false;
}
bool CdmEngine::FindSessionForKey(
const KeyId& key_id,
CdmSessionId* session_id) {
if (NULL == session_id) {
LOGE("CdmEngine::FindSessionForKey: session id not provided");
return false;
}
uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id);
for (CdmSessionMap::iterator iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
CdmSessionId id = iter->second->session_id();
if (Properties::GetSessionSharingId(id) == session_sharing_id) {
if (iter->second->IsKeyValid(key_id)) {
*session_id = id;
return true;
}
}
}
return false;
}
bool CdmEngine::AttachEventListener(
const CdmSessionId& session_id,
WvCdmEventListener* listener) {

View File

@@ -324,22 +324,7 @@ CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
}
bool CdmSession::IsKeyValid(const KeyId& key_id) {
if (crypto_session_.get() == NULL || !crypto_session_->IsOpen())
return false;
if (key_id_ != key_id) {
// OEMCrypto does not provide a way to query the existence/validity of a
// key. SelectKey can be used to check whether a key is valid, but there
// is also a side effect - the key is selected for decryption, which might
// be undesirable and it posts restriction on the use of IsKeyValid API.
// TODO(kqyang, gmorgan): consider adding a function in OEMCrypto to check
// if a key is valid.
if (!crypto_session_->SelectKey(key_id)) {
return false;
}
key_id_ = key_id;
}
return true;
return license_parser_.IsKeyLoaded(key_id);
}
CdmSessionId CdmSession::GenerateSessionId() {

View File

@@ -539,9 +539,22 @@ CdmResponseType CdmLicense::HandleKeyResponse(
// merge from Eureka)
policy_engine_->SetLicense(license);
return session_->LoadKeys(signed_response.msg(), signed_response.signature(),
mac_key_iv, mac_key, key_array.size(),
&key_array[0]);
CdmResponseType resp = session_->LoadKeys(signed_response.msg(),
signed_response.signature(),
mac_key_iv,
mac_key,
key_array.size(),
&key_array[0]);
if (KEY_ADDED == resp) {
loaded_keys_.clear();
for (std::vector<CryptoKey>::iterator it = key_array.begin();
it != key_array.end();
++it) {
loaded_keys_.insert(it->key_id());
}
}
return resp;
}
CdmResponseType CdmLicense::HandleKeyUpdateResponse(
@@ -753,4 +766,8 @@ CdmResponseType CdmLicense::HandleKeyErrorResponse(
}
}
bool CdmLicense::IsKeyLoaded(const KeyId& key_id) {
return loaded_keys_.find(key_id) != loaded_keys_.end();
}
} // namespace wvcdm

View File

@@ -100,6 +100,17 @@ bool Properties::UsePrivacyMode(const CdmSessionId& session_id) {
return property_set->use_privacy_mode();
}
uint32_t Properties::GetSessionSharingId(const CdmSessionId& session_id) {
const CdmClientPropertySet* property_set =
GetCdmClientPropertySet(session_id);
if (NULL == property_set) {
LOGE("Properties::GetSessionSharingId: cannot find property set for %s",
session_id.c_str());
return 0;
}
return property_set->session_sharing_id();
}
bool Properties::GetSecurityLevelDirectories(std::vector<std::string>* dirs) {
dirs->resize(sizeof(kSecurityLevelDirs)/sizeof(const char*));
for (size_t i = 0; i < dirs->size(); ++i) {

View File

@@ -21,7 +21,7 @@ class WvContentDecryptionModule {
// Session related methods
virtual CdmResponseType OpenSession(
const CdmKeySystem& key_system,
const CdmClientPropertySet* property_set,
CdmClientPropertySet* property_set,
CdmSessionId* session_id);
virtual CdmResponseType CloseSession(const CdmSessionId& session_id);
@@ -84,6 +84,7 @@ class WvContentDecryptionModule {
WvCdmEventListener* listener);
private:
uint32_t GenerateSessionSharingId();
// instance variables
UniquePtr<CdmEngine> cdm_engine_;

View File

@@ -4,8 +4,10 @@
#include <iostream>
#include "cdm_client_property_set.h"
#include "cdm_engine.h"
#include "log.h"
#include "properties.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
@@ -18,8 +20,13 @@ WvContentDecryptionModule::~WvContentDecryptionModule() {}
CdmResponseType WvContentDecryptionModule::OpenSession(
const CdmKeySystem& key_system,
const CdmClientPropertySet* property_set,
CdmClientPropertySet* property_set,
CdmSessionId* session_id) {
if (property_set && property_set->is_session_sharing_enabled()) {
if (property_set->session_sharing_id() == 0)
property_set->set_session_sharing_id(GenerateSessionSharingId());
}
return cdm_engine_->OpenSession(key_system, property_set, session_id);
}
@@ -111,7 +118,15 @@ CdmResponseType WvContentDecryptionModule::ReleaseSecureStops(
CdmResponseType WvContentDecryptionModule::Decrypt(
const CdmSessionId& session_id,
const CdmDecryptionParameters& parameters) {
return cdm_engine_->Decrypt(session_id, parameters);
CdmSessionId id = session_id;
if (Properties::GetSessionSharingId(session_id) != 0) {
bool status = cdm_engine_->FindSessionForKey(*parameters.key_id, &id);
if (!status) {
LOGE("WvContentDecryptionModule::Decrypt: unable to find session");
return KEY_ERROR;
}
}
return cdm_engine_->Decrypt(id, parameters);
}
bool WvContentDecryptionModule::AttachEventListener(
@@ -124,4 +139,9 @@ bool WvContentDecryptionModule::DetachEventListener(
return cdm_engine_->DetachEventListener(session_id, listener);
}
uint32_t WvContentDecryptionModule::GenerateSessionSharingId() {
static int next_session_sharing_id = 0;
return ++next_session_sharing_id;
}
} // namespace wvcdm

View File

@@ -205,7 +205,10 @@ SubSampleInfo partial_offset_single_encrypted_sub_sample = {
namespace wvcdm {
class TestWvCdmClientPropertySet : public CdmClientPropertySet {
public:
TestWvCdmClientPropertySet() : use_privacy_mode_(false) {}
TestWvCdmClientPropertySet()
: use_privacy_mode_(false),
is_session_sharing_enabled_(false),
session_sharing_id_(0) {}
virtual ~TestWvCdmClientPropertySet() {}
virtual std::string security_level() const { return security_level_; }
@@ -213,6 +216,10 @@ class TestWvCdmClientPropertySet : public CdmClientPropertySet {
return service_certificate_;
}
virtual bool use_privacy_mode() const { return use_privacy_mode_; }
bool is_session_sharing_enabled() const {
return is_session_sharing_enabled_;
}
uint32_t session_sharing_id() const { return session_sharing_id_; }
void set_security_level(const std::string& security_level) {
if (!security_level.compare(QUERY_VALUE_SECURITY_LEVEL_L1) ||
@@ -227,11 +234,17 @@ class TestWvCdmClientPropertySet : public CdmClientPropertySet {
void set_use_privacy_mode(bool use_privacy_mode) {
use_privacy_mode_ = use_privacy_mode;
}
void set_session_sharing_mode(bool enable) {
is_session_sharing_enabled_ = enable;
}
void set_session_sharing_id(uint32_t id) { session_sharing_id_ = id; }
private:
std::string security_level_;
std::vector<uint8_t> service_certificate_;
bool use_privacy_mode_;
bool is_session_sharing_enabled_;
uint32_t session_sharing_id_;
};
class TestWvCdmEventListener : public WvCdmEventListener {
@@ -377,6 +390,10 @@ class WvCdmDecryptionTest
: public WvCdmRequestLicenseTest,
public ::testing::WithParamInterface<SubSampleInfo*> {};
class WvCdmSessionSharingTest
: public WvCdmRequestLicenseTest,
public ::testing::WithParamInterface<bool> {};
TEST_F(WvCdmRequestLicenseTest, ProvisioningTest) {
decryptor_.OpenSession(g_key_system, NULL, &session_id_);
std::string provisioning_server_url;
@@ -523,6 +540,54 @@ TEST_F(WvCdmRequestLicenseTest, DISABLED_PrivacyModeWithServiceCertificateTest)
decryptor_.CloseSession(session_id_);
}
TEST_P(WvCdmSessionSharingTest, SessionSharingTest) {
bool enable_session_sharing = GetParam();
TestWvCdmClientPropertySet property_set;
property_set.set_session_sharing_mode(enable_session_sharing);
decryptor_.OpenSession(g_key_system, &property_set, &session_id_);
CdmSessionId gp_session_id_1 = session_id_;
GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming);
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
// TODO(rfrias): Move content information to ConfigTestEnv
std::string gp_client_auth2 =
"?source=YOUTUBE&video_id=z3S_NhwueaM&oauth=ya.gtsqawidevine";
std::string gp_key_id2 =
wvcdm::a2bs_hex(
"000000347073736800000000" // blob size and pssh
"edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id
"08011210bdf1cb4fffc6506b8b7945b0bd2917fb"); // pssh data
decryptor_.OpenSession(g_key_system, &property_set, &session_id_);
CdmSessionId gp_session_id_2 = session_id_;
GenerateKeyRequest(g_key_system, gp_key_id2, kLicenseTypeStreaming);
VerifyKeyRequestResponse(g_license_server, gp_client_auth2, gp_key_id2, false);
SubSampleInfo* data = &single_encrypted_sub_sample;
std::vector<uint8_t> decrypt_buffer(data->encrypt_data.size());
CdmDecryptionParameters decryption_parameters(&data->key_id,
&data->encrypt_data.front(),
data->encrypt_data.size(),
&data->iv,
data->block_offset,
&decrypt_buffer[0]);
decryption_parameters.is_encrypted = data->is_encrypted;
decryption_parameters.is_secure = data->is_secure;
EXPECT_EQ(
NO_ERROR == decryptor_.Decrypt(gp_session_id_2, decryption_parameters),
enable_session_sharing);
EXPECT_EQ(std::equal(data->decrypt_data.begin(), data->decrypt_data.end(),
decrypt_buffer.begin()),
enable_session_sharing);
decryptor_.CloseSession(gp_session_id_1);
decryptor_.CloseSession(gp_session_id_2);
}
INSTANTIATE_TEST_CASE_P(Cdm, WvCdmSessionSharingTest, ::testing::Bool());
TEST_F(WvCdmRequestLicenseTest, BaseMessageTest) {
decryptor_.OpenSession(g_key_system, NULL, &session_id_);
GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming);

View File

@@ -161,40 +161,58 @@ class WVDrmPlugin : public android::DrmPlugin,
class WVClientPropertySet : public wvcdm::CdmClientPropertySet {
public:
WVClientPropertySet()
: mUsePrivacyMode(false) {}
: mUsePrivacyMode(false), mShareKeys(false), mSessionSharingId(0) {}
virtual ~WVClientPropertySet() {}
void set_security_level(const std::string& securityLevel) {
mSecurityLevel = securityLevel;
}
virtual std::string security_level() const {
return mSecurityLevel;
}
void set_use_privacy_mode(bool usePrivacyMode) {
mUsePrivacyMode = usePrivacyMode;
void set_security_level(const std::string& securityLevel) {
mSecurityLevel = securityLevel;
}
virtual bool use_privacy_mode() const {
return mUsePrivacyMode;
}
void set_service_certificate(const std::vector<uint8_t>& serviceCertificate) {
mServiceCertificate = serviceCertificate;
void set_use_privacy_mode(bool usePrivacyMode) {
mUsePrivacyMode = usePrivacyMode;
}
virtual std::vector<uint8_t> service_certificate() const {
return mServiceCertificate;
}
void set_service_certificate(const std::vector<uint8_t>& serviceCertificate) {
mServiceCertificate = serviceCertificate;
}
virtual bool is_session_sharing_enabled() const {
return mShareKeys;
}
void set_is_session_sharing_enabled(bool shareKeys) {
mShareKeys = shareKeys;
}
virtual uint32_t session_sharing_id() const {
return mSessionSharingId;
}
virtual void set_session_sharing_id(uint32_t id) {
mSessionSharingId = id;
}
private:
DISALLOW_EVIL_CONSTRUCTORS(WVClientPropertySet);
std::string mSecurityLevel;
bool mUsePrivacyMode;
std::vector<uint8_t> mServiceCertificate;
bool mShareKeys;
uint32_t mSessionSharingId;
} mPropertySet;
WvContentDecryptionModule* mCDM;

View File

@@ -392,6 +392,12 @@ status_t WVDrmPlugin::getPropertyString(const String8& name,
} else {
value = kDisable;
}
} else if (name == "sessionSharing") {
if (mPropertySet.is_session_sharing_enabled()) {
value = kEnable;
} else {
value = kDisable;
}
} else {
ALOGE("App requested unknown string property %s", name.string());
return android::ERROR_DRM_CANNOT_HANDLE;
@@ -475,6 +481,20 @@ status_t WVDrmPlugin::setPropertyString(const String8& name,
ALOGE("App requested unknown privacy mode %s", value.string());
return android::BAD_VALUE;
}
} else if (name == "sessionSharing") {
if (mCryptoSessions.size() == 0) {
if (value == kEnable) {
mPropertySet.set_is_session_sharing_enabled(true);
} else if (value == kDisable) {
mPropertySet.set_is_session_sharing_enabled(false);
} else {
ALOGE("App requested unknown sharing type %s", value.string());
return android::BAD_VALUE;
}
} else {
ALOGE("App tried to change key sharing while sessions are open.");
return kErrorSessionIsOpen;
}
} else {
ALOGE("App set unknown string property %s", name.string());
return android::ERROR_DRM_CANNOT_HANDLE;

View File

@@ -26,7 +26,7 @@ using namespace wvdrm;
class MockCDM : public WvContentDecryptionModule {
public:
MOCK_METHOD3(OpenSession, CdmResponseType(const CdmKeySystem&,
const CdmClientPropertySet*,
CdmClientPropertySet*,
CdmSessionId*));
MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&));
@@ -1181,7 +1181,6 @@ TEST_F(WVDrmPluginTest, GeneratesProvisioningNeededEvent) {
ASSERT_EQ(ERROR_DRM_NOT_PROVISIONED, res);
}
TEST_F(WVDrmPluginTest, ProvidesExpectedDefaultPropertiesToCdm) {
StrictMock<MockCDM> cdm;
StrictMock<MockCrypto> crypto;
@@ -1219,6 +1218,8 @@ TEST_F(WVDrmPluginTest, ProvidesExpectedDefaultPropertiesToCdm) {
EXPECT_STREQ("", propertySet->security_level().c_str());
EXPECT_FALSE(propertySet->use_privacy_mode());
EXPECT_EQ(0u, propertySet->service_certificate().size());
EXPECT_FALSE(propertySet->is_session_sharing_enabled());
EXPECT_EQ(0u, propertySet->session_sharing_id());
}
TEST_F(WVDrmPluginTest, CanSetSecurityLevel) {
@@ -1395,3 +1396,107 @@ TEST_F(WVDrmPluginTest, CanSetServiceCertificate) {
ASSERT_EQ(OK, res);
EXPECT_EQ(0u, propertySet->service_certificate().size());
}
TEST_F(WVDrmPluginTest, CanSetSessionSharing) {
StrictMock<MockCDM> cdm;
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(&cdm, &crypto);
const CdmClientPropertySet* propertySet = NULL;
// Provide expected mock behavior
{
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(cdm, OpenSession(_, _, _))
.WillRepeatedly(DoAll(SetArgPointee<2>(cdmSessionId),
SaveArg<1>(&propertySet),
Return(wvcdm::NO_ERROR)));
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Let gMock know these calls will happen but we aren't interested in them.
EXPECT_CALL(cdm, AttachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, DetachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, CloseSession(_))
.Times(AtLeast(0));
}
status_t res;
// Test turning on session sharing
res = plugin.setPropertyString(String8("sessionSharing"), String8("enable"));
ASSERT_EQ(OK, res);
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
EXPECT_TRUE(propertySet->is_session_sharing_enabled());
plugin.closeSession(sessionId);
// Test turning off session sharing
res = plugin.setPropertyString(String8("sessionSharing"), String8("disable"));
ASSERT_EQ(OK, res);
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
EXPECT_FALSE(propertySet->is_session_sharing_enabled());
plugin.closeSession(sessionId);
// Test nonsense (Should Fail)
res = plugin.setPropertyString(String8("sessionSharing"), String8("nonsense"));
ASSERT_NE(OK, res);
// Test changing sharing with a session open (Should Fail)
plugin.openSession(sessionId);
res = plugin.setPropertyString(String8("sessionSharing"), String8("enable"));
ASSERT_NE(OK, res);
}
TEST_F(WVDrmPluginTest, AllowsStoringOfSessionSharingId) {
StrictMock<MockCDM> cdm;
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(&cdm, &crypto);
CdmClientPropertySet* propertySet = NULL;
uint32_t sharingId;
FILE* fp = fopen("/dev/urandom", "r");
fread(&sharingId, sizeof(uint32_t), 1, fp);
fclose(fp);
// Provide expected mock behavior
{
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(cdm, OpenSession(_, _, _))
.WillRepeatedly(DoAll(SetArgPointee<2>(cdmSessionId),
SaveArg<1>(&propertySet),
Return(wvcdm::NO_ERROR)));
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Let gMock know these calls will happen but we aren't interested in them.
EXPECT_CALL(cdm, AttachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, DetachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, CloseSession(_))
.Times(AtLeast(0));
}
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
propertySet->set_session_sharing_id(sharingId);
EXPECT_EQ(sharingId, propertySet->session_sharing_id());
}