Files
android/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp
John "Juce" Bruce 89682556ad Fix CanSetServiceCertificate Test
(This is a merge of http://go/wvgerrit/14783)

When validation was added to the service certificate property, it
broke the associated test, which sends random data. It also did not
do any testing of the new validation itself.

This fix makes the validation method on WvContentDecryptionModule
non-static so that it can be mocked and handled appropriately in the
test.

Bug: 21923281
Change-Id: Id5d2315709fce35f9347b3545f594371810349f0
2015-06-18 18:54:19 -07:00

1846 lines
64 KiB
C++

//
// Copyright 2013 Google Inc. All Rights Reserved.
//
#include <stdio.h>
#include <string.h>
#include <ostream>
#include <string>
#include "cdm_client_property_set.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "media/stagefright/foundation/ABase.h"
#include "media/stagefright/foundation/AString.h"
#include "media/stagefright/MediaErrors.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
#include "wv_content_decryption_module.h"
#include "WVDrmPlugin.h"
#include "WVErrors.h"
using namespace android;
using namespace std;
using namespace testing;
using namespace wvcdm;
using namespace wvdrm;
namespace {
const String8 kEmptyString;
const String8 kOrigin("widevine.com");
const String8 kAppId("com.unittest.mock.app.id");
const uint8_t* const kUnprovisionResponse =
reinterpret_cast<const uint8_t*>("unprovision");
const size_t kUnprovisionResponseSize = 11;
}
class MockCDM : public WvContentDecryptionModule {
public:
MOCK_METHOD5(OpenSession, CdmResponseType(const CdmKeySystem&,
CdmClientPropertySet*,
const std::string&,
WvCdmEventListener*,
CdmSessionId*));
MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&));
MOCK_METHOD11(GenerateKeyRequest,
CdmResponseType(const CdmSessionId&, const CdmKeySetId&,
const std::string&, const CdmInitData&,
const CdmLicenseType, CdmAppParameterMap&,
CdmClientPropertySet*, const std::string&,
CdmKeyMessage*, CdmKeyRequestType*, string*));
MOCK_METHOD3(AddKey, CdmResponseType(const CdmSessionId&,
const CdmKeyResponse&,
CdmKeySetId*));
MOCK_METHOD1(RemoveKeys, CdmResponseType(const CdmSessionId&));
MOCK_METHOD2(RestoreKey, CdmResponseType(const CdmSessionId&,
const CdmKeySetId&));
MOCK_METHOD2(QueryStatus, CdmResponseType(SecurityLevel, CdmQueryMap*));
MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&,
CdmQueryMap*));
MOCK_METHOD2(QueryKeyControlInfo, CdmResponseType(const CdmSessionId&,
CdmQueryMap*));
MOCK_METHOD5(GetProvisioningRequest, CdmResponseType(CdmCertificateType,
const std::string&,
const std::string&,
CdmProvisioningRequest*,
std::string*));
MOCK_METHOD4(HandleProvisioningResponse,
CdmResponseType(const std::string&, CdmProvisioningResponse&,
std::string*, std::string*));
MOCK_METHOD2(Unprovision, CdmResponseType(CdmSecurityLevel,
const std::string&));
MOCK_METHOD2(GetUsageInfo, CdmResponseType(const std::string&,
CdmUsageInfo*));
MOCK_METHOD3(GetUsageInfo, CdmResponseType(const std::string&,
const CdmSecureStopId&,
CdmUsageInfo*));
MOCK_METHOD1(ReleaseAllUsageInfo, CdmResponseType(const std::string&));
MOCK_METHOD1(ReleaseUsageInfo,
CdmResponseType(const CdmUsageInfoReleaseMessage&));
MOCK_METHOD1(IsValidServiceCertificate, bool(const std::string&));
};
class MockCrypto : public WVGenericCryptoInterface {
public:
MOCK_METHOD3(selectKey, OEMCryptoResult(const OEMCrypto_SESSION,
const uint8_t*, size_t));
MOCK_METHOD6(encrypt, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*,
size_t, const uint8_t*,
OEMCrypto_Algorithm, uint8_t*));
MOCK_METHOD6(decrypt, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*,
size_t, const uint8_t*,
OEMCrypto_Algorithm, uint8_t*));
MOCK_METHOD6(sign, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*, size_t,
OEMCrypto_Algorithm, uint8_t*, size_t*));
MOCK_METHOD6(verify, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*,
size_t, OEMCrypto_Algorithm,
const uint8_t*, size_t));
MOCK_METHOD3(loadDeviceRSAKey, OEMCryptoResult(OEMCrypto_SESSION,
const uint8_t*, size_t));
MOCK_METHOD6(generateRSASignature, OEMCryptoResult(OEMCrypto_SESSION,
const uint8_t*, size_t,
uint8_t*, size_t*,
RSA_Padding_Scheme));
};
class MockDrmPluginListener : public DrmPluginListener {
public:
MOCK_METHOD4(sendEvent, void(DrmPlugin::EventType, int,
const Vector<uint8_t>*, const Vector<uint8_t>*));
MOCK_METHOD2(sendExpirationUpdate, void(const Vector<uint8_t>*, int64_t));
MOCK_METHOD3(sendKeysChange, void(const Vector<uint8_t>*,
const Vector<DrmPlugin::KeyStatus>*, bool));
};
template <uint8_t DIGIT>
CdmResponseType setSessionIdOnMap(Unused, CdmQueryMap* map) {
static const char oecId[] = {DIGIT + '0', '\0'};
(*map)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = oecId;
return wvcdm::NO_ERROR;
}
class WVDrmPluginTest : public Test {
protected:
static const uint32_t kSessionIdSize = 16;
uint8_t sessionIdRaw[kSessionIdSize];
Vector<uint8_t> sessionId;
CdmSessionId cdmSessionId;
virtual void SetUp() {
// Fill the session ID
FILE* fp = fopen("/dev/urandom", "r");
fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp);
fclose(fp);
memcpy(sessionIdRaw, SESSION_ID_PREFIX, sizeof(SESSION_ID_PREFIX) - 1);
sessionId.appendArray(sessionIdRaw, kSessionIdSize);
cdmSessionId.assign(sessionId.begin(), sessionId.end());
// Set default return values for gMock
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
DefaultValue<OEMCryptoResult>::Set(OEMCrypto_SUCCESS);
DefaultValue<bool>::Set(true);
}
};
struct OriginTestVariant {
// For a test that does not expect any follow-up queries
OriginTestVariant(const std::string& nameValue, const String8& originValue,
const std::string& expectedOriginValue)
: name(nameValue), origin(originValue),
expectedOrigin(expectedOriginValue) {}
const std::string name;
const String8 origin;
const std::string expectedOrigin;
};
void PrintTo(const OriginTestVariant& param, ::std::ostream* os) {
*os << param.name << " Variant";
}
class WVDrmPluginOriginTest : public WVDrmPluginTest,
public WithParamInterface<OriginTestVariant> {};
TEST_F(WVDrmPluginTest, OpensSessions) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
EXPECT_CALL(*cdm,
OpenSession(StrEq("com.widevine"), _, StrEq(EMPTY_ORIGIN), _, _))
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
EXPECT_THAT(sessionId, ElementsAreArray(sessionIdRaw, kSessionIdSize));
}
TEST_F(WVDrmPluginTest, ClosesSessions) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
EXPECT_CALL(*cdm, CloseSession(cdmSessionId))
.Times(1);
status_t res = plugin.closeSession(sessionId);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, ClosesSessionWithoutReturningError) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
EXPECT_CALL(*cdm, CloseSession(cdmSessionId))
.WillOnce(Return(SESSION_NOT_FOUND_1));
status_t res = plugin.closeSession(sessionId);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const size_t kInitDataSize = 128;
uint8_t initDataRaw[kInitDataSize];
static const size_t kRequestSize = 256;
uint8_t requestRaw[kRequestSize];
static const uint32_t kKeySetIdSize = 32;
uint8_t keySetIdRaw[kKeySetIdSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(initDataRaw, sizeof(uint8_t), kInitDataSize, fp);
fread(requestRaw, sizeof(uint8_t), kRequestSize, fp);
fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp);
fclose(fp);
memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1);
CdmKeySetId cdmKeySetId(reinterpret_cast<char*>(keySetIdRaw), kKeySetIdSize);
Vector<uint8_t> keySetId;
keySetId.appendArray(keySetIdRaw, kKeySetIdSize);
CdmInitData cdmInitData(reinterpret_cast<char*>(initDataRaw), kInitDataSize);
Vector<uint8_t> initData;
initData.appendArray(initDataRaw, kInitDataSize);
static const uint8_t psshPrefix[] = {
0, 0, 0, 32 + kInitDataSize, // Total size
'p', 's', 's', 'h', // "PSSH"
0, 0, 0, 0, // Flags - must be zero
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, // Widevine UUID
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
0, 0, 0, kInitDataSize // Size of initData
};
static const size_t kPsshPrefixSize = sizeof(psshPrefix);
static const size_t kPsshBoxSize = kPsshPrefixSize + kInitDataSize;
uint8_t psshBoxRaw[kPsshBoxSize];
memcpy(psshBoxRaw, psshPrefix, kPsshPrefixSize);
memcpy(psshBoxRaw + kPsshPrefixSize, initDataRaw, kInitDataSize);
CdmInitData cdmPsshBox(reinterpret_cast<char*>(psshBoxRaw), kPsshBoxSize);
Vector<uint8_t> psshBox;
psshBox.appendArray(psshBoxRaw, kPsshBoxSize);
CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize);
KeyedVector<String8, String8> parameters;
CdmAppParameterMap cdmParameters;
parameters.add(String8("paddingScheme"), String8("BUBBLE WRAP"));
cdmParameters["paddingScheme"] = "BUBBLE WRAP";
parameters.add(String8("favorite-particle"), String8("tetraquark"));
cdmParameters["favorite-particle"] = "tetraquark";
parameters.add(String8("answer"), String8("6 * 9"));
cdmParameters["answer"] = "6 * 9";
static const char* kDefaultUrl = "http://google.com/";
static const char* kIsoBmffMimeType = "cenc";
static const char* kWebmMimeType = "webm";
struct TestSet {
const char* mimeType;
const Vector<uint8_t>& initDataIn;
const CdmInitData& initDataOut;
};
// We run the same set of operations on several sets of data, simulating
// different valid calling patterns.
TestSet testSets[] = {
{kIsoBmffMimeType, psshBox, cdmPsshBox}, // ISO-BMFF, EME passing style
{kIsoBmffMimeType, initData, cdmPsshBox}, // ISO-BMFF, old passing style
{kWebmMimeType, initData, cdmInitData} // WebM
};
size_t testSetCount = sizeof(testSets) / sizeof(TestSet);
// Set up the expected calls. Per gMock rules, this must be done for all test
// sets prior to testing any of them.
{
InSequence calls;
for (size_t i = 0; i < testSetCount; ++i)
{
const char* mimeType = testSets[i].mimeType;
const CdmInitData& initData = testSets[i].initDataOut;
EXPECT_CALL(*cdm, GenerateKeyRequest(cdmSessionId, "", mimeType, initData,
kLicenseTypeOffline, cdmParameters, _,
_, _, _, _))
.WillOnce(DoAll(SetArgPointee<8>(cdmRequest),
SetArgPointee<9>(kKeyRequestTypeInitial),
SetArgPointee<10>(kDefaultUrl),
Return(wvcdm::KEY_MESSAGE)));
EXPECT_CALL(*cdm, GenerateKeyRequest(cdmSessionId, "", mimeType, initData,
kLicenseTypeStreaming, cdmParameters,
_, _, _, _, _))
.WillOnce(DoAll(SetArgPointee<8>(cdmRequest),
SetArgPointee<9>(kKeyRequestTypeRenewal),
SetArgPointee<10>(kDefaultUrl),
Return(wvcdm::KEY_MESSAGE)));
EXPECT_CALL(*cdm, GenerateKeyRequest("", cdmKeySetId, mimeType, initData,
kLicenseTypeRelease, cdmParameters,
NotNull(), StrEq(EMPTY_ORIGIN), _, _,
_))
.WillOnce(DoAll(SetArgPointee<8>(cdmRequest),
SetArgPointee<9>(kKeyRequestTypeRelease),
SetArgPointee<10>(kDefaultUrl),
Return(wvcdm::KEY_MESSAGE)));
}
}
// Performs the actual tests
for (size_t i = 0; i < testSetCount; ++i)
{
const String8 mimeType(testSets[i].mimeType);
const Vector<uint8_t>& initData = testSets[i].initDataIn;
Vector<uint8_t> request;
String8 defaultUrl;
DrmPlugin::KeyRequestType keyRequestType;
status_t res = plugin.getKeyRequest(sessionId, initData, mimeType,
DrmPlugin::kKeyType_Offline,
parameters, request, defaultUrl,
&keyRequestType);
ASSERT_EQ(OK, res);
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
EXPECT_EQ(DrmPlugin::kKeyRequestType_Initial, keyRequestType);
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
res = plugin.getKeyRequest(sessionId, initData, mimeType,
DrmPlugin::kKeyType_Streaming, parameters,
request, defaultUrl, &keyRequestType);
ASSERT_EQ(OK, res);
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
EXPECT_EQ(DrmPlugin::kKeyRequestType_Renewal, keyRequestType);
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
res = plugin.getKeyRequest(keySetId, initData, mimeType,
DrmPlugin::kKeyType_Release, parameters,
request, defaultUrl, &keyRequestType);
ASSERT_EQ(OK, res);
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
EXPECT_EQ(DrmPlugin::kKeyRequestType_Release, keyRequestType);
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
}
}
TEST_F(WVDrmPluginTest, AddsKeys) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const uint32_t kResponseSize = 256;
uint8_t responseRaw[kResponseSize];
static const uint32_t kKeySetIdSize = 32;
uint8_t keySetIdRaw[kKeySetIdSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(responseRaw, sizeof(uint8_t), kResponseSize, fp);
fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp);
fclose(fp);
Vector<uint8_t> response;
response.appendArray(responseRaw, kResponseSize);
memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1);
CdmKeySetId cdmKeySetId(reinterpret_cast<char *>(keySetIdRaw), kKeySetIdSize);
Vector<uint8_t> keySetId;
Vector<uint8_t> emptyKeySetId;
EXPECT_CALL(*cdm, AddKey(cdmSessionId,
ElementsAreArray(responseRaw, kResponseSize), _))
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetId),
Return(wvcdm::KEY_ADDED)));
EXPECT_CALL(*cdm, AddKey("", ElementsAreArray(responseRaw, kResponseSize),
Pointee(cdmKeySetId)))
.Times(1);
status_t res = plugin.provideKeyResponse(sessionId, response, keySetId);
ASSERT_EQ(OK, res);
ASSERT_THAT(keySetId, ElementsAreArray(keySetIdRaw, kKeySetIdSize));
res = plugin.provideKeyResponse(keySetId, response, emptyKeySetId);
ASSERT_EQ(OK, res);
EXPECT_EQ(0u, emptyKeySetId.size());
}
TEST_F(WVDrmPluginTest, HandlesPrivacyCertCaseOfAddKey) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
sp<StrictMock<MockDrmPluginListener> > listener =
new StrictMock<MockDrmPluginListener>();
const CdmClientPropertySet* propertySet = NULL;
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
.WillRepeatedly(DoAll(SetArgPointee<4>(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>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
static const uint32_t kResponseSize = 256;
uint8_t responseRaw[kResponseSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(responseRaw, sizeof(uint8_t), kResponseSize, fp);
fclose(fp);
Vector<uint8_t> response;
response.appendArray(responseRaw, kResponseSize);
Vector<uint8_t> keySetId;
EXPECT_CALL(*listener, sendEvent(DrmPlugin::kDrmPluginEventKeyNeeded, 0,
Pointee(ElementsAreArray(sessionIdRaw,
kSessionIdSize)),
NULL))
.Times(1);
EXPECT_CALL(*cdm, AddKey(_, _, _))
.WillRepeatedly(Return(wvcdm::NEED_KEY));
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
status_t res = plugin.setListener(listener);
ASSERT_EQ(OK, res);
res = plugin.setPropertyString(String8("privacyMode"), String8("enable"));
ASSERT_EQ(OK, res);
EXPECT_TRUE(propertySet->use_privacy_mode());
res = plugin.provideKeyResponse(sessionId, response, keySetId);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, RemovesKeys) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
EXPECT_CALL(*cdm, RemoveKeys(cdmSessionId))
.Times(1);
status_t res = plugin.removeKeys(sessionId);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, RestoresKeys) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const size_t kKeySetIdSize = 32;
uint8_t keySetIdRaw[kKeySetIdSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp);
fclose(fp);
Vector<uint8_t> keySetId;
keySetId.appendArray(keySetIdRaw, kKeySetIdSize);
EXPECT_CALL(*cdm, RestoreKey(cdmSessionId,
ElementsAreArray(keySetIdRaw, kKeySetIdSize)))
.Times(1);
status_t res = plugin.restoreKeys(sessionId, keySetId);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, QueriesKeyStatus) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
KeyedVector<String8, String8> expectedLicenseStatus;
CdmQueryMap cdmLicenseStatus;
expectedLicenseStatus.add(String8("areTheKeysAllRight"), String8("yes"));
cdmLicenseStatus["areTheKeysAllRight"] = "yes";
expectedLicenseStatus.add(String8("isGMockAwesome"), String8("ohhhhhhYeah"));
cdmLicenseStatus["isGMockAwesome"] = "ohhhhhhYeah";
expectedLicenseStatus.add(String8("answer"), String8("42"));
cdmLicenseStatus["answer"] = "42";
EXPECT_CALL(*cdm, QueryKeyStatus(cdmSessionId, _))
.WillOnce(DoAll(SetArgPointee<1>(cdmLicenseStatus),
Return(wvcdm::NO_ERROR)));
KeyedVector<String8, String8> licenseStatus;
status_t res = plugin.queryKeyStatus(sessionId, licenseStatus);
ASSERT_EQ(OK, res);
ASSERT_EQ(expectedLicenseStatus.size(), licenseStatus.size());
for (size_t i = 0; i < expectedLicenseStatus.size(); ++i) {
const String8& key = expectedLicenseStatus.keyAt(i);
EXPECT_NE(android::NAME_NOT_FOUND, licenseStatus.indexOfKey(key));
EXPECT_EQ(expectedLicenseStatus.valueFor(key), licenseStatus.valueFor(key));
}
}
TEST_F(WVDrmPluginTest, GetsProvisioningRequests) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const uint32_t kRequestSize = 256;
uint8_t requestRaw[kRequestSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(requestRaw, sizeof(uint8_t), kRequestSize, fp);
fclose(fp);
CdmProvisioningRequest cdmRequest(requestRaw, requestRaw + kRequestSize);
static const char* kDefaultUrl = "http://google.com/";
EXPECT_CALL(*cdm, GetProvisioningRequest(kCertificateWidevine, IsEmpty(),
EMPTY_ORIGIN, _, _))
.WillOnce(DoAll(SetArgPointee<3>(cdmRequest),
SetArgPointee<4>(kDefaultUrl),
Return(wvcdm::NO_ERROR)));
Vector<uint8_t> request;
String8 defaultUrl;
status_t res = plugin.getProvisionRequest(String8(""), String8(""), request,
defaultUrl);
ASSERT_EQ(OK, res);
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
}
TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const uint32_t kResponseSize = 512;
uint8_t responseRaw[kResponseSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(responseRaw, sizeof(uint8_t), kResponseSize, fp);
fclose(fp);
Vector<uint8_t> response;
response.appendArray(responseRaw, kResponseSize);
EXPECT_CALL(*cdm, HandleProvisioningResponse(EMPTY_ORIGIN,
ElementsAreArray(responseRaw,
kResponseSize),
_, _))
.Times(1);
Vector<uint8_t> cert;
Vector<uint8_t> key;
status_t res = plugin.provideProvisionResponse(response, cert, key);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, UnprovisionsDevice) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, EMPTY_ORIGIN))
.Times(1);
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, EMPTY_ORIGIN))
.Times(1);
status_t res = plugin.unprovisionDevice();
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, MuxesUnprovisioningErrors) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
// Tests that both Unprovisions are called even if one fails. Also tests that
// no matter which fails, the function always propagates the error.
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, EMPTY_ORIGIN))
.WillOnce(Return(wvcdm::UNKNOWN_ERROR))
.WillOnce(Return(wvcdm::NO_ERROR))
.WillOnce(Return(wvcdm::UNKNOWN_ERROR));
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, EMPTY_ORIGIN))
.WillOnce(Return(wvcdm::NO_ERROR))
.WillOnce(Return(wvcdm::UNKNOWN_ERROR))
.WillOnce(Return(wvcdm::UNKNOWN_ERROR));
status_t res = plugin.unprovisionDevice();
ASSERT_NE(OK, res);
res = plugin.unprovisionDevice();
ASSERT_NE(OK, res);
res = plugin.unprovisionDevice();
ASSERT_NE(OK, res);
}
TEST_F(WVDrmPluginTest, UnprovisionsOrigin) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
Vector<uint8_t> cert;
Vector<uint8_t> key;
Vector<uint8_t> specialResponse;
specialResponse.appendArray(kUnprovisionResponse, kUnprovisionResponseSize);
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, StrEq(kOrigin.string())))
.Times(1);
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, StrEq(kOrigin.string())))
.Times(1);
status_t res = plugin.setPropertyString(String8("origin"), kOrigin);
ASSERT_EQ(OK, res);
res = plugin.provideProvisionResponse(specialResponse, cert, key);
EXPECT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, WillNotUnprovisionWithoutOrigin) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
Vector<uint8_t> cert;
Vector<uint8_t> key;
Vector<uint8_t> specialResponse;
specialResponse.appendArray(kUnprovisionResponse, kUnprovisionResponseSize);
EXPECT_CALL(*cdm, Unprovision(_, _))
.Times(0);
status_t res = plugin.provideProvisionResponse(specialResponse, cert, key);
EXPECT_NE(OK, res);
}
TEST_F(WVDrmPluginTest, MuxesOriginUnprovisioningErrors) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
Vector<uint8_t> cert;
Vector<uint8_t> key;
Vector<uint8_t> specialResponse;
specialResponse.appendArray(kUnprovisionResponse, kUnprovisionResponseSize);
// Tests that both Unprovisions are called even if one fails. Also tests that
// no matter which fails, the function always propagates the error.
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, StrEq(kOrigin.string())))
.WillOnce(Return(wvcdm::UNKNOWN_ERROR))
.WillOnce(Return(wvcdm::NO_ERROR))
.WillOnce(Return(wvcdm::UNKNOWN_ERROR));
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, StrEq(kOrigin.string())))
.WillOnce(Return(wvcdm::NO_ERROR))
.WillOnce(Return(wvcdm::UNKNOWN_ERROR))
.WillOnce(Return(wvcdm::UNKNOWN_ERROR));
status_t res = plugin.setPropertyString(String8("origin"), kOrigin);
ASSERT_EQ(OK, res);
res = plugin.provideProvisionResponse(specialResponse, cert, key);
EXPECT_NE(OK, res);
res = plugin.provideProvisionResponse(specialResponse, cert, key);
EXPECT_NE(OK, res);
res = plugin.provideProvisionResponse(specialResponse, cert, key);
EXPECT_NE(OK, res);
}
TEST_F(WVDrmPluginTest, GetsSecureStops) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
const char* app_id = "my_app_id";
plugin.setPropertyString(String8("appId"), String8(app_id));
static const uint32_t kStopSize = 53;
static const uint32_t kStopCount = 7;
uint8_t stopsRaw[kStopCount][kStopSize];
FILE* fp = fopen("/dev/urandom", "r");
for (uint32_t i = 0; i < kStopCount; ++i) {
fread(stopsRaw[i], sizeof(uint8_t), kStopSize, fp);
}
fclose(fp);
CdmUsageInfo cdmStops;
for (uint32_t i = 0; i < kStopCount; ++i) {
cdmStops.push_back(string(stopsRaw[i], stopsRaw[i] + kStopSize));
}
EXPECT_CALL(*cdm, GetUsageInfo(StrEq(app_id), _))
.WillOnce(DoAll(SetArgPointee<1>(cdmStops),
Return(wvcdm::NO_ERROR)));
List<Vector<uint8_t> > stops;
status_t res = plugin.getSecureStops(stops);
ASSERT_EQ(OK, res);
List<Vector<uint8_t> >::iterator iter = stops.begin();
uint32_t rawIter = 0;
while (rawIter < kStopCount && iter != stops.end()) {
EXPECT_THAT(*iter, ElementsAreArray(stopsRaw[rawIter], kStopSize));
++iter;
++rawIter;
}
// Assert that both lists are the same length
EXPECT_EQ(kStopCount, rawIter);
EXPECT_EQ(stops.end(), iter);
}
TEST_F(WVDrmPluginTest, ReleasesAllSecureStops) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
status_t res = plugin.setPropertyString(String8("appId"), String8(""));
ASSERT_EQ(OK, res);
EXPECT_CALL(*cdm, ReleaseAllUsageInfo(StrEq("")))
.Times(1);
res = plugin.releaseAllSecureStops();
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, ReleasesSecureStops) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const uint32_t kMessageSize = 128;
uint8_t messageRaw[kMessageSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(messageRaw, sizeof(uint8_t), kMessageSize, fp);
fclose(fp);
Vector<uint8_t> message;
message.appendArray(messageRaw, kMessageSize);
EXPECT_CALL(*cdm, ReleaseUsageInfo(ElementsAreArray(messageRaw,
kMessageSize)))
.Times(1);
status_t res = plugin.releaseSecureStops(message);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
CdmQueryMap l1Map;
l1Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
CdmQueryMap l3Map;
l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
static const string uniqueId = "The Universe";
CdmQueryMap deviceIDMap;
deviceIDMap[QUERY_KEY_DEVICE_ID] = uniqueId;
static const string systemId = "42";
CdmQueryMap systemIDMap;
systemIDMap[QUERY_KEY_SYSTEM_ID] = systemId;
static const string provisioningId("Life\0&Everything", 16);
CdmQueryMap provisioningIDMap;
provisioningIDMap[QUERY_KEY_PROVISIONING_ID] = provisioningId;
static const string openSessions = "15";
CdmQueryMap openSessionsMap;
openSessionsMap[QUERY_KEY_NUMBER_OF_OPEN_SESSIONS] = openSessions;
static const string maxSessions = "18";
CdmQueryMap maxSessionsMap;
maxSessionsMap[QUERY_KEY_MAX_NUMBER_OF_SESSIONS] = maxSessions;
EXPECT_CALL(*cdm, QueryStatus(_, _))
.WillOnce(DoAll(SetArgPointee<1>(l1Map),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<1>(l3Map),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<1>(deviceIDMap),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<1>(systemIDMap),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<1>(provisioningIDMap),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<1>(openSessionsMap),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<1>(maxSessionsMap),
Return(wvcdm::NO_ERROR)));
String8 stringResult;
Vector<uint8_t> vectorResult;
status_t res = plugin.getPropertyString(String8("vendor"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_STREQ("Google", stringResult.string());
res = plugin.getPropertyString(String8("version"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_STREQ("1.0", stringResult.string());
res = plugin.getPropertyString(String8("description"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_STREQ("Widevine CDM", stringResult.string());
res = plugin.getPropertyString(String8("algorithms"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_STREQ("AES/CBC/NoPadding,HmacSHA256", stringResult.string());
res = plugin.getPropertyString(String8("securityLevel"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L1.c_str(), stringResult.string());
res = plugin.getPropertyString(String8("securityLevel"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L3.c_str(), stringResult.string());
res = plugin.getPropertyByteArray(String8("deviceUniqueId"), vectorResult);
ASSERT_EQ(OK, res);
EXPECT_THAT(vectorResult, ElementsAreArray(uniqueId.data(), uniqueId.size()));
res = plugin.getPropertyString(String8("systemId"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_STREQ(systemId.c_str(), stringResult.string());
res = plugin.getPropertyByteArray(String8("provisioningUniqueId"), vectorResult);
ASSERT_EQ(OK, res);
EXPECT_THAT(vectorResult, ElementsAreArray(provisioningId.data(),
provisioningId.size()));
res = plugin.getPropertyString(String8("numberOfOpenSessions"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_EQ(openSessions, stringResult.string());
res = plugin.getPropertyString(String8("maxNumberOfSessions"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_EQ(maxSessions, stringResult.string());
}
TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
String8 stringResult;
Vector<uint8_t> vectorResult;
status_t res = plugin.getPropertyString(String8("unknownProperty"),
stringResult);
ASSERT_NE(OK, res);
EXPECT_TRUE(stringResult.isEmpty());
res = plugin.getPropertyByteArray(String8("unknownProperty"),
vectorResult);
ASSERT_NE(OK, res);
EXPECT_TRUE(vectorResult.isEmpty());
}
TEST_F(WVDrmPluginTest, DoesNotSetUnknownProperties) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const uint32_t kValueSize = 32;
uint8_t valueRaw[kValueSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(valueRaw, sizeof(uint8_t), kValueSize, fp);
fclose(fp);
Vector<uint8_t> value;
value.appendArray(valueRaw, kValueSize);
status_t res = plugin.setPropertyString(String8("unknownProperty"),
String8("ignored"));
ASSERT_NE(OK, res);
res = plugin.setPropertyByteArray(String8("unknownProperty"), value);
ASSERT_NE(OK, res);
}
TEST_F(WVDrmPluginTest, FailsGenericMethodsWithoutAnAlgorithmSet) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
Vector<uint8_t> keyId;
Vector<uint8_t> input;
Vector<uint8_t> iv;
Vector<uint8_t> output;
bool match;
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
// Note that we do not set the algorithms. This should cause these methods
// to fail.
res = plugin.encrypt(sessionId, keyId, input, iv, output);
EXPECT_EQ(NO_INIT, res);
res = plugin.decrypt(sessionId, keyId, input, iv, output);
EXPECT_EQ(NO_INIT, res);
res = plugin.sign(sessionId, keyId, input, output);
EXPECT_EQ(NO_INIT, res);
res = plugin.verify(sessionId, keyId, input, output, match);
EXPECT_EQ(NO_INIT, res);
}
MATCHER_P(IsIV, iv, "") {
for (size_t i = 0; i < KEY_IV_SIZE; ++i) {
if (iv[i] != arg[i]) {
return false;
}
}
return true;
}
TEST_F(WVDrmPluginTest, CallsGenericEncrypt) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const size_t kDataSize = 256;
uint8_t keyIdRaw[KEY_ID_SIZE];
uint8_t inputRaw[kDataSize];
uint8_t ivRaw[KEY_IV_SIZE];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(inputRaw, sizeof(uint8_t), kDataSize, fp);
fread(ivRaw, sizeof(uint8_t), KEY_IV_SIZE, fp);
fclose(fp);
Vector<uint8_t> keyId;
keyId.appendArray(keyIdRaw, KEY_ID_SIZE);
Vector<uint8_t> input;
input.appendArray(inputRaw, kDataSize);
Vector<uint8_t> iv;
iv.appendArray(ivRaw, KEY_IV_SIZE);
Vector<uint8_t> output;
{
InSequence calls;
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
.Times(1);
EXPECT_CALL(crypto, encrypt(4, _, kDataSize, IsIV(ivRaw),
OEMCrypto_AES_CBC_128_NO_PADDING, _))
.With(Args<1, 2>(ElementsAreArray(inputRaw, kDataSize)))
.Times(1);
}
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
res = plugin.setCipherAlgorithm(sessionId, String8("AES/CBC/NoPadding"));
ASSERT_EQ(OK, res);
res = plugin.encrypt(sessionId, keyId, input, iv, output);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, CallsGenericDecrypt) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const size_t kDataSize = 256;
uint8_t keyIdRaw[KEY_ID_SIZE];
uint8_t inputRaw[kDataSize];
uint8_t ivRaw[KEY_IV_SIZE];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(inputRaw, sizeof(uint8_t), kDataSize, fp);
fread(ivRaw, sizeof(uint8_t), KEY_IV_SIZE, fp);
fclose(fp);
Vector<uint8_t> keyId;
keyId.appendArray(keyIdRaw, KEY_ID_SIZE);
Vector<uint8_t> input;
input.appendArray(inputRaw, kDataSize);
Vector<uint8_t> iv;
iv.appendArray(ivRaw, KEY_IV_SIZE);
Vector<uint8_t> output;
{
InSequence calls;
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
.Times(1);
EXPECT_CALL(crypto, decrypt(4, _, kDataSize, IsIV(ivRaw),
OEMCrypto_AES_CBC_128_NO_PADDING, _))
.With(Args<1, 2>(ElementsAreArray(inputRaw, kDataSize)))
.Times(1);
}
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
res = plugin.setCipherAlgorithm(sessionId, String8("AES/CBC/NoPadding"));
ASSERT_EQ(OK, res);
res = plugin.decrypt(sessionId, keyId, input, iv, output);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, CallsGenericSign) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const size_t kDataSize = 256;
uint8_t keyIdRaw[KEY_ID_SIZE];
uint8_t messageRaw[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(messageRaw, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
Vector<uint8_t> keyId;
keyId.appendArray(keyIdRaw, KEY_ID_SIZE);
Vector<uint8_t> message;
message.appendArray(messageRaw, kDataSize);
Vector<uint8_t> signature;
{
InSequence calls;
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
.Times(1);
EXPECT_CALL(crypto, sign(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
Pointee(0)))
.With(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)))
.WillOnce(DoAll(SetArgPointee<5>(64),
Return(OEMCrypto_ERROR_SHORT_BUFFER)));
EXPECT_CALL(crypto, sign(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
Pointee(64)))
.With(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)))
.Times(1);
}
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
res = plugin.setMacAlgorithm(sessionId, String8("HmacSHA256"));
ASSERT_EQ(OK, res);
res = plugin.sign(sessionId, keyId, message, signature);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, CallsGenericVerify) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
static const size_t kDataSize = 256;
static const size_t kSignatureSize = 16;
uint8_t keyIdRaw[KEY_ID_SIZE];
uint8_t messageRaw[kDataSize];
uint8_t signatureRaw[kSignatureSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(messageRaw, sizeof(uint8_t), kDataSize, fp);
fread(signatureRaw, sizeof(uint8_t), kSignatureSize, fp);
fclose(fp);
Vector<uint8_t> keyId;
keyId.appendArray(keyIdRaw, KEY_ID_SIZE);
Vector<uint8_t> message;
message.appendArray(messageRaw, kDataSize);
Vector<uint8_t> signature;
signature.appendArray(signatureRaw, kSignatureSize);
bool match;
{
InSequence calls;
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
.Times(1);
EXPECT_CALL(crypto, verify(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
kSignatureSize))
.With(AllOf(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)),
Args<4, 5>(ElementsAreArray(signatureRaw, kSignatureSize))))
.WillOnce(Return(OEMCrypto_SUCCESS));
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
.Times(1);
EXPECT_CALL(crypto, verify(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
kSignatureSize))
.With(AllOf(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)),
Args<4, 5>(ElementsAreArray(signatureRaw, kSignatureSize))))
.WillOnce(Return(OEMCrypto_ERROR_SIGNATURE_FAILURE));
}
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
res = plugin.setMacAlgorithm(sessionId, String8("HmacSHA256"));
ASSERT_EQ(OK, res);
res = plugin.verify(sessionId, keyId, message, signature, match);
ASSERT_EQ(OK, res);
EXPECT_TRUE(match);
res = plugin.verify(sessionId, keyId, message, signature, match);
ASSERT_EQ(OK, res);
EXPECT_FALSE(match);
}
TEST_F(WVDrmPluginTest, RegistersForEvents) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, &plugin, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, UnregistersForAllEventsOnDestruction) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
{
WVDrmPlugin plugin(cdm.get(), &crypto);
uint8_t sessionIdRaw1[kSessionIdSize];
uint8_t sessionIdRaw2[kSessionIdSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(sessionIdRaw1, sizeof(uint8_t), kSessionIdSize, fp);
fread(sessionIdRaw2, sizeof(uint8_t), kSessionIdSize, fp);
fclose(fp);
CdmSessionId cdmSessionId1(sessionIdRaw1, sessionIdRaw1 + kSessionIdSize);
CdmSessionId cdmSessionId2(sessionIdRaw2, sessionIdRaw2 + kSessionIdSize);
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId1),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId2),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId1, _))
.WillOnce(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId2, _))
.WillOnce(Invoke(setSessionIdOnMap<5>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
}
}
TEST_F(WVDrmPluginTest, MarshalsEvents) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
sp<StrictMock<MockDrmPluginListener> > listener =
new StrictMock<MockDrmPluginListener>();
const int64_t kExpiryTimeInSeconds = 123456789012LL;
const char kKeyId1[] = "Testing Key1 Id ";
const char kKeyId2[] = "Testing Key2 Id ";
const char kKeyId3[] = "Testing Key3 Id ";
const char kKeyId4[] = "Testing Key4 Id ";
{
InSequence calls;
EXPECT_CALL(*listener,
sendKeysChange(
Pointee(ElementsAreArray(sessionIdRaw, kSessionIdSize)),
Pointee(UnorderedElementsAre(AllOf(
Field(&DrmPlugin::KeyStatus::mKeyId,
ElementsAreArray(kKeyId1, sizeof(kKeyId1) - 1)),
Field(&DrmPlugin::KeyStatus::mType,
DrmPlugin::kKeyStatusType_Expired)))),
false));
EXPECT_CALL(
*listener,
sendEvent(DrmPlugin::kDrmPluginEventKeyExpired, 0,
Pointee(ElementsAreArray(sessionIdRaw, kSessionIdSize)),
NULL));
EXPECT_CALL(
*listener,
sendEvent(DrmPlugin::kDrmPluginEventKeyNeeded, 0,
Pointee(ElementsAreArray(sessionIdRaw, kSessionIdSize)),
NULL));
// Expiry Time in Java API is in milliseconds.
EXPECT_CALL(*listener,
sendExpirationUpdate(
Pointee(ElementsAreArray(sessionIdRaw, kSessionIdSize)),
NEVER_EXPIRES));
EXPECT_CALL(*listener,
sendExpirationUpdate(
Pointee(ElementsAreArray(sessionIdRaw, kSessionIdSize)),
kExpiryTimeInSeconds * 1000));
EXPECT_CALL(
*listener,
sendKeysChange(
Pointee(ElementsAreArray(sessionIdRaw, kSessionIdSize)),
Pointee(UnorderedElementsAre(
AllOf(Field(&DrmPlugin::KeyStatus::mKeyId,
ElementsAreArray(kKeyId1, sizeof(kKeyId1) - 1)),
Field(&DrmPlugin::KeyStatus::mType,
DrmPlugin::kKeyStatusType_Usable)),
AllOf(Field(&DrmPlugin::KeyStatus::mKeyId,
ElementsAreArray(kKeyId2, sizeof(kKeyId2) - 1)),
Field(&DrmPlugin::KeyStatus::mType,
DrmPlugin::kKeyStatusType_OutputNotAllowed)),
AllOf(Field(&DrmPlugin::KeyStatus::mKeyId,
ElementsAreArray(kKeyId3, sizeof(kKeyId3) - 1)),
Field(&DrmPlugin::KeyStatus::mType,
DrmPlugin::kKeyStatusType_InternalError)),
AllOf(Field(&DrmPlugin::KeyStatus::mKeyId,
ElementsAreArray(kKeyId4, sizeof(kKeyId4) - 1)),
Field(&DrmPlugin::KeyStatus::mType,
DrmPlugin::kKeyStatusType_StatusPending)))),
true));
}
status_t res = plugin.setListener(listener);
ASSERT_EQ(OK, res);
CdmKeyStatusMap cdmKeysStatus;
cdmKeysStatus[kKeyId1] = kKeyStatusExpired;
plugin.OnSessionKeysChange(cdmSessionId, cdmKeysStatus, false);
plugin.OnSessionRenewalNeeded(cdmSessionId);
plugin.OnExpirationUpdate(cdmSessionId, NEVER_EXPIRES);
plugin.OnExpirationUpdate(cdmSessionId, kExpiryTimeInSeconds);
cdmKeysStatus[kKeyId1] = kKeyStatusUsable;
cdmKeysStatus[kKeyId2] = kKeyStatusOutputNotAllowed;
cdmKeysStatus[kKeyId3] = kKeyStatusInternalError;
cdmKeysStatus[kKeyId4] = kKeyStatusPending;
plugin.OnSessionKeysChange(cdmSessionId, cdmKeysStatus, true);
}
TEST_F(WVDrmPluginTest, GeneratesProvisioningNeededEvent) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
sp<StrictMock<MockDrmPluginListener> > listener =
new StrictMock<MockDrmPluginListener>();
EXPECT_CALL(*listener, sendEvent(DrmPlugin::kDrmPluginEventProvisionRequired, 0,
Pointee(ElementsAreArray(sessionIdRaw,
kSessionIdSize)),
NULL))
.Times(1);
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
Return(wvcdm::NEED_PROVISIONING)));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
status_t res = plugin.setListener(listener);
ASSERT_EQ(OK, res);
res = plugin.openSession(sessionId);
ASSERT_EQ(ERROR_DRM_NOT_PROVISIONED, res);
}
TEST_F(WVDrmPluginTest, ProvidesExpectedDefaultPropertiesToCdm) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &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<4>(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>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
}
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
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());
EXPECT_STREQ("", propertySet->app_id().c_str());
}
TEST_F(WVDrmPluginTest, CanSetAppId) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
const CdmClientPropertySet* propertySet = NULL;
CdmQueryMap l3Map;
l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
// Provide expected mock behavior
{
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
SaveArg<1>(&propertySet),
Return(wvcdm::NO_ERROR)));
// Provide expected behavior when plugin queries for the security level
EXPECT_CALL(*cdm, QueryStatus(_, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(l3Map),
Return(wvcdm::NO_ERROR)));
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
}
status_t res;
// Test setting an empty string
res = plugin.setPropertyString(String8("appId"), String8(""));
ASSERT_EQ(OK, res);
// Test setting an application id before a session is opened.
res = plugin.setPropertyString(String8("appId"), kAppId);
ASSERT_EQ(OK, res);
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
// Verify application id is set correctly.
EXPECT_STREQ(kAppId, propertySet->app_id().c_str());
// Test setting application id while session is opened, this should fail.
res = plugin.setPropertyString(String8("appId"), kAppId);
ASSERT_EQ(kErrorSessionIsOpen, res);
}
TEST_P(WVDrmPluginOriginTest, CanSetOrigin) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
OriginTestVariant params = GetParam();
// Provide expected mock behavior
{
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(*cdm, QueryKeyControlInfo(cdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Provide expected behavior when plugin closes a session
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
}
// Note which mock calls we expect
EXPECT_CALL(*cdm, OpenSession(_, _, StrEq(params.expectedOrigin), _, _))
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
// Set the properties & run the test
if (!params.origin.isEmpty()) {
ASSERT_EQ(OK, plugin.setPropertyString(String8("origin"), params.origin));
}
EXPECT_EQ(OK, plugin.openSession(sessionId));
// Test setting an origin while sessions are opened. This should fail.
EXPECT_NE(OK, plugin.setPropertyString(String8("origin"), kOrigin));
EXPECT_EQ(OK, plugin.closeSession(sessionId));
}
INSTANTIATE_TEST_CASE_P(OriginTests, WVDrmPluginOriginTest, Values(
OriginTestVariant("No Origin", kEmptyString, EMPTY_ORIGIN),
OriginTestVariant("With an Origin", kOrigin, kOrigin.string())));
TEST_F(WVDrmPluginTest, CanSetSecurityLevel) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
const CdmClientPropertySet* propertySet = NULL;
CdmQueryMap l1Map;
l1Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
CdmQueryMap l3Map;
l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
EXPECT_CALL(*cdm, QueryStatus(_, _))
.WillOnce(DoAll(SetArgPointee<1>(l3Map),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<1>(l1Map),
Return(wvcdm::NO_ERROR)));
// Provide expected mock behavior
{
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
.WillRepeatedly(DoAll(SetArgPointee<4>(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>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
}
status_t res;
// Test forcing L3
res = plugin.setPropertyString(String8("securityLevel"), String8("L3"));
ASSERT_EQ(OK, res);
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
EXPECT_STREQ("L3", propertySet->security_level().c_str());
plugin.closeSession(sessionId);
// Test returning to L1 on an L3 device (Should Fail)
res = plugin.setPropertyString(String8("securityLevel"), String8("L1"));
ASSERT_NE(OK, res);
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
EXPECT_STREQ("L3", propertySet->security_level().c_str());
plugin.closeSession(sessionId);
// Test returning to L1 on an L1 device
res = plugin.setPropertyString(String8("securityLevel"), String8("L1"));
ASSERT_EQ(OK, res);
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
EXPECT_STREQ("", propertySet->security_level().c_str());
plugin.closeSession(sessionId);
// Test un-forcing a level (first forcing to L3 so we have something to reset)
res = plugin.setPropertyString(String8("securityLevel"), String8("L3"));
ASSERT_EQ(OK, res);
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
EXPECT_STREQ("L3", propertySet->security_level().c_str());
plugin.closeSession(sessionId);
res = plugin.setPropertyString(String8("securityLevel"), String8(""));
ASSERT_EQ(OK, res);
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
EXPECT_STREQ("", propertySet->security_level().c_str());
plugin.closeSession(sessionId);
// Test nonsense (Should Fail)
res = plugin.setPropertyString(String8("securityLevel"), String8("nonsense"));
ASSERT_NE(OK, res);
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
EXPECT_STREQ("", propertySet->security_level().c_str());
plugin.closeSession(sessionId);
// Test attempting to force a level with a session open (Should Fail)
plugin.openSession(sessionId);
res = plugin.setPropertyString(String8("securityLevel"), String8("L3"));
ASSERT_NE(OK, res);
}
TEST_F(WVDrmPluginTest, CanSetPrivacyMode) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &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<4>(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>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
}
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
status_t res;
// Test turning on privacy mode
res = plugin.setPropertyString(String8("privacyMode"), String8("enable"));
ASSERT_EQ(OK, res);
EXPECT_TRUE(propertySet->use_privacy_mode());
// Test turning off privacy mode
res = plugin.setPropertyString(String8("privacyMode"), String8("disable"));
ASSERT_EQ(OK, res);
EXPECT_FALSE(propertySet->use_privacy_mode());
// Test nonsense (Should Fail)
res = plugin.setPropertyString(String8("privacyMode"), String8("nonsense"));
ASSERT_NE(OK, res);
}
TEST_F(WVDrmPluginTest, CanSetServiceCertificate) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &crypto);
const CdmClientPropertySet* propertySet = NULL;
static const size_t kPrivacyCertSize = 256;
uint8_t privacyCertRaw[kPrivacyCertSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(privacyCertRaw, sizeof(uint8_t), kPrivacyCertSize, fp);
fclose(fp);
Vector<uint8_t> privacyCert;
privacyCert.appendArray(privacyCertRaw, kPrivacyCertSize);
std::string strPrivacyCert(reinterpret_cast<char*>(privacyCertRaw),
kPrivacyCertSize);
Vector<uint8_t> emptyVector;
// Provide expected mock behavior
{
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
.WillRepeatedly(DoAll(SetArgPointee<4>(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>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
}
// Validate that the certificate is validated. Accept it once and reject it
// once. Note that there is no expected call for when the certificate is
// cleared.
EXPECT_CALL(*cdm, IsValidServiceCertificate(strPrivacyCert))
.WillOnce(Return(true))
.WillOnce(Return(false));
plugin.openSession(sessionId);
ASSERT_THAT(propertySet, NotNull());
status_t res;
// Test setting a certificate
res = plugin.setPropertyByteArray(String8("serviceCertificate"), privacyCert);
ASSERT_EQ(OK, res);
EXPECT_THAT(propertySet->service_certificate(),
ElementsAreArray(privacyCertRaw, kPrivacyCertSize));
// Test clearing a certificate
res = plugin.setPropertyByteArray(String8("serviceCertificate"), emptyVector);
ASSERT_EQ(OK, res);
EXPECT_EQ(0u, propertySet->service_certificate().size());
// Test setting a certificate and having it fail
res = plugin.setPropertyByteArray(String8("serviceCertificate"), privacyCert);
ASSERT_NE(OK, res);
EXPECT_EQ(0u, propertySet->service_certificate().size());
}
TEST_F(WVDrmPluginTest, CanSetSessionSharing) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &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<4>(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>));
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) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
WVDrmPlugin plugin(cdm.get(), &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<4>(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>));
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());
}