// // Copyright 2013 Google Inc. All Rights Reserved. // #include #include #include #include #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("unprovision"); const size_t kUnprovisionResponseSize = 11; // This is a serialized MetricsGroup message containing a small amount of // sample data. This ensures we're able to extract it via a property. const char kSerializedMetrics[] = { 0x0a, 0x0a, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x02, 0x08, 0x00, 0x0a, 0x12, 0x0a, 0x05, 0x74, 0x65, 0x73, 0x74, 0x32, 0x12, 0x09, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x15, 0x0a, 0x05, 0x74, 0x65, 0x73, 0x74, 0x33, 0x12, 0x0c, 0x1a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65}; } // anonymous namespace class MockCDM : public WvContentDecryptionModule { public: MOCK_METHOD5(OpenSession, CdmResponseType(const CdmKeySystem&, CdmClientPropertySet*, const CdmIdentifier&, WvCdmEventListener*, CdmSessionId*)); MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&)); MOCK_METHOD9(GenerateKeyRequest, CdmResponseType(const CdmSessionId&, const CdmKeySetId&, const std::string&, const CdmInitData&, const CdmLicenseType, CdmAppParameterMap&, CdmClientPropertySet*, const CdmIdentifier&, CdmKeyRequest*)); MOCK_METHOD3(AddKey, CdmResponseType(const CdmSessionId&, const CdmKeyResponse&, CdmKeySetId*)); MOCK_METHOD1(RemoveKeys, CdmResponseType(const CdmSessionId&)); MOCK_METHOD2(RestoreKey, CdmResponseType(const CdmSessionId&, const CdmKeySetId&)); MOCK_METHOD3(QueryStatus, CdmResponseType(SecurityLevel, const std::string&, std::string*)); MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&, CdmQueryMap*)); MOCK_METHOD2(QueryOemCryptoSessionId, CdmResponseType(const CdmSessionId&, CdmQueryMap*)); MOCK_METHOD5(GetProvisioningRequest, CdmResponseType(CdmCertificateType, const std::string&, const CdmIdentifier&, CdmProvisioningRequest*, std::string*)); MOCK_METHOD4(HandleProvisioningResponse, CdmResponseType(const CdmIdentifier&, CdmProvisioningResponse&, std::string*, std::string*)); MOCK_METHOD2(Unprovision, CdmResponseType(CdmSecurityLevel, const CdmIdentifier&)); MOCK_METHOD3(GetUsageInfo, CdmResponseType(const std::string&, const CdmIdentifier&, CdmUsageInfo*)); MOCK_METHOD4(GetUsageInfo, CdmResponseType(const std::string&, const CdmSecureStopId&, const CdmIdentifier&, CdmUsageInfo*)); MOCK_METHOD2(ReleaseAllUsageInfo, CdmResponseType(const std::string&, const CdmIdentifier&)); MOCK_METHOD2(ReleaseUsageInfo, CdmResponseType(const CdmUsageInfoReleaseMessage&, const CdmIdentifier&)); MOCK_METHOD1(IsValidServiceCertificate, bool(const std::string&)); MOCK_METHOD1(GetSerializedMetrics, void(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*, const Vector*)); MOCK_METHOD2(sendExpirationUpdate, void(const Vector*, int64_t)); MOCK_METHOD3(sendKeysChange, void(const Vector*, const Vector*, bool)); }; template CdmResponseType setSessionIdOnMap(Unused, CdmQueryMap* map) { static const char oecId[] = {DIGIT + '0', '\0'}; (*map)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = oecId; return wvcdm::NO_ERROR; } MATCHER_P(HasOrigin, origin, "") { return arg.origin == origin; } class WVDrmPluginTest : public Test { protected: static const uint32_t kSessionIdSize = 16; uint8_t sessionIdRaw[kSessionIdSize]; Vector 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::Set(wvcdm::NO_ERROR); DefaultValue::Set(OEMCrypto_SUCCESS); DefaultValue::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 {}; TEST_F(WVDrmPluginTest, OpensSessions) { android::sp> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, HasOrigin(EMPTY_ORIGIN), _, _)) .WillOnce(DoAll(SetArgPointee<4>(cdmSessionId), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(*cdm, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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, ClosesSessionWithError) { android::sp> cdm = new StrictMock(); StrictMock 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(ERROR_DRM_SESSION_NOT_OPENED, res); } TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { android::sp> cdm = new StrictMock(); StrictMock 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(keySetIdRaw), kKeySetIdSize); Vector keySetId; keySetId.appendArray(keySetIdRaw, kKeySetIdSize); CdmInitData cdmInitData(reinterpret_cast(initDataRaw), kInitDataSize); Vector 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(psshBoxRaw), kPsshBoxSize); Vector psshBox; psshBox.appendArray(psshBoxRaw, kPsshBoxSize); CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize); KeyedVector 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& 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; CdmKeyRequest initialRequest = { cdmRequest, kKeyRequestTypeInitial, kDefaultUrl }; CdmKeyRequest renewalRequest = { cdmRequest, kKeyRequestTypeRenewal, kDefaultUrl }; CdmKeyRequest releaseRequest = { cdmRequest, kKeyRequestTypeRelease, kDefaultUrl }; EXPECT_CALL(*cdm, GenerateKeyRequest(cdmSessionId, "", mimeType, initData, kLicenseTypeOffline, cdmParameters, NotNull(), HasOrigin(EMPTY_ORIGIN), _)) .WillOnce(DoAll(SetArgPointee<8>(initialRequest), Return(wvcdm::KEY_MESSAGE))); EXPECT_CALL(*cdm, GenerateKeyRequest(cdmSessionId, "", mimeType, initData, kLicenseTypeStreaming, cdmParameters, NotNull(), HasOrigin(EMPTY_ORIGIN), _)) .WillOnce(DoAll(SetArgPointee<8>(renewalRequest), Return(wvcdm::KEY_MESSAGE))); EXPECT_CALL(*cdm, GenerateKeyRequest("", cdmKeySetId, mimeType, initData, kLicenseTypeRelease, cdmParameters, NotNull(), HasOrigin(EMPTY_ORIGIN), _)) .WillOnce(DoAll(SetArgPointee<8>(releaseRequest), Return(wvcdm::KEY_MESSAGE))); } } // Performs the actual tests for (size_t i = 0; i < testSetCount; ++i) { const String8 mimeType(testSets[i].mimeType); const Vector& initData = testSets[i].initDataIn; Vector 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> cdm = new StrictMock(); StrictMock 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 response; response.appendArray(responseRaw, kResponseSize); memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1); CdmKeySetId cdmKeySetId(reinterpret_cast(keySetIdRaw), kKeySetIdSize); Vector keySetId; Vector 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> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); sp > listener = new StrictMock(); 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, QueryOemCryptoSessionId(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 response; response.appendArray(responseRaw, kResponseSize); Vector 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> cdm = new StrictMock(); StrictMock 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> cdm = new StrictMock(); StrictMock 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 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> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); KeyedVector 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 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> cdm = new StrictMock(); StrictMock 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(), HasOrigin(EMPTY_ORIGIN), _, _)) .WillOnce(DoAll(SetArgPointee<3>(cdmRequest), SetArgPointee<4>(kDefaultUrl), Return(wvcdm::NO_ERROR))); Vector 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> cdm = new StrictMock(); StrictMock 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 response; response.appendArray(responseRaw, kResponseSize); EXPECT_CALL(*cdm, HandleProvisioningResponse(HasOrigin(EMPTY_ORIGIN), ElementsAreArray(responseRaw, kResponseSize), _, _)) .Times(1); Vector cert; Vector key; status_t res = plugin.provideProvisionResponse(response, cert, key); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, UnprovisionsDevice) { android::sp> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN))) .Times(1); EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN))) .Times(1); status_t res = plugin.unprovisionDevice(); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, MuxesUnprovisioningErrors) { android::sp> cdm = new StrictMock(); StrictMock 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, HasOrigin(EMPTY_ORIGIN))) .WillOnce(Return(wvcdm::UNKNOWN_ERROR)) .WillOnce(Return(wvcdm::NO_ERROR)) .WillOnce(Return(wvcdm::UNKNOWN_ERROR)); EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(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> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); Vector cert; Vector key; Vector specialResponse; specialResponse.appendArray(kUnprovisionResponse, kUnprovisionResponseSize); EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, HasOrigin(kOrigin.string()))) .Times(1); EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(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> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); Vector cert; Vector key; Vector 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> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); Vector cert; Vector key; Vector 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, HasOrigin(kOrigin.string()))) .WillOnce(Return(wvcdm::UNKNOWN_ERROR)) .WillOnce(Return(wvcdm::NO_ERROR)) .WillOnce(Return(wvcdm::UNKNOWN_ERROR)); EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(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> cdm = new StrictMock(); StrictMock 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<2>(cdmStops), Return(wvcdm::NO_ERROR))); List > stops; status_t res = plugin.getSecureStops(stops); ASSERT_EQ(OK, res); List >::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> cdm = new StrictMock(); StrictMock 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> cdm = new StrictMock(); StrictMock 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 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> cdm = new StrictMock(); StrictMock 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"; static const string systemId = "42"; static const string provisioningId("Life\0&Everything", 16); static const string openSessions = "15"; static const string maxSessions = "18"; static const string oemCryptoApiVersion = "10"; static const string currentSRMVersion = "1"; string serializedMetrics(kSerializedMetrics, kSerializedMetrics + sizeof(kSerializedMetrics)); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), Return(wvcdm::NO_ERROR))) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _)) .WillOnce(DoAll(SetArgPointee<2>(uniqueId), Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SYSTEM_ID, _)) .WillOnce(DoAll(SetArgPointee<2>(systemId), Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_PROVISIONING_ID, _)) .WillOnce(DoAll(SetArgPointee<2>(provisioningId), Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_NUMBER_OF_OPEN_SESSIONS, _)) .WillOnce(DoAll(SetArgPointee<2>(openSessions), Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_MAX_NUMBER_OF_SESSIONS, _)) .WillOnce(DoAll(SetArgPointee<2>(maxSessions), Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_API_VERSION, _)) .WillOnce(DoAll(SetArgPointee<2>(oemCryptoApiVersion), Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SRM_UPDATE_SUPPORT, _)) .WillOnce(DoAll(SetArgPointee<2>("True"), testing::Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_CURRENT_SRM_VERSION, _)) .WillOnce(DoAll(SetArgPointee<2>(currentSRMVersion), testing::Return(wvcdm::NO_ERROR))); EXPECT_CALL(*cdm, GetSerializedMetrics(_)) .WillOnce(SetArgPointee<0>(serializedMetrics)); String8 stringResult; Vector 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()); res = plugin.getPropertyString(String8("oemCryptoApiVersion"), stringResult); ASSERT_EQ(OK, res); EXPECT_STREQ(oemCryptoApiVersion.c_str(), stringResult.string()); res = plugin.getPropertyString(String8("SRMUpdateSupport"), stringResult); ASSERT_EQ(OK, res); EXPECT_STREQ("True", stringResult.string()); res = plugin.getPropertyString(String8("CurrentSRMVersion"), stringResult); ASSERT_EQ(OK, res); EXPECT_STREQ(currentSRMVersion.c_str(), stringResult.string()); vectorResult.clear(); res = plugin.getPropertyByteArray(String8("metrics"), vectorResult); ASSERT_EQ(OK, res); EXPECT_THAT(vectorResult, ElementsAreArray(serializedMetrics.data(), serializedMetrics.size())); } TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) { android::sp> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); String8 stringResult; Vector 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> cdm = new StrictMock(); StrictMock 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 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> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); Vector keyId; Vector input; Vector iv; Vector 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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 keyId; keyId.appendArray(keyIdRaw, KEY_ID_SIZE); Vector input; input.appendArray(inputRaw, kDataSize); Vector iv; iv.appendArray(ivRaw, KEY_IV_SIZE); Vector 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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 keyId; keyId.appendArray(keyIdRaw, KEY_ID_SIZE); Vector input; input.appendArray(inputRaw, kDataSize); Vector iv; iv.appendArray(ivRaw, KEY_IV_SIZE); Vector 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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 keyId; keyId.appendArray(keyIdRaw, KEY_ID_SIZE); Vector message; message.appendArray(messageRaw, kDataSize); Vector 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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 keyId; keyId.appendArray(keyIdRaw, KEY_ID_SIZE); Vector message; message.appendArray(messageRaw, kDataSize); Vector 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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, QueryOemCryptoSessionId(cdmSessionId1, _)) .WillOnce(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*cdm, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); sp > listener = new StrictMock(); 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> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); sp > listener = new StrictMock(); 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> cdm = new StrictMock(); StrictMock 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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 queries for the security level EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(*cdm, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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, QueryOemCryptoSessionId(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(_, _, HasOrigin(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> cdm = new StrictMock(); StrictMock crypto; WVDrmPlugin plugin(cdm.get(), &crypto); const CdmClientPropertySet* propertySet = NULL; EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), Return(wvcdm::NO_ERROR))) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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 privacyCert; privacyCert.appendArray(privacyCertRaw, kPrivacyCertSize); std::string strPrivacyCert(reinterpret_cast(privacyCertRaw), kPrivacyCertSize); Vector 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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, QueryOemCryptoSessionId(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> cdm = new StrictMock(); StrictMock 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, QueryOemCryptoSessionId(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()); }