// // Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. // // #define LOG_NDEBUG 0 #define LOG_TAG "WVDrmPluginHalTest" #include #include #include #include #include #include #include "WVDrmPlugin.h" #include "WVErrors.h" #include "cdm_client_property_set.h" #include "cdm_random.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "media/stagefright/MediaErrors.h" #include "media/stagefright/foundation/ABase.h" #include "string_conversions.h" #include "wv_cdm_constants.h" #include "wv_cdm_types.h" #include "wv_content_decryption_module.h" namespace { typedef std::vector<::aidl::android::hardware::drm::KeyValue> KeyedVector; typedef std::vector SessionId; } // namespace namespace wvdrm { namespace hardware { namespace drm { namespace widevine { using ::aidl::android::hardware::drm::EventType; using ::aidl::android::hardware::drm::KeyRequest; using ::aidl::android::hardware::drm::KeyRequestType; using ::aidl::android::hardware::drm::KeySetId; using ::aidl::android::hardware::drm::KeyStatus; using ::aidl::android::hardware::drm::KeyStatusType; using ::aidl::android::hardware::drm::KeyType; using ::aidl::android::hardware::drm::KeyValue; using ::aidl::android::hardware::drm::OfflineLicenseState; using ::aidl::android::hardware::drm::ProvideProvisionResponseResult; using ::aidl::android::hardware::drm::ProvisionRequest; using ::aidl::android::hardware::drm::SecureStop; using ::aidl::android::hardware::drm::SecureStopId; using ::aidl::android::hardware::drm::SecurityLevel; using ::aidl::android::hardware::drm::Status; using ::testing::_; using ::testing::AllOf; using ::testing::Args; using ::testing::AtLeast; using ::testing::Contains; using ::testing::DefaultValue; using ::testing::DoAll; using ::testing::ElementsAreArray; using ::testing::Field; using ::testing::InSequence; using ::testing::Invoke; using ::testing::IsEmpty; using ::testing::Matcher; using ::testing::NotNull; using ::testing::Pointee; using ::testing::SaveArg; using ::testing::SetArgPointee; using ::testing::SetArrayArgument; using ::testing::StrEq; using ::testing::StrictMock; using ::testing::Test; using ::testing::Values; using ::testing::WithParamInterface; using ::testing::internal::ElementsAreArrayMatcher; using wvcdm::CdmAppParameterMap; using wvcdm::CdmCertificateType; using wvcdm::CdmClientPropertySet; using wvcdm::CdmIdentifier; using wvcdm::CdmInitData; using wvcdm::CdmKeyMessage; using wvcdm::CdmKeyRequest; using wvcdm::CdmKeyResponse; using wvcdm::CdmKeySetId; using wvcdm::CdmKeySystem; using wvcdm::CdmLicenseType; using wvcdm::CdmOfflineLicenseState; using wvcdm::CdmProvisioningRequest; using wvcdm::CdmProvisioningResponse; using wvcdm::CdmQueryMap; using wvcdm::CdmSecureStopId; using wvcdm::CdmSecurityLevel; using wvcdm::CdmUsageReport; using wvcdm::CdmUsageReportList; using wvcdm::EMPTY_ORIGIN; using wvcdm::kCertificateWidevine; using wvcdm::KEY_ID_SIZE; using wvcdm::KEY_IV_SIZE; using wvcdm::KEY_SET_ID_PREFIX; using wvcdm::kKeyRequestTypeInitial; using wvcdm::kKeyRequestTypeRelease; using wvcdm::kKeyRequestTypeRenewal; using wvcdm::kLicenseTypeOffline; using wvcdm::kLicenseTypeRelease; using wvcdm::kLicenseTypeStreaming; using wvcdm::kSecurityLevelL1; using wvcdm::kSecurityLevelL3; using wvcdm::NEVER_EXPIRES; using wvcdm::QUERY_KEY_ANALOG_OUTPUT_CAPABILITIES; using wvcdm::QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT; using wvcdm::QUERY_KEY_CURRENT_HDCP_LEVEL; using wvcdm::QUERY_KEY_CURRENT_SRM_VERSION; using wvcdm::QUERY_KEY_DECRYPT_HASH_SUPPORT; using wvcdm::QUERY_KEY_DEVICE_ID; using wvcdm::QUERY_KEY_MAX_HDCP_LEVEL; using wvcdm::QUERY_KEY_MAX_NUMBER_OF_SESSIONS; using wvcdm::QUERY_KEY_MAX_USAGE_TABLE_ENTRIES; using wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS; using wvcdm::QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION; using wvcdm::QUERY_KEY_OEMCRYPTO_API_VERSION; using wvcdm::QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION; using wvcdm::QUERY_KEY_OEMCRYPTO_SESSION_ID; using wvcdm::QUERY_KEY_PRODUCTION_READY; using wvcdm::QUERY_KEY_PROVISIONING_ID; using wvcdm::QUERY_KEY_PROVISIONING_MODEL; using wvcdm::QUERY_KEY_RESOURCE_RATING_TIER; using wvcdm::QUERY_KEY_SECURITY_LEVEL; using wvcdm::QUERY_KEY_SRM_UPDATE_SUPPORT; using wvcdm::QUERY_KEY_SYSTEM_ID; using wvcdm::QUERY_KEY_USAGE_SUPPORT; using wvcdm::QUERY_KEY_WATERMARKING_SUPPORT; using wvcdm::QUERY_KEY_WVCDM_VERSION; using wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1; using wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3; using wvcdm::SESSION_ID_PREFIX; using wvcdm::WvCdmEventListener; using wvutil::Base64Encode; using wvutil::CdmRandom; namespace { const std::string kEmptyString; const std::string kOrigin("widevine.com"); const std::string kAppId("com.unittest.mock.app.id"); const uint8_t *const kUnprovisionResponse = reinterpret_cast("unprovision"); const size_t kUnprovisionResponseSize = 11; const std::string kDeviceId("0123456789\0ABCDEF", 17); // This is a serialized WvCdmMetrics message containing a small amount of // sample data. This ensures we're able to extract it via a property. const char kSerializedMetricsHex[] = "0a580a001a0210072202100d2a02100832182216636f6d2e676f6f676c652e616e64726f69" "642e676d734208220631342e302e304a06080112020800520610d5f3fad5056a0b1d00fd4c" "47280132020804a2010608011202080012cb010a0622047369643412b5010a021001520919" "1d5a643bdfff50405a0b1d00d01945280132021001620d1d00f8e84528013204080020006a" "0310b739820102100d8a01060801120248009a010310ff01da0106080112024800e2010e1d" "005243472801320528800248009a020d1d00b016452801320428404800a202060801120248" "19aa0206080212024800b2020b1d8098f047280132024800ba02021001ca020b1d00101945" "280132024800e202021004fa02021002a203021000b2030210021a09196891ed7c3f35504" "0"; #define N_ELEM(a) (sizeof(a) / sizeof(a[0])) class MockCDM : public WvContentDecryptionModule { public: virtual ~MockCDM() {} MOCK_METHOD(CdmResponseType, OpenSession, (const CdmKeySystem &, CdmClientPropertySet *, const CdmIdentifier &, WvCdmEventListener *, CdmSessionId *), (override)); MOCK_METHOD(CdmResponseType, CloseSession, (const CdmSessionId &), (override)); MOCK_METHOD(CdmResponseType, GenerateKeyRequest, (const CdmSessionId &, const CdmKeySetId &, const std::string &, const CdmInitData &, const CdmLicenseType, CdmAppParameterMap &, CdmClientPropertySet *, const CdmIdentifier &, CdmKeyRequest *), (override)); MOCK_METHOD(CdmResponseType, AddKey, (const CdmSessionId &, const CdmKeyResponse &, CdmKeySetId *), (override)); MOCK_METHOD(CdmResponseType, RemoveKeys, (const CdmSessionId &), (override)); MOCK_METHOD(CdmResponseType, RestoreKey, (const CdmSessionId &, const CdmKeySetId &), (override)); MOCK_METHOD(CdmResponseType, QueryStatus, (wvcdm::RequestedSecurityLevel, const std::string &, std::string *), (override)); MOCK_METHOD(CdmResponseType, QuerySessionStatus, (const CdmSessionId &, CdmQueryMap *), (override)); MOCK_METHOD(CdmResponseType, QueryKeyStatus, (const CdmSessionId &, CdmQueryMap *), (override)); MOCK_METHOD(CdmResponseType, QueryOemCryptoSessionId, (const CdmSessionId &, CdmQueryMap *), (override)); MOCK_METHOD(CdmResponseType, GetProvisioningRequest, (CdmCertificateType, const std::string &, const CdmIdentifier &, const std::string &, wvcdm::RequestedSecurityLevel, CdmProvisioningRequest *, std::string *), (override)); MOCK_METHOD(CdmResponseType, HandleProvisioningResponse, (const CdmIdentifier &, const CdmProvisioningResponse &, wvcdm::RequestedSecurityLevel, std::string *, std::string *), (override)); MOCK_METHOD(CdmResponseType, Unprovision, (CdmSecurityLevel, const CdmIdentifier &), (override)); MOCK_METHOD(bool, IsProvisioned, (CdmSecurityLevel, const std::string &, const std::string &, bool), (override)); MOCK_METHOD(CdmResponseType, GetUsageInfo, (const std::string &, const CdmIdentifier &, CdmUsageReportList *), (override)); MOCK_METHOD(CdmResponseType, GetUsageInfo, (const std::string &, const CdmSecureStopId &, const CdmIdentifier &, CdmUsageReport *), (override)); MOCK_METHOD(CdmResponseType, RemoveAllUsageInfo, (const std::string &, const CdmIdentifier &), (override)); MOCK_METHOD(CdmResponseType, ReleaseUsageInfo, (const CdmKeyResponse &, const CdmIdentifier &), (override)); MOCK_METHOD(bool, IsValidServiceCertificate, (const std::string &), (override)); MOCK_METHOD(CdmResponseType, GetCurrentMetrics, (const CdmIdentifier &, drm_metrics::WvCdmMetrics *), (override)); MOCK_METHOD(CdmResponseType, GetDecryptHashError, (const CdmSessionId &, std::string *), (override)); MOCK_METHOD(CdmResponseType, ListStoredLicenses, (CdmSecurityLevel, const CdmIdentifier &, std::vector *), (override)); MOCK_METHOD(CdmResponseType, GetOfflineLicenseState, (const std::string &, CdmSecurityLevel, const CdmIdentifier &, CdmOfflineLicenseState *), (override)); MOCK_METHOD(CdmResponseType, RemoveOfflineLicense, (const std::string &, CdmSecurityLevel, const CdmIdentifier &), (override)); MOCK_METHOD(CdmResponseType, StoreAtscLicense, (const CdmIdentifier &, wvcdm::RequestedSecurityLevel, const std::string &, const std::string &), (override)); // Generic crypto API. MOCK_METHOD(CdmResponseType, GenericEncrypt, (const CdmSessionId &, const wvcdm::KeyId &, const std::string &, const std::string &, wvcdm::CdmEncryptionAlgorithm, std::string *)); MOCK_METHOD(CdmResponseType, GenericDecrypt, (const CdmSessionId &, const wvcdm::KeyId &, const std::string &, const std::string &, wvcdm::CdmEncryptionAlgorithm, std::string *)); MOCK_METHOD(CdmResponseType, GenericSign, (const CdmSessionId &, const wvcdm::KeyId &, const std::string &, wvcdm::CdmSigningAlgorithm, std::string *)); MOCK_METHOD(CdmResponseType, GenericVerify, (const CdmSessionId &, const wvcdm::KeyId &, const std::string &, wvcdm::CdmSigningAlgorithm, const std::string &)); }; class MockCrypto : public WVGenericCryptoInterface { public: MOCK_METHOD(OEMCryptoResult, loadDeviceRSAKey, (OEMCrypto_SESSION, const uint8_t *, size_t), (override)); }; } // anonymous namespace template CdmResponseType setSessionIdOnMap(testing::Unused, CdmQueryMap *map) { static const char oecId[] = {DIGIT + '0', '\0'}; (*map)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = oecId; return CdmResponseType(wvcdm::NO_ERROR); } MATCHER_P(HasOrigin, origin, "") { return arg.origin == origin; } class WVDrmPluginHalTest : public Test { protected: static const uint32_t kKeySetIdSize = 32; static const uint32_t kSessionIdSize = 16; std::vector sessionId; uint8_t keySetIdRaw[kKeySetIdSize]; uint8_t sessionIdRaw[kSessionIdSize]; CdmSessionId mCdmSessionId; KeySetId keySetId; android::sp> mCdm = nullptr; std::shared_ptr mPlugin = nullptr; std::string mAppPackageName; StrictMock mCrypto; public: void SetUp() override { // Fill the session ID FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp); memcpy(sessionIdRaw, SESSION_ID_PREFIX, sizeof(SESSION_ID_PREFIX) - 1); sessionId.assign(sessionIdRaw, sessionIdRaw + kSessionIdSize); mCdmSessionId.assign(sessionId.begin(), sessionId.end()); fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); fclose(fp); memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX)); CdmKeySetId mCdmKeySetId(reinterpret_cast(keySetIdRaw), kKeySetIdSize); keySetId.keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize); // Set default return values for gMock DefaultValue::Set(CdmResponseType(wvcdm::NO_ERROR)); DefaultValue::Set(OEMCrypto_SUCCESS); DefaultValue::Set(true); mCdm = new StrictMock(); ASSERT_TRUE(mCdm) << "Failed to create mocked CDM"; mPlugin = ::ndk::SharedRefBase::make( mCdm.get(), mAppPackageName.c_str(), &mCrypto, false); ASSERT_TRUE(mPlugin) << "Failed to create drm plugin"; } void TearDown() override { if (mCdm) mCdm.clear(); if (mPlugin) mPlugin.reset(); } }; struct OriginTestVariant { // For a test that does not expect any follow-up queries OriginTestVariant(const std::string &nameValue, const std::string &originValue, const std::string &expectedOriginValue) : name(nameValue), origin(originValue), expectedOrigin(expectedOriginValue) {} const std::string name; const std::string origin; const std::string expectedOrigin; }; void PrintTo(const OriginTestVariant ¶m, ::std::ostream *os) { *os << param.name << " Variant"; } class WVDrmPluginHalOriginTest : public WVDrmPluginHalTest, public WithParamInterface { }; TEST_F(WVDrmPluginHalTest, OpensSessions) { const CdmClientPropertySet *propertySet = nullptr; // Provide expected mock behavior EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); CdmQueryMap securityLevelQueryMap; securityLevelQueryMap[wvcdm::QUERY_KEY_SECURITY_LEVEL] = wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3; EXPECT_CALL(*mCdm, QuerySessionStatus(_, NotNull())) .WillOnce(DoAll(SetArgPointee<1>(securityLevelQueryMap), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); auto ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L1")); EXPECT_TRUE(ret.isOk()); SessionId sessionId; ret = mPlugin->openSession(SecurityLevel::SW_SECURE_CRYPTO, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_THAT(sessionId, ElementsAreArray(sessionIdRaw, kSessionIdSize)); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, ClosesSessions) { EXPECT_CALL(*mCdm, CloseSession(mCdmSessionId)).Times(1); auto ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, ClosesSessionWithError) { EXPECT_CALL(*mCdm, CloseSession(mCdmSessionId)) .WillOnce(testing::Return(CdmResponseType(wvcdm::SESSION_NOT_FOUND_1))); auto ret = mPlugin->closeSession(sessionId); EXPECT_EQ(static_cast(Status::ERROR_DRM_SESSION_NOT_OPENED), ret.getServiceSpecificError()); } // TODO b/35325611 Fix this disabled test TEST_F(WVDrmPluginHalTest, DISABLED_GeneratesKeyRequests) { 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"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; 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 mCdmKeySetId(reinterpret_cast(keySetIdRaw), kKeySetIdSize); std::vector keySetId; keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize); CdmInitData mCdmInitData(reinterpret_cast(initDataRaw), kInitDataSize); std::vector initData; initData.assign(initDataRaw, initDataRaw + kInitDataSize); // clang-format off 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 }; // clang-format on 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 mCdmPsshBox(reinterpret_cast(psshBoxRaw), kPsshBoxSize); std::vector psshBox; psshBox.assign(psshBoxRaw, psshBoxRaw + kPsshBoxSize); CdmKeyMessage mCdmRequest(requestRaw, requestRaw + kRequestSize); std::map parameters = { {"paddingScheme", "BUBBLE WRAP"}, {"favorite-particle", "tetraquark"}, {"answer", "6 * 9"}, }; CdmAppParameterMap mCdmParameters; for (const auto &pair : parameters) { mCdmParameters[pair.first] = pair.second; } std::vector optionalParameters; KeyValue keyValue; for (auto itr = parameters.begin(); itr != parameters.end(); ++itr) { keyValue.key = itr->first; keyValue.value = itr->second; optionalParameters.push_back(keyValue); } static const char *kDefaultUrl = "http://google.com/"; static const char *kIsoBmffMimeType = "cenc"; static const char *kWebmMimeType = "webm"; struct TestSet { const char *mimeType; const std::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, mCdmPsshBox}, // ISO-BMFF, EME passing style {kIsoBmffMimeType, initData, mCdmPsshBox}, // ISO-BMFF, old passing style {kWebmMimeType, initData, mCdmInitData} // WebM }; size_t testSetCount = N_ELEM(testSets); // 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 = {mCdmRequest, kKeyRequestTypeInitial, kDefaultUrl}; CdmKeyRequest renewalRequest = {mCdmRequest, kKeyRequestTypeRenewal, kDefaultUrl}; CdmKeyRequest releaseRequest = {mCdmRequest, kKeyRequestTypeRelease, kDefaultUrl}; EXPECT_CALL(*mCdm, GenerateKeyRequest(mCdmSessionId, "", mimeType, initData, kLicenseTypeOffline, mCdmParameters, NotNull(), HasOrigin(EMPTY_ORIGIN), _)) .WillOnce( DoAll(SetArgPointee<8>(initialRequest), testing::Return(CdmResponseType(wvcdm::KEY_MESSAGE)))); EXPECT_CALL(*mCdm, GenerateKeyRequest(mCdmSessionId, "", mimeType, initData, kLicenseTypeStreaming, mCdmParameters, NotNull(), HasOrigin(EMPTY_ORIGIN), _)) .WillOnce( DoAll(SetArgPointee<8>(renewalRequest), testing::Return(CdmResponseType(wvcdm::KEY_MESSAGE)))); EXPECT_CALL(*mCdm, GenerateKeyRequest("", mCdmKeySetId, mimeType, initData, kLicenseTypeRelease, mCdmParameters, NotNull(), HasOrigin(EMPTY_ORIGIN), _)) .WillOnce( DoAll(SetArgPointee<8>(releaseRequest), testing::Return(CdmResponseType(wvcdm::KEY_MESSAGE)))); } } // Performs the actual tests for (size_t i = 0; i < testSetCount; ++i) { const std::string mimeType(testSets[i].mimeType); const std::vector &initData = testSets[i].initDataIn; KeyRequest result; auto ret = mPlugin->getKeyRequest(sessionId, initData, std::string(mimeType), KeyType::OFFLINE, optionalParameters, &result); EXPECT_TRUE(ret.isOk()); EXPECT_THAT(result.request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_EQ(KeyRequestType::INITIAL, result.requestType); EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); ret = mPlugin->getKeyRequest(sessionId, initData, std::string(mimeType), KeyType::STREAMING, optionalParameters, &result); EXPECT_TRUE(ret.isOk()); EXPECT_THAT(result.request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_EQ(KeyRequestType::RENEWAL, result.requestType); EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); ret = mPlugin->getKeyRequest(sessionId, initData, std::string(mimeType), KeyType::RELEASE, optionalParameters, &result); EXPECT_TRUE(ret.isOk()); EXPECT_THAT(result.request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_EQ(KeyRequestType::RELEASE, result.requestType); EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); } } TEST_F(WVDrmPluginHalTest, AddsKeys) { 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"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(responseRaw, sizeof(uint8_t), kResponseSize, fp); fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); fclose(fp); const std::vector response(responseRaw, responseRaw + kResponseSize); memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1); CdmKeySetId mCdmKeySetId(reinterpret_cast(keySetIdRaw), kKeySetIdSize); EXPECT_CALL(*mCdm, AddKey(mCdmSessionId, ElementsAreArray(responseRaw, kResponseSize), _)) .WillOnce( DoAll(SetArgPointee<2>(mCdmKeySetId), testing::Return(wvcdm::CdmResponseType(wvcdm::KEY_ADDED)))); EXPECT_CALL(*mCdm, AddKey("", ElementsAreArray(responseRaw, kResponseSize), Pointee(mCdmKeySetId))) .Times(1); KeySetId result; auto ret = mPlugin->provideKeyResponse(sessionId, response, &result); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(result.keySetId, ElementsAreArray(keySetIdRaw, kKeySetIdSize)); ret = mPlugin->provideKeyResponse(result.keySetId, response, &result); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(0u, result.keySetId.size()); } TEST_F(WVDrmPluginHalTest, HandlesPrivacyCertCaseOfAddKey) { const CdmClientPropertySet *propertySet = nullptr; // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); static const uint32_t kResponseSize = 256; uint8_t responseRaw[kResponseSize]; FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(responseRaw, sizeof(uint8_t), kResponseSize, fp); fclose(fp); EXPECT_CALL(*mCdm, AddKey(_, _, _)) .WillRepeatedly(testing::Return(CdmResponseType(wvcdm::NEED_KEY))); SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); EXPECT_THAT(sessionId, ElementsAreArray(sessionIdRaw, kSessionIdSize)); ASSERT_THAT(propertySet, NotNull()); ret = mPlugin->setPropertyString(std::string("privacyMode"), std::string("enable")); EXPECT_TRUE(ret.isOk()); EXPECT_TRUE(propertySet->use_privacy_mode()); std::vector response; response.assign(responseRaw, responseRaw + kResponseSize); std::vector keySetId; KeySetId result; ret = mPlugin->provideKeyResponse(sessionId, response, &result); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, RemovesKeys) { EXPECT_CALL(*mCdm, RemoveKeys(mCdmSessionId)).Times(1); auto ret = mPlugin->removeKeys(sessionId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, RestoresKeys) { EXPECT_CALL(*mCdm, RestoreKey(mCdmSessionId, ElementsAreArray(keySetIdRaw, kKeySetIdSize))) .Times(1); auto ret = mPlugin->restoreKeys(sessionId, keySetId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, QueriesKeyStatus) { std::map expectedLicenseStatus = { {"areTheKeysAllRight", "yes"}, {"isGMockAwesome", "ohhhhhhYeah"}, {"answer", "42"}, }; CdmQueryMap mCdmLicenseStatus; for (const auto &pair : expectedLicenseStatus) { mCdmLicenseStatus[pair.first] = pair.second; } EXPECT_CALL(*mCdm, QueryKeyStatus(mCdmSessionId, _)) .WillOnce(DoAll(SetArgPointee<1>(mCdmLicenseStatus), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); std::vector keyValues; auto ret = mPlugin->queryKeyStatus(sessionId, &keyValues); EXPECT_TRUE(ret.isOk()); ASSERT_EQ(expectedLicenseStatus.size(), keyValues.size()); KeyValue keyValuePair; size_t i = 0; for (std::map::iterator itr = expectedLicenseStatus.begin(); itr != expectedLicenseStatus.end(); ++itr) { keyValuePair.value = keyValues[i++].value; EXPECT_EQ(itr->second.c_str(), std::string(keyValuePair.value.c_str())); } } TEST_F(WVDrmPluginHalTest, GetsProvisioningRequests) { static const uint32_t kRequestSize = 256; uint8_t requestRaw[kRequestSize]; FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(requestRaw, sizeof(uint8_t), kRequestSize, fp); fclose(fp); CdmProvisioningRequest mCdmRequest(requestRaw, requestRaw + kRequestSize); static const char *kDefaultUrl = "http://google.com/"; EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // The first and the third invocation should be at default security level, // while the second one should be L3 EXPECT_CALL(*mCdm, GetProvisioningRequest(kCertificateWidevine, IsEmpty(), HasOrigin(EMPTY_ORIGIN), IsEmpty(), wvcdm::kLevelDefault, _, _)) .Times(2) .WillRepeatedly(DoAll(SetArgPointee<5>(mCdmRequest), SetArgPointee<6>(kDefaultUrl), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, GetProvisioningRequest(kCertificateWidevine, IsEmpty(), HasOrigin(EMPTY_ORIGIN), IsEmpty(), wvcdm::kLevel3, _, _)) .WillOnce(DoAll(SetArgPointee<5>(mCdmRequest), SetArgPointee<6>(kDefaultUrl), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Make 3 provisioning requests at security level default then L3 then L1 ProvisionRequest result; auto ret = mPlugin->getProvisionRequest(std::string(""), std::string(""), &result); EXPECT_TRUE(ret.isOk()); std::vector request(result.request); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); // Set L3 security level ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L3")); EXPECT_TRUE(ret.isOk()); ret = mPlugin->getProvisionRequest(std::string(""), std::string(""), &result); EXPECT_TRUE(ret.isOk()); std::vector requestL3(result.request); EXPECT_THAT(requestL3, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); // Reset security level to L1 ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L1")); EXPECT_TRUE(ret.isOk()); ret = mPlugin->getProvisionRequest(std::string(""), std::string(""), &result); EXPECT_TRUE(ret.isOk()); std::vector requestL1(result.request); EXPECT_THAT(requestL1, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); } TEST_F(WVDrmPluginHalTest, RejectsAtscProvisioningRequests) { auto ret = mPlugin->setPropertyString(std::string("atscMode"), std::string("enable")); EXPECT_TRUE(ret.isOk()); ProvisionRequest result; ret = mPlugin->getProvisionRequest(std::string(""), std::string(""), &result); EXPECT_EQ(static_cast(Status::ERROR_DRM_CANNOT_HANDLE), ret.getServiceSpecificError()); } TEST_F(WVDrmPluginHalTest, HandlesProvisioningResponses) { static const uint32_t kResponseSize = 512; uint8_t responseRaw[kResponseSize]; FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(responseRaw, sizeof(uint8_t), kResponseSize, fp); fclose(fp); std::vector response; response.assign(responseRaw, responseRaw + kResponseSize); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // The first and the third invocation should be at default security level, // while the second one should be L3 EXPECT_CALL(*mCdm, HandleProvisioningResponse( HasOrigin(EMPTY_ORIGIN), ElementsAreArray(responseRaw, kResponseSize), wvcdm::kLevelDefault, _, _)) .Times(2); EXPECT_CALL(*mCdm, HandleProvisioningResponse( HasOrigin(EMPTY_ORIGIN), ElementsAreArray(responseRaw, kResponseSize), wvcdm::kLevel3, _, _)) .Times(1); std::vector cert; std::vector key; // Process 3 provisioning responses at security level default then L3 then L1 ProvideProvisionResponseResult result; auto ret = mPlugin->provideProvisionResponse(response, &result); EXPECT_TRUE(ret.isOk()); // Set L3 security level ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L3")); EXPECT_TRUE(ret.isOk()); ret = mPlugin->provideProvisionResponse(response, &result); EXPECT_TRUE(ret.isOk()); // Reset security level to L1 ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L1")); EXPECT_TRUE(ret.isOk()); ret = mPlugin->provideProvisionResponse(response, &result); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, UnprovisionsDevice) { EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN))) .Times(1); EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN))) .Times(1); Status res = mPlugin->unprovisionDevice(); ASSERT_EQ(Status::OK, res); } TEST_F(WVDrmPluginHalTest, MuxesUnprovisioningErrors) { // 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(*mCdm, Unprovision(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN))) .WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR))) .WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR))) .WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR))); EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN))) .WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR))) .WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR))) .WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR))); Status res = mPlugin->unprovisionDevice(); ASSERT_NE(Status::OK, res); res = mPlugin->unprovisionDevice(); ASSERT_NE(Status::OK, res); res = mPlugin->unprovisionDevice(); ASSERT_NE(Status::OK, res); } TEST_F(WVDrmPluginHalTest, UnprovisionsOrigin) { std::vector specialResponse; specialResponse.assign(kUnprovisionResponse, kUnprovisionResponse + kUnprovisionResponseSize); EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL1, HasOrigin(kOrigin.c_str()))) .Times(1); EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL3, HasOrigin(kOrigin.c_str()))) .Times(1); auto ret = mPlugin->setPropertyString(std::string("origin"), std::string(kOrigin)); EXPECT_TRUE(ret.isOk()); ProvideProvisionResponseResult result; ret = mPlugin->provideProvisionResponse(specialResponse, &result); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, UnprovisionsGloballyWithSpoid) { std::vector specialResponse; specialResponse.assign(kUnprovisionResponse, kUnprovisionResponse + kUnprovisionResponseSize); android::sp> cdm = new StrictMock(); ASSERT_TRUE(cdm) << "Failed to create mocked CDM"; EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _)) .WillRepeatedly(DoAll(SetArgPointee<2>(kDeviceId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN))) .Times(1); EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN))) .Times(1); std::shared_ptr plugin = ::ndk::SharedRefBase::make( cdm.get(), mAppPackageName.c_str(), &mCrypto, true); ASSERT_TRUE(plugin) << "Failed to create drm plugin"; StrictMock crypto; ProvideProvisionResponseResult result; auto ret = plugin->provideProvisionResponse(specialResponse, &result); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, WillNotUnprovisionWithoutOriginOrSpoid) { std::vector specialResponse; specialResponse.assign(kUnprovisionResponse, kUnprovisionResponse + kUnprovisionResponseSize); EXPECT_CALL(*mCdm, Unprovision(_, _)).Times(0); ProvideProvisionResponseResult result; auto ret = mPlugin->provideProvisionResponse(specialResponse, &result); EXPECT_FALSE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, MuxesOriginUnprovisioningErrors) { std::vector specialResponse; specialResponse.assign(kUnprovisionResponse, 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(*mCdm, Unprovision(kSecurityLevelL1, HasOrigin(kOrigin.c_str()))) .WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR))) .WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR))) .WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR))); EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL3, HasOrigin(kOrigin.c_str()))) .WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR))) .WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR))) .WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR))); auto ret = mPlugin->setPropertyString(std::string("origin"), std::string(kOrigin)); EXPECT_TRUE(ret.isOk()); ProvideProvisionResponseResult result; ret = mPlugin->provideProvisionResponse(specialResponse, &result); EXPECT_FALSE(ret.isOk()); ret = mPlugin->provideProvisionResponse(specialResponse, &result); EXPECT_FALSE(ret.isOk()); ret = mPlugin->provideProvisionResponse(specialResponse, &result); EXPECT_FALSE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, RejectsAtscUnprovisionDeviceRequests) { auto ret = mPlugin->setPropertyString(std::string("atscMode"), std::string("enable")); EXPECT_TRUE(ret.isOk()); Status status = mPlugin->unprovisionDevice(); ASSERT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); } TEST_F(WVDrmPluginHalTest, GetsSecureStops) { static constexpr uint32_t kStopSize = 53; static constexpr uint32_t kStopCount = 7; CdmUsageReportList fakeSecureStops; for (uint32_t i = 0; i < kStopCount; ++i) { fakeSecureStops.push_back(CdmRandom::RandomData(kStopSize)); } const std::string app_id = "my_app_id"; EXPECT_CALL(*mCdm, GetUsageInfo(app_id, _, _)) .WillOnce(DoAll(SetArgPointee<2>(fakeSecureStops), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); auto ret = mPlugin->setPropertyString("appId", app_id); EXPECT_TRUE(ret.isOk()); std::vector secureStops; ret = mPlugin->getSecureStops(&secureStops); EXPECT_TRUE(ret.isOk()); CdmUsageReportList stops; for (const auto &stop : secureStops) { stops.emplace_back(stop.opaqueData.begin(), stop.opaqueData.end()); } EXPECT_EQ(kStopCount, stops.size()); for (const CdmUsageReport &expectedSecureStop : fakeSecureStops) { EXPECT_THAT(stops, Contains(expectedSecureStop)); } } TEST_F(WVDrmPluginHalTest, ReleasesAllSecureStops) { const std::string app_id = ""; EXPECT_CALL(*mCdm, RemoveAllUsageInfo(app_id, _)).Times(1); auto ret = mPlugin->setPropertyString("appId", app_id); EXPECT_TRUE(ret.isOk()); ret = mPlugin->releaseAllSecureStops(); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, ReleasesSecureStop) { static constexpr uint32_t kMessageSize = 128; const CdmKeyResponse releaseMessage = CdmRandom::RandomData(kMessageSize); EXPECT_CALL(*mCdm, ReleaseUsageInfo(releaseMessage, _)).Times(1); SecureStopId stopId; stopId.secureStopId.assign(releaseMessage.begin(), releaseMessage.end()); auto ret = mPlugin->releaseSecureStop(stopId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, ReturnsExpectedPropertyValues) { 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 std::string analogOutputCap = wvcdm::QUERY_VALUE_CGMS_A; static const std::string currentHdcpLevel = wvcdm::QUERY_VALUE_HDCP_NONE; static const std::string maxHdcpLevel = wvcdm::QUERY_VALUE_HDCP_V2_3; static const std::string systemId = "The Universe"; static const std::string provisioningId("Life\0&Everything", 16); static const std::string openSessions = "42"; static const std::string maxSessions = "54"; static const std::string maxUsageEntries = "256"; static const std::string oemCryptoApiVersion = "13"; static const std::string oemCryptoApiMinorVersion = "2"; static const std::string currentSRMVersion = "1"; static const std::string mCdmVersion = "Infinity Minus 1"; static const std::string resourceRatingTier = "1"; static const std::string oemCryptoBuildInformation = "Mostly Harmless"; static const std::string oemCryptoHashNotSupported = "0"; static const std::string oemCryptoCrcClearBuffer = "1"; static const std::string oemCryptoPartnerDefinedHash = "2"; static const std::string decryptHashErrorBadHashAndFrameNumber = "53, 1"; static const std::string provisioningModel = "Zaphod Beeblebrox"; drm_metrics::WvCdmMetrics expected_metrics; std::string serialized_metrics = wvutil::a2bs_hex(kSerializedMetricsHex); ASSERT_TRUE(expected_metrics.ParseFromString(serialized_metrics)); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))) .WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _)) .WillOnce(DoAll(SetArgPointee<2>(kDeviceId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SYSTEM_ID, _)) .WillOnce(DoAll(SetArgPointee<2>(systemId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_PROVISIONING_ID, _)) .WillOnce(DoAll(SetArgPointee<2>(provisioningId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_NUMBER_OF_OPEN_SESSIONS, _)) .WillOnce(DoAll(SetArgPointee<2>(openSessions), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_MAX_NUMBER_OF_SESSIONS, _)) .WillOnce(DoAll(SetArgPointee<2>(maxSessions), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_API_VERSION, _)) .WillOnce(DoAll(SetArgPointee<2>(oemCryptoApiVersion), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION, _)) .WillOnce(DoAll(SetArgPointee<2>(oemCryptoApiMinorVersion), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SRM_UPDATE_SUPPORT, _)) .WillOnce(DoAll(SetArgPointee<2>("True"), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_CURRENT_SRM_VERSION, _)) .WillOnce(DoAll(SetArgPointee<2>(currentSRMVersion), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_WVCDM_VERSION, _)) .WillOnce(DoAll(SetArgPointee<2>(mCdmVersion), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_RESOURCE_RATING_TIER, _)) .WillOnce(DoAll(SetArgPointee<2>(resourceRatingTier), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_DECRYPT_HASH_SUPPORT, _)) .WillOnce(DoAll(SetArgPointee<2>("1"), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION, _)) .WillOnce(DoAll(SetArgPointee<2>(oemCryptoBuildInformation), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, GetDecryptHashError(_, _)) .WillOnce(DoAll(SetArgPointee<1>(decryptHashErrorBadHashAndFrameNumber), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_PROVISIONING_MODEL, _)) .WillOnce(DoAll(SetArgPointee<2>(provisioningModel), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_ANALOG_OUTPUT_CAPABILITIES, _)) .WillOnce(DoAll(SetArgPointee<2>(analogOutputCap), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT, _)) .WillOnce(DoAll(SetArgPointee<2>("True"), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_CURRENT_HDCP_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(currentHdcpLevel), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_MAX_HDCP_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(maxHdcpLevel), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_MAX_USAGE_TABLE_ENTRIES, _)) .WillOnce(DoAll(SetArgPointee<2>(maxUsageEntries), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_PRODUCTION_READY, _)) .WillOnce(DoAll(SetArgPointee<2>(wvcdm::QUERY_VALUE_TRUE), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_USAGE_SUPPORT, _)) .WillOnce(DoAll(SetArgPointee<2>("True"), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_WATERMARKING_SUPPORT, _)) .WillOnce(DoAll(SetArgPointee<2>(wvcdm::QUERY_VALUE_NOT_SUPPORTED), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, GetCurrentMetrics(_, _)) .WillOnce(DoAll(SetArgPointee<1>(expected_metrics), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); std::string stringResult; std::vector vectorResult; auto ret = mPlugin->getPropertyString(std::string("vendor"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ("Google", stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("version"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ(mCdmVersion.c_str(), stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("description"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ("Widevine CDM", stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("algorithms"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ("AES/CBC/NoPadding,HmacSHA256", stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("securityLevel"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L1.c_str(), stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("securityLevel"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L3.c_str(), stringResult.c_str()); ret = mPlugin->getPropertyByteArray(std::string("deviceUniqueId"), &vectorResult); EXPECT_TRUE(ret.isOk()); EXPECT_THAT(vectorResult, ElementsAreArray(kDeviceId.data(), kDeviceId.size())); ret = mPlugin->getPropertyString(std::string("systemId"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ(systemId.c_str(), stringResult.c_str()); ret = mPlugin->getPropertyByteArray(std::string("provisioningUniqueId"), &vectorResult); EXPECT_TRUE(ret.isOk()); EXPECT_THAT(vectorResult, ElementsAreArray(provisioningId.data(), provisioningId.size())); ret = mPlugin->getPropertyString(std::string("numberOfOpenSessions"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(openSessions, stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("maxNumberOfSessions"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(maxSessions, stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("oemCryptoApiVersion"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ(oemCryptoApiVersion.c_str(), stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("oemCryptoApiMinorVersion"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ(oemCryptoApiMinorVersion.c_str(), stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("SRMUpdateSupport"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ("True", stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("CurrentSRMVersion"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ(currentSRMVersion.c_str(), stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("resourceRatingTier"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ(resourceRatingTier.c_str(), stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("oemCryptoBuildInformation"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ(oemCryptoBuildInformation.c_str(), stringResult.c_str()); std::stringstream ss; ss << oemCryptoHashNotSupported << " " << oemCryptoCrcClearBuffer << " " << oemCryptoPartnerDefinedHash; std::string validResults = ss.str(); ret = mPlugin->getPropertyString(std::string("decryptHashSupport"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(oemCryptoCrcClearBuffer, stringResult); ret = mPlugin->getPropertyString(std::string("decryptHashError"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(decryptHashErrorBadHashAndFrameNumber, stringResult); ret = mPlugin->getPropertyString(std::string("provisioningModel"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(provisioningModel, stringResult); ret = mPlugin->getPropertyString(std::string("analogOutputCapabilities"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(analogOutputCap, stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("canDisableAnalogOutput"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ("True", stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("hdcpLevel"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(currentHdcpLevel, stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("maxHdcpLevel"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(maxHdcpLevel, stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("maxUsageEntriesSupported"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(maxUsageEntries, stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("usageReportingSupport"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_STREQ("True", stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("productionReady"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE, stringResult.c_str()); ret = mPlugin->getPropertyString(std::string("watermarkingSupport"), &stringResult); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(wvcdm::QUERY_VALUE_NOT_SUPPORTED, stringResult.c_str()); // This call occurs before any open session or other call. This means // that the mCdm identifier is not yet sealed, and metrics return empty // metrics data. ret = mPlugin->getPropertyByteArray(std::string("metrics"), &vectorResult); EXPECT_TRUE(ret.isOk()); const char empty[] = {}; EXPECT_THAT(vectorResult, ElementsAreArray(empty, sizeof(empty))); // Set expectations for the OpenSession call and a CloseSession call. EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, HasOrigin(EMPTY_ORIGIN), _, _)) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); // This call causes the mCdm identifier to become sealed. SessionId sessionId; ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); // This call occurs after open session. The CDM identifier should be sealed. // And the call should populate the mock metrics data. ret = mPlugin->getPropertyByteArray(std::string("metrics"), &vectorResult); EXPECT_TRUE(ret.isOk()); EXPECT_THAT(vectorResult, ElementsAreArray(serialized_metrics.data(), serialized_metrics.size())); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, DoesNotGetUnknownProperties) { std::string stringResult; std::vector vectorResult; auto ret = mPlugin->getPropertyString(std::string("unknownProperty"), &stringResult); EXPECT_FALSE(ret.isOk()); EXPECT_TRUE(stringResult.empty()); ret = mPlugin->getPropertyByteArray(std::string("unknownProperty"), &vectorResult); EXPECT_FALSE(ret.isOk()); EXPECT_EQ(0u, vectorResult.size()); } TEST_F(WVDrmPluginHalTest, DoesNotSetUnknownProperties) { static const uint32_t kValueSize = 32; uint8_t valueRaw[kValueSize]; FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(valueRaw, sizeof(uint8_t), kValueSize, fp); fclose(fp); std::vector value; value.assign(valueRaw, valueRaw + kValueSize); auto ret = mPlugin->setPropertyString(std::string("unknownProperty"), std::string("ignored")); EXPECT_FALSE(ret.isOk()); ret = mPlugin->setPropertyByteArray(std::string("unknownProperty"), value); EXPECT_FALSE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, CompliesWithSpoidVariability) { const std::string kDeviceIds[] = { kDeviceId, kDeviceId + " the Second", }; const size_t kDeviceCount = N_ELEM(kDeviceIds); const std::string kAppNames[] = { std::string("com.google.widevine"), std::string("com.youtube"), }; const size_t kAppCount = N_ELEM(kAppNames); const std::string kOrigins[] = { kOrigin, kOrigin + " but not that one, the other one.", std::string(/* Intentionally Empty */), }; const size_t kOriginCount = N_ELEM(kOrigins); const size_t kPluginCount = 2; const size_t kPluginsPerCdm = kAppCount * kOriginCount * kPluginCount; // We will get kPluginCount SPOIDs for every app package name + device id + // origin combination. std::vector spoids[kDeviceCount][kAppCount][kOriginCount] [kPluginCount]; StrictMock crypto; for (size_t deviceIndex = 0; deviceIndex < kDeviceCount; ++deviceIndex) { const std::string &deviceId = kDeviceIds[deviceIndex]; android::sp> cdm = new StrictMock(); ASSERT_TRUE(cdm) << "Failed to create mocked CDM"; EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _)) .Times(AtLeast(kPluginsPerCdm)) .WillRepeatedly( DoAll(SetArgPointee<2>(deviceId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); for (size_t appIndex = 0; appIndex < kAppCount; ++appIndex) { const std::string &appPackageName = kAppNames[appIndex]; for (size_t originIndex = 0; originIndex < kOriginCount; ++originIndex) { const std::string &origin = kOrigins[originIndex]; for (size_t mPluginIndex = 0; mPluginIndex < kPluginCount; ++mPluginIndex) { std::shared_ptr plugin = ::ndk::SharedRefBase::make( cdm.get(), appPackageName.c_str(), &crypto, true); ASSERT_TRUE(plugin) << "Failed to create drm plugin"; if (!origin.empty()) { auto ret = plugin->setPropertyString(std::string("origin"), std::string(origin)); EXPECT_TRUE(ret.isOk()); } std::vector vectorResult; auto ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &vectorResult); EXPECT_TRUE(ret.isOk()); spoids[deviceIndex][appIndex][originIndex][mPluginIndex] = vectorResult; } } } } // This nest of loops makes sure all the SPOIDs we retrieved above are // identical if their parameters were identical and dissimilar otherwise. for (size_t deviceIndex = 0; deviceIndex < kDeviceCount; ++deviceIndex) { for (size_t appIndex = 0; appIndex < kAppCount; ++appIndex) { for (size_t originIndex = 0; originIndex < kOriginCount; ++originIndex) { for (size_t mPluginIndex = 0; mPluginIndex < kPluginCount; ++mPluginIndex) { const std::vector &firstSpoid = spoids[deviceIndex][appIndex][originIndex][mPluginIndex]; for (size_t deviceIndex2 = 0; deviceIndex2 < kDeviceCount; ++deviceIndex2) { for (size_t appIndex2 = 0; appIndex2 < kAppCount; ++appIndex2) { for (size_t originIndex2 = 0; originIndex2 < kOriginCount; ++originIndex2) { for (size_t mPluginIndex2 = 0; mPluginIndex2 < kPluginCount; ++mPluginIndex2) { const std::vector &secondSpoid = spoids[deviceIndex2][appIndex2][originIndex2] [mPluginIndex2]; if (deviceIndex == deviceIndex2 && appIndex == appIndex2 && originIndex == originIndex2) { EXPECT_EQ(firstSpoid, secondSpoid); } else { EXPECT_NE(firstSpoid, secondSpoid); } } } } } } } } } } TEST_F(WVDrmPluginHalTest, ReturnsSameL1Spoid) { std::string kL1DeviceId = kDeviceId + "L1"; constexpr size_t kSpoidQuery = 2; std::vector spoid[kSpoidQuery]; android::sp> cdm = new StrictMock(); ASSERT_TRUE(cdm) << "Failed to create mocked CDM"; EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _)) .Times(kSpoidQuery) .WillRepeatedly(DoAll(SetArgPointee<2>(kL1DeviceId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior to support session creation EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .Times(kSpoidQuery) .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .Times(kSpoidQuery) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*cdm, CloseSession(_)).Times(kSpoidQuery); // Open a session twice with the same security level, app package name and // origin and make sure the spoids returned are the same const std::string kAppPackageName("com.google.widevine"); StrictMock crypto; for (int i = 0; i < kSpoidQuery; ++i) { std::shared_ptr plugin = ::ndk::SharedRefBase::make( cdm.get(), kAppPackageName.c_str(), &crypto, true); ASSERT_TRUE(plugin) << "Failed to create drm plugin"; SessionId sessionId; auto ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = plugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &spoid[i]); EXPECT_TRUE(ret.isOk()); } EXPECT_EQ(spoid[0], spoid[1]); } TEST_F(WVDrmPluginHalTest, ReturnsL3SpoidsWhenL3ProvisionedUsingL3Spoid) { std::string kL3DeviceId = kDeviceId + "L3"; constexpr size_t kSpoidQuery = 2; std::vector spoid[kSpoidQuery]; android::sp> cdm = new StrictMock(); ASSERT_TRUE(cdm) << "Failed to create mocked CDM"; EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _)) .Times(kSpoidQuery) .WillRepeatedly(DoAll(SetArgPointee<2>(kL3DeviceId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior to support session creation EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .Times(kSpoidQuery) .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .Times(kSpoidQuery) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*cdm, IsProvisioned(wvcdm::kSecurityLevelL3, _, _, _)) .Times(kSpoidQuery) .WillRepeatedly(testing::Return(true)); EXPECT_CALL(*cdm, CloseSession(_)).Times(kSpoidQuery); // The device is provisioned at L3 security level. Open a session twice // and make sure that the spoids returned are the same when the // session are opened at the same security level(L3), app package name // and origin const std::string kAppPackageName("com.google.widevine"); StrictMock crypto; for (size_t i = 0; i < kSpoidQuery; ++i) { std::shared_ptr plugin = ::ndk::SharedRefBase::make( cdm.get(), kAppPackageName.c_str(), &crypto, true); ASSERT_TRUE(plugin) << "Failed to create drm plugin"; // Forcing L3 auto ret = plugin->setPropertyString(std::string("securityLevel"), std::string("L3")); EXPECT_TRUE(ret.isOk()); SessionId sessionId; ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = plugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); std::vector result; ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); EXPECT_TRUE(ret.isOk()); spoid[i] = result; } EXPECT_EQ(spoid[0], spoid[1]); } TEST_F(WVDrmPluginHalTest, ReturnsL3SpoidsWhenL3Unprovisioned) { std::string kL1DeviceId = kDeviceId + "L1"; std::string kL3DeviceId = kDeviceId + "L3"; constexpr size_t kSpoidQuery = 2; std::vector spoid[kSpoidQuery]; android::sp> cdm = new StrictMock(); ASSERT_TRUE(cdm) << "Failed to create mocked CDM"; EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _)) .WillOnce(DoAll(SetArgPointee<2>(kL1DeviceId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _)) .Times(kSpoidQuery) .WillRepeatedly(DoAll(SetArgPointee<2>(kL3DeviceId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior to support session creation EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .Times(kSpoidQuery) .WillOnce(testing::Return(CdmResponseType(wvcdm::NEED_PROVISIONING))) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillOnce(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*cdm, IsProvisioned(wvcdm::kSecurityLevelL3, _, _, _)) .WillOnce(testing::Return(false)) .WillOnce(testing::Return(true)); EXPECT_CALL(*cdm, IsProvisioned(wvcdm::kSecurityLevelL1, _, _, _)) .WillOnce(testing::Return(false)); EXPECT_CALL(*cdm, CloseSession(_)).Times(1); // This device is unprovisioned at the L3 security level. An attempt to // open a session results in an ERROR_DRM_NOT_PROVISIONED error. // Spoids are computed using device Unique IDs at each security level. // Since provisioning has not occurred for either spoid, the spoid // using the L3 device ID is used. const std::string kAppPackageName("com.google.widevine"); StrictMock crypto; std::shared_ptr plugin = ::ndk::SharedRefBase::make( cdm.get(), kAppPackageName.c_str(), &crypto, true); ASSERT_TRUE(plugin) << "Failed to create drm plugin"; // Force L3 auto ret = plugin->setPropertyString(std::string("securityLevel"), std::string("L3")); EXPECT_TRUE(ret.isOk()); SessionId sessionId; ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_EQ(static_cast(Status::ERROR_DRM_NOT_PROVISIONED), ret.getServiceSpecificError()); std::vector result; ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); EXPECT_TRUE(ret.isOk()); spoid[0] = result; // Try to open a session again. If provisioning took place, this time the // attempt will be successful. Retrieve the spoid. This time only // the device unique ID at the L3 security level will be queried. // Confirm that it matches the spoid queried earlier. plugin = ::ndk::SharedRefBase::make( cdm.get(), kAppPackageName.c_str(), &crypto, true); ASSERT_TRUE(plugin) << "Failed to create drm plugin"; // Force L3 ret = plugin->setPropertyString(std::string("securityLevel"), std::string("L3")); EXPECT_TRUE(ret.isOk()); sessionId.clear(); ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = plugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); result.clear(); ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); EXPECT_TRUE(ret.isOk()); spoid[1] = result; EXPECT_EQ(spoid[0], spoid[1]); } TEST_F(WVDrmPluginHalTest, ReturnsL1SpoidsWhenL3ProvisionedUsingL1Spoid) { std::string kL1DeviceId = kDeviceId + "L1"; std::string kL3DeviceId = kDeviceId + "L3"; constexpr size_t kSpoidQuery = 2; std::vector spoidL1; std::vector spoidL3[kSpoidQuery]; // Set expectations for the first mPlugin instance android::sp> cdm1 = new StrictMock(); ASSERT_TRUE(cdm1) << "Failed to create mocked CDM 1"; EXPECT_CALL(*cdm1, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _)) .WillOnce(DoAll(SetArgPointee<2>(kL1DeviceId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior to support session creation EXPECT_CALL(*cdm1, OpenSession(StrEq("com.widevine"), _, _, _, _)) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))) .WillOnce(testing::Return(CdmResponseType(wvcdm::NEED_PROVISIONING))); EXPECT_CALL(*cdm1, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillOnce(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*cdm1, CloseSession(_)); // Open a session at L1 security level. The spoid is now computed with // the L1 device unique ID const std::string kAppPackageName("com.google.widevine"); StrictMock crypto; std::shared_ptr plugin = ::ndk::SharedRefBase::make( cdm1.get(), kAppPackageName.c_str(), &crypto, true); ASSERT_TRUE(plugin) << "Failed to create drm plugin"; std::vector sessionId; auto ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = plugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); std::vector result; ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); EXPECT_TRUE(ret.isOk()); spoidL1 = result; // Force L3. This should not be allowed since the spoid has been // computed. We defer correcting this, as this might have an // impact on apps. L3 is not provisioned so a call to openSession // returns a ERROR_DRM_NOT_PROVISIONED error. No spoid computation takes // place. ret = plugin->setPropertyString(std::string("securityLevel"), std::string("L3")); EXPECT_TRUE(ret.isOk()); sessionId.clear(); ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); ASSERT_EQ(static_cast(Status::ERROR_DRM_NOT_PROVISIONED), ret.getServiceSpecificError()); result.clear(); ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); EXPECT_TRUE(ret.isOk()); spoidL3[0] = result; // Set expectations for the second mPlugin instance android::sp> cdm2 = new StrictMock(); ASSERT_TRUE(cdm2) << "Failed to create mocked CDM 2"; EXPECT_CALL(*cdm2, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _)) .WillOnce(DoAll(SetArgPointee<2>(kL1DeviceId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*cdm2, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _)) .WillOnce(DoAll(SetArgPointee<2>(kL3DeviceId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior to support session creation EXPECT_CALL(*cdm2, OpenSession(StrEq("com.widevine"), _, _, _, _)) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*cdm2, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillOnce(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*cdm2, IsProvisioned(wvcdm::kSecurityLevelL1, _, _, _)) .WillOnce(testing::Return(true)); EXPECT_CALL(*cdm2, IsProvisioned(wvcdm::kSecurityLevelL3, _, _, _)) .WillOnce(testing::Return(false)); EXPECT_CALL(*cdm2, CloseSession(_)); // Try to open a session again. If provisioning took place, this time the // attempt will be successful. Spoids are computed using device unique IDs // from both L1 and L3. The device is provisioned for L3 using the spoid with // L1 but not L3 device unique ID, so the spoid with L1 device unique ID is // used. plugin = ::ndk::SharedRefBase::make( cdm2.get(), kAppPackageName.c_str(), &crypto, true); ASSERT_TRUE(plugin) << "Failed to create drm plugin"; // Force L3 ret = plugin->setPropertyString(std::string("securityLevel"), std::string("L3")); EXPECT_TRUE(ret.isOk()); sessionId.clear(); ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = plugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); result.clear(); ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); EXPECT_TRUE(ret.isOk()); spoidL3[1] = result; EXPECT_EQ(spoidL3[0], spoidL3[1]); EXPECT_EQ(spoidL1, spoidL3[0]); } TEST_F(WVDrmPluginHalTest, FailsGenericMethodsWithoutAnAlgorithmSet) { std::vector keyId; std::vector input; std::vector iv; std::vector signature; // Provide expected behavior to support session creation EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); // Note that we do not set the algorithms. This should cause these methods // to fail. std::vector result; ret = mPlugin->encrypt(sessionId, keyId, input, iv, &result); // NO_INIT is converted to Status::ERROR_DRM_UNKNOWN EXPECT_EQ(static_cast(Status::ERROR_DRM_UNKNOWN), ret.getServiceSpecificError()); ret = mPlugin->decrypt(sessionId, keyId, input, iv, &result); EXPECT_EQ(static_cast(Status::ERROR_DRM_UNKNOWN), ret.getServiceSpecificError()); ret = mPlugin->sign(sessionId, keyId, input, &result); EXPECT_EQ(static_cast(Status::ERROR_DRM_UNKNOWN), ret.getServiceSpecificError()); bool match = false; ret = mPlugin->verify(sessionId, keyId, input, signature, &match); EXPECT_EQ(static_cast(Status::ERROR_DRM_UNKNOWN), ret.getServiceSpecificError()); } 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(WVDrmPluginHalTest, CallsGenericEncrypt) { 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"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; 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); std::vector keyId; keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE); std::vector input; input.assign(inputRaw, inputRaw + kDataSize); std::vector iv; iv.assign(ivRaw, ivRaw + KEY_IV_SIZE); std::vector output; // Provide expected behavior to support session creation EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, GenericEncrypt(mCdmSessionId, _, _, _, wvcdm::kEncryptionAlgorithmAesCbc128, NotNull())) .WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR))); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = mPlugin->setCipherAlgorithm(sessionId, std::string("AES/CBC/NoPadding")); EXPECT_TRUE(ret.isOk()); ret = mPlugin->encrypt(sessionId, keyId, input, iv, &output); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, CallsGenericDecrypt) { 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"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; 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); std::vector keyId; keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE); std::vector input; input.assign(inputRaw, inputRaw + kDataSize); std::vector iv; iv.assign(ivRaw, ivRaw + KEY_IV_SIZE); std::vector output; // Provide expected behavior to support session creation EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, GenericDecrypt(mCdmSessionId, _, _, _, wvcdm::kEncryptionAlgorithmAesCbc128, NotNull())) .WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR))); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = mPlugin->setCipherAlgorithm(sessionId, std::string("AES/CBC/NoPadding")); EXPECT_TRUE(ret.isOk()); ret = mPlugin->decrypt(sessionId, keyId, input, iv, &output); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, CallsGenericSign) { static const size_t kDataSize = 256; uint8_t keyIdRaw[KEY_ID_SIZE]; uint8_t messageRaw[kDataSize]; FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp); fread(messageRaw, sizeof(uint8_t), kDataSize, fp); fclose(fp); std::vector keyId; keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE); std::vector message; message.assign(messageRaw, messageRaw + kDataSize); // Provide expected behavior to support session creation EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); const std::string kFakeSign = "super secure signature generated"; EXPECT_CALL(*mCdm, GenericSign(mCdmSessionId, _, _, wvcdm::kSigningAlgorithmHmacSha256, NotNull())) .WillOnce(DoAll(SetArgPointee<4>(kFakeSign), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = mPlugin->setMacAlgorithm(sessionId, std::string("HmacSHA256")); EXPECT_TRUE(ret.isOk()); std::vector signature; ret = mPlugin->sign(sessionId, keyId, message, &signature); EXPECT_TRUE(ret.isOk()); const std::vector kFakeSignVec(kFakeSign.begin(), kFakeSign.end()); ASSERT_EQ(signature, kFakeSignVec); } TEST_F(WVDrmPluginHalTest, CallsGenericVerify) { 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"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; 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); std::vector keyId; keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE); std::vector message; message.assign(messageRaw, messageRaw + kDataSize); std::vector signature; signature.assign(signatureRaw, signatureRaw + kSignatureSize); const wvcdm::CdmResponseType kSignatureOkResponse(wvcdm::NO_ERROR); const wvcdm::CdmResponseType kSignatureBadResponse( wvcdm::UNKNOWN_ERROR, OEMCrypto_ERROR_SIGNATURE_FAILURE, "GenericVerify"); // Provide expected behavior to support session creation EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, GenericVerify(mCdmSessionId, _, _, wvcdm::kSigningAlgorithmHmacSha256, _)) .WillOnce(testing::Return(kSignatureOkResponse)) .WillOnce(testing::Return(kSignatureBadResponse)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = mPlugin->setMacAlgorithm(sessionId, std::string("HmacSHA256")); EXPECT_TRUE(ret.isOk()); bool match = false; ret = mPlugin->verify(sessionId, keyId, message, signature, &match); EXPECT_TRUE(ret.isOk()); EXPECT_TRUE(match); ret = mPlugin->verify(sessionId, keyId, message, signature, &match); EXPECT_TRUE(ret.isOk()); EXPECT_FALSE(match); } TEST_F(WVDrmPluginHalTest, RegistersForEvents) { // Provide expected behavior to support session creation EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, UnregistersForAllEventsOnDestruction) { uint8_t sessionIdRaw1[kSessionIdSize]; uint8_t sessionIdRaw2[kSessionIdSize]; FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(sessionIdRaw1, sizeof(uint8_t), kSessionIdSize, fp); fread(sessionIdRaw2, sizeof(uint8_t), kSessionIdSize, fp); fclose(fp); CdmSessionId mCdmSessionId1(sessionIdRaw1, sessionIdRaw1 + kSessionIdSize); CdmSessionId mCdmSessionId2(sessionIdRaw2, sessionIdRaw2 + kSessionIdSize); EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId1), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId2), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId1, _)) .WillOnce(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId2, _)) .WillOnce(Invoke(setSessionIdOnMap<5>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); { SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); } } TEST_F(WVDrmPluginHalTest, MarshalsEvents) { const int64_t kExpiryTimeInSeconds = 123456789012LL; std::string kKeyId1 = "Testing Key1 Id "; std::string kKeyId2 = "Testing Key2 Id "; std::string kKeyId3 = "Testing Key3 Id "; std::string kKeyId4 = "Testing Key4 Id "; std::string kKeyId5 = "Testing Key5 Id "; { InSequence calls; std::vector keyId; std::vector keyStatusList; KeyStatus keyStatus; keyId.assign(kKeyId1.begin(), kKeyId1.end()); keyStatus.keyId = keyId; keyStatus.type = KeyStatusType::EXPIRED; keyStatusList.push_back(keyStatus); std::vector hKeyStatusList = keyStatusList; keyStatusList.clear(); keyId.clear(); keyId.assign(kKeyId1.begin(), kKeyId1.end()); keyStatus.type = KeyStatusType::USABLE; keyStatusList.push_back(keyStatus); keyId.assign(kKeyId2.begin(), kKeyId2.end()); keyStatus.type = KeyStatusType::OUTPUT_NOT_ALLOWED; keyStatusList.push_back(keyStatus); keyId.assign(kKeyId3.begin(), kKeyId3.end()); keyStatus.type = KeyStatusType::INTERNAL_ERROR; keyStatusList.push_back(keyStatus); keyId.assign(kKeyId4.begin(), kKeyId4.end()); keyStatus.type = KeyStatusType::STATUS_PENDING; keyStatusList.push_back(keyStatus); keyId.assign(kKeyId5.begin(), kKeyId5.end()); keyStatus.type = KeyStatusType::USABLE_IN_FUTURE; keyStatusList.push_back(keyStatus); std::vector hKeyStatusList2 = keyStatusList; } CdmKeyStatusMap mCdmKeysStatus; mCdmKeysStatus[kKeyId1] = wvcdm::kKeyStatusExpired; mPlugin->OnSessionKeysChange(mCdmSessionId, mCdmKeysStatus, false); mPlugin->OnSessionRenewalNeeded(mCdmSessionId); mPlugin->OnExpirationUpdate(mCdmSessionId, NEVER_EXPIRES); mPlugin->OnExpirationUpdate(mCdmSessionId, kExpiryTimeInSeconds); mCdmKeysStatus[kKeyId1] = wvcdm::kKeyStatusUsable; mCdmKeysStatus[kKeyId2] = wvcdm::kKeyStatusOutputNotAllowed; mCdmKeysStatus[kKeyId3] = wvcdm::kKeyStatusInternalError; mCdmKeysStatus[kKeyId4] = wvcdm::kKeyStatusPending; mCdmKeysStatus[kKeyId5] = wvcdm::kKeyStatusUsableInFuture; mPlugin->OnSessionKeysChange(mCdmSessionId, mCdmKeysStatus, true); } TEST_F(WVDrmPluginHalTest, GeneratesProvisioningNeededEvent) { EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) .Times(AtLeast(1)) .WillRepeatedly( DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NEED_PROVISIONING)))); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_EQ(static_cast(Status::ERROR_DRM_NOT_PROVISIONED), ret.getServiceSpecificError()); } TEST_F(WVDrmPluginHalTest, ProvidesExpectedDefaultPropertiesToCdm) { const CdmClientPropertySet *propertySet = nullptr; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly( DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); } SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); 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(WVDrmPluginHalTest, CanSetAppId) { const CdmClientPropertySet *propertySet = nullptr; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly( DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin queries for the security level EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillRepeatedly( DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); } // Test setting an empty string auto ret = mPlugin->setPropertyString(std::string("appId"), std::string("")); EXPECT_TRUE(ret.isOk()); // Test setting an application id before a session is opened. ret = mPlugin->setPropertyString(std::string("appId"), std::string(kAppId)); EXPECT_TRUE(ret.isOk()); SessionId sessionId; ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); // Verify application id is set correctly. EXPECT_STREQ(kAppId.c_str(), propertySet->app_id().c_str()); // Test setting application id while session is opened, this should fail. ret = mPlugin->setPropertyString(std::string("appId"), std::string(kAppId)); EXPECT_EQ(static_cast(Status::ERROR_DRM_UNKNOWN), ret.getServiceSpecificError()); } TEST_P(WVDrmPluginHalOriginTest, CanSetOrigin) { OriginTestVariant params = GetParam(); // Provide expected mock behavior { // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); // Provide expected behavior when mPlugin closes a session EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); } // Note which mock calls we expect EXPECT_CALL(*mCdm, OpenSession(_, _, HasOrigin(params.expectedOrigin), _, _)) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Set the properties & run the test if (!params.origin.empty()) { auto ret = mPlugin->setPropertyString(std::string("origin"), std::string(params.origin)); EXPECT_TRUE(ret.isOk()); } SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); // Test setting an origin while sessions are opened. This should fail. ret = mPlugin->setPropertyString(std::string("origin"), std::string(kOrigin)); EXPECT_FALSE(ret.isOk()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); } INSTANTIATE_TEST_CASE_P( OriginTests, WVDrmPluginHalOriginTest, Values(OriginTestVariant("No Origin", kEmptyString, EMPTY_ORIGIN), OriginTestVariant("With an Origin", kOrigin, kOrigin.c_str()))); TEST_F(WVDrmPluginHalTest, CanSetSecurityLevel) { const CdmClientPropertySet *propertySet = nullptr; EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly( DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); } // Test forcing L3 auto ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L3")); EXPECT_TRUE(ret.isOk()); SessionId sessionId; ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("L3", propertySet->security_level().c_str()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Test returning to L1 on an L3 device (Should Fail) ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L1")); EXPECT_FALSE(ret.isOk()); sessionId.clear(); ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("L3", propertySet->security_level().c_str()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Test returning to L1 on an L1 device ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L1")); EXPECT_TRUE(ret.isOk()); sessionId.clear(); ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("", propertySet->security_level().c_str()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Test un-forcing a level (first forcing to L3 so we have something to reset) ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L3")); EXPECT_TRUE(ret.isOk()); sessionId.clear(); ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("L3", propertySet->security_level().c_str()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("")); EXPECT_TRUE(ret.isOk()); sessionId.clear(); ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("", propertySet->security_level().c_str()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Test nonsense (Should Fail) ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("nonsense")); EXPECT_FALSE(ret.isOk()); sessionId.clear(); ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("", propertySet->security_level().c_str()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Test attempting to force a level with a session open (Should Fail) sessionId.clear(); ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L3")); EXPECT_FALSE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, CanSetPrivacyMode) { const CdmClientPropertySet *propertySet = nullptr; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly( DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); } SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); // Test turning on privacy mode ret = mPlugin->setPropertyString(std::string("privacyMode"), std::string("enable")); EXPECT_TRUE(ret.isOk()); EXPECT_TRUE(propertySet->use_privacy_mode()); // Test turning off privacy mode ret = mPlugin->setPropertyString(std::string("privacyMode"), std::string("disable")); EXPECT_TRUE(ret.isOk()); EXPECT_FALSE(propertySet->use_privacy_mode()); // Test nonsense (Should Fail) ret = mPlugin->setPropertyString(std::string("privacyMode"), std::string("nonsense")); EXPECT_FALSE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, CanSetServiceCertificate) { const CdmClientPropertySet *propertySet = nullptr; static const size_t kPrivacyCertSize = 256; uint8_t privacyCertRaw[kPrivacyCertSize]; FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(privacyCertRaw, sizeof(uint8_t), kPrivacyCertSize, fp); fclose(fp); std::vector privacyCert; privacyCert.assign(privacyCertRaw, privacyCertRaw + kPrivacyCertSize); std::string strPrivacyCert(reinterpret_cast(privacyCertRaw), kPrivacyCertSize); std::vector emptyVector; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly( DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, 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(*mCdm, IsValidServiceCertificate(strPrivacyCert)) .WillOnce(testing::Return(true)) .WillOnce(testing::Return(false)); SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); // Test setting a certificate ret = mPlugin->setPropertyByteArray(std::string("serviceCertificate"), privacyCert); EXPECT_TRUE(ret.isOk()); EXPECT_THAT(propertySet->service_certificate(), ElementsAreArray(privacyCertRaw, kPrivacyCertSize)); // Test clearing a certificate ret = mPlugin->setPropertyByteArray(std::string("serviceCertificate"), emptyVector); EXPECT_TRUE(ret.isOk()); EXPECT_EQ(0u, propertySet->service_certificate().size()); // Test setting a certificate and having it fail ret = mPlugin->setPropertyByteArray(std::string("serviceCertificate"), privacyCert); EXPECT_FALSE(ret.isOk()); EXPECT_EQ(0u, propertySet->service_certificate().size()); } TEST_F(WVDrmPluginHalTest, CanSetSessionSharing) { const CdmClientPropertySet *propertySet = nullptr; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly( DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); } // Test turning on session sharing auto ret = mPlugin->setPropertyString(std::string("sessionSharing"), std::string("enable")); EXPECT_TRUE(ret.isOk()); SessionId sessionId; ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_TRUE(propertySet->is_session_sharing_enabled()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Test turning off session sharing ret = mPlugin->setPropertyString(std::string("sessionSharing"), std::string("disable")); EXPECT_TRUE(ret.isOk()); sessionId.clear(); ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_FALSE(propertySet->is_session_sharing_enabled()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Test nonsense (Should Fail) ret = mPlugin->setPropertyString(std::string("sessionSharing"), std::string("nonsense")); EXPECT_FALSE(ret.isOk()); // Test changing sharing with a session open (Should Fail) sessionId.clear(); ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ret = mPlugin->setPropertyString(std::string("sessionSharing"), std::string("enable")); EXPECT_FALSE(ret.isOk()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, AllowsStoringOfSessionSharingId) { CdmClientPropertySet *propertySet = nullptr; uint32_t sharingId; FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; 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(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly( DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); } SessionId sessionId; auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); propertySet->set_session_sharing_id(sharingId); EXPECT_EQ(sharingId, propertySet->session_sharing_id()); } TEST_F(WVDrmPluginHalTest, CanSetAtscMode) { android::sp> cdm = new StrictMock(); StrictMock crypto; std::string appPackageName = "com.test.package"; const CdmClientPropertySet *propertySet = nullptr; CdmIdentifier mCdmIdAtscModeNotSet; CdmIdentifier mCdmIdAtscModeSet; CdmIdentifier mCdmIdAtscModeReset; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _)) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), SaveArg<2>(&mCdmIdAtscModeNotSet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), SaveArg<2>(&mCdmIdAtscModeSet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), SaveArg<2>(&mCdmIdAtscModeReset), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*cdm, CloseSession(_)).Times(AtLeast(0)); } std::shared_ptr plugin1 = ::ndk::SharedRefBase::make(cdm.get(), appPackageName.c_str(), &crypto, false); ASSERT_TRUE(plugin1) << "Failed to create first drm plugin"; // Verify that ATSC mode is disabled by default SessionId sessionId; auto ret = plugin1->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_TRUE(plugin1); ASSERT_THAT(propertySet, NotNull()); EXPECT_FALSE(propertySet->use_atsc_mode()); EXPECT_EQ(mCdmIdAtscModeNotSet.app_package_name, appPackageName); ret = plugin1->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Verify that ATSC mode can be enabled std::shared_ptr plugin2 = ::ndk::SharedRefBase::make(cdm.get(), appPackageName.c_str(), &crypto, false); ASSERT_TRUE(plugin2) << "Failed to create first drm plugin"; // Test turning on ATSC mode ret = plugin2->setPropertyString(std::string("atscMode"), std::string("enable")); EXPECT_TRUE(ret.isOk()); ret = plugin2->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_TRUE(propertySet->use_atsc_mode()); EXPECT_EQ(mCdmIdAtscModeSet.app_package_name, wvcdm::ATSC_APP_PACKAGE_NAME); ret = plugin2->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Verify that ATSC mode can be enabled and disabled std::shared_ptr plugin3 = ::ndk::SharedRefBase::make(cdm.get(), appPackageName.c_str(), &crypto, false); ASSERT_TRUE(plugin3) << "Failed to create first drm plugin"; // Test turning on ATSC mode ret = plugin3->setPropertyString(std::string("atscMode"), std::string("enable")); EXPECT_TRUE(ret.isOk()); // Test turning off ATSC mode ret = plugin3->setPropertyString(std::string("atscMode"), std::string("disable")); EXPECT_TRUE(ret.isOk()); ret = plugin3->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_FALSE(propertySet->use_atsc_mode()); EXPECT_EQ(mCdmIdAtscModeReset.app_package_name, appPackageName); ret = plugin3->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Test turning on and off ATSC mode. They should be rejected since the SPOID // has been calculated ret = plugin3->setPropertyString(std::string("atscMode"), std::string("enable")); EXPECT_FALSE(ret.isOk()); ret = plugin3->setPropertyString(std::string("atscMode"), std::string("disable")); EXPECT_FALSE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, CanSetDecryptHashProperties) { // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly( DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); } std::vector sessionId(sessionIdRaw, sessionIdRaw + kSessionIdSize); auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); // CDM expects the string property value to be in the following format: // ",," static const std::string frameNumber = ",1"; uint32_t hash = 0xbeef; // crc32 hash const std::vector hashVector(reinterpret_cast(&hash), reinterpret_cast(&hash) + sizeof(uint32_t)); const std::string base64EncodedHash = wvutil::Base64Encode(hashVector); std::string computedHash(sessionId.begin(), sessionId.end()); computedHash.append(frameNumber.c_str()); computedHash.append(base64EncodedHash.c_str()); ret = mPlugin->setPropertyString(std::string("decryptHash"), std::string(computedHash)); EXPECT_FALSE(ret.isOk()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, DoesNotSetDecryptHashProperties) { // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) .WillRepeatedly( DoAll(SetArgPointee<4>(mCdmSessionId), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when mPlugin requests session control info EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); } std::vector sessionId(sessionIdRaw, sessionIdRaw + kSessionIdSize); auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); // CDM expects the string property value to be in the following format: // ",," static const std::string frameNumber = ",1"; static const std::string hash = ",AZaz0+,/"; std::string value(sessionId.begin(), sessionId.end()); value.append(frameNumber.c_str()); // Tests for missing token handling ret = mPlugin->setPropertyString(std::string("decryptHash"), std::string(value)); EXPECT_FALSE(ret.isOk()); // Tests for empty token value.append(","); ret = mPlugin->setPropertyString(std::string("decryptHash"), std::string(value)); EXPECT_FALSE(ret.isOk()); // Tests for invalid sessionId value.clear(); value.append("bad session id"); value.append(",1"); value.append(hash.c_str()); ret = mPlugin->setPropertyString(std::string("decryptHash"), std::string(value)); EXPECT_FALSE(ret.isOk()); // Tests for malformed Base64encode hash, with a "," std::string computedHash(sessionId.begin(), sessionId.end()); computedHash.append(frameNumber.c_str()); computedHash.append(hash.c_str()); ret = mPlugin->setPropertyString(std::string("decryptHash"), std::string(computedHash)); EXPECT_FALSE(ret.isOk()); ret = mPlugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, GetOfflineLicenseIds) { const uint32_t kLicenseCount = 5; uint8_t mockIdsRaw[kLicenseCount * 2][kKeySetIdSize]; FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; for (uint32_t i = 0; i < kLicenseCount * 2; ++i) { fread(mockIdsRaw[i], sizeof(uint8_t), kKeySetIdSize, fp); } fclose(fp); std::vector mockIdsL1; for (uint32_t i = 0; i < kLicenseCount; ++i) { mockIdsL1.push_back( std::string(mockIdsRaw[i], mockIdsRaw[i] + kKeySetIdSize)); } std::vector mockIdsL3; for (uint32_t i = 0; i < kLicenseCount; ++i) { mockIdsL3.push_back( std::string(mockIdsRaw[i + 5], mockIdsRaw[i + 5] + kKeySetIdSize)); } EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN), _)) .WillOnce(DoAll(SetArgPointee<2>(mockIdsL1), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN), _)) .WillOnce(DoAll(SetArgPointee<2>(mockIdsL3), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); std::vector offlineIds; auto ret = mPlugin->getOfflineLicenseKeySetIds(&offlineIds); EXPECT_TRUE(ret.isOk()); size_t index = 0; for (auto id : offlineIds) { EXPECT_THAT(id.keySetId, ElementsAreArray(mockIdsRaw[index++], kKeySetIdSize)); } EXPECT_EQ(kLicenseCount * 2, index); } TEST_F(WVDrmPluginHalTest, GetOfflineLicenseState) { EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); EXPECT_CALL(*mCdm, GetOfflineLicenseState(_, kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN), _)) .WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateActive), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))) .WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateReleasing), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))) .WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateUnknown), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); auto ret = mPlugin->setPropertyString(std::string("securityLevel"), std::string("L1")); EXPECT_TRUE(ret.isOk()); OfflineLicenseState result; ret = mPlugin->getOfflineLicenseState(keySetId, &result); EXPECT_TRUE(ret.isOk()); ASSERT_EQ(OfflineLicenseState::USABLE, result); ret = mPlugin->getOfflineLicenseState(keySetId, &result); EXPECT_TRUE(ret.isOk()); ASSERT_EQ(OfflineLicenseState::INACTIVE, result); ret = mPlugin->getOfflineLicenseState(keySetId, &result); EXPECT_TRUE(ret.isOk()); ASSERT_EQ(OfflineLicenseState::UNKNOWN, result); } TEST_F(WVDrmPluginHalTest, RemoveOfflineLicense) { EXPECT_CALL( *mCdm, RemoveOfflineLicense(_, kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN))) .Times(1); auto ret = mPlugin->removeOfflineLicense(keySetId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, CanStoreAtscLicense) { android::sp> cdm = new StrictMock(); StrictMock crypto; std::string appPackageName = "com.test.package"; const CdmClientPropertySet *propertySet = nullptr; CdmIdentifier mCdmIdAtscModeSet; // Generate ATSC keyset and license data uint8_t atscKeySetIdRaw[kKeySetIdSize]; std::vector licenseDataRaw; const size_t kLicenseDataSize = 512; licenseDataRaw.resize(kLicenseDataSize); FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(atscKeySetIdRaw, sizeof(uint8_t), sizeof(atscKeySetIdRaw), fp); fread(&licenseDataRaw[0], sizeof(uint8_t), licenseDataRaw.size(), fp); fclose(fp); // Generate an ATSC key set ID of the form + // with a total length of kKeySetIdSize std::string atscKeySetId(wvcdm::ATSC_KEY_SET_ID_PREFIX); atscKeySetId.append(wvutil::HexEncode(atscKeySetIdRaw, kKeySetIdSize)); atscKeySetId.resize(kKeySetIdSize); memcpy(atscKeySetIdRaw, atscKeySetId.c_str(), sizeof(atscKeySetIdRaw)); std::string licenseDataBase64 = Base64Encode(licenseDataRaw); std::string atscLicenseData = atscKeySetId + ":" + licenseDataBase64; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _)) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), SaveArg<2>(&mCdmIdAtscModeSet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL( *cdm, StoreAtscLicense(HasOrigin(EMPTY_ORIGIN), wvcdm::kLevelDefault, ElementsAreArray(atscKeySetIdRaw, kKeySetIdSize), ElementsAreArray(licenseDataRaw))) .Times(1); EXPECT_CALL(*cdm, CloseSession(_)).Times(1); } std::shared_ptr plugin = ::ndk::SharedRefBase::make( cdm.get(), appPackageName, &crypto, false); // Test turning on ATSC mode auto ret = plugin->setPropertyString(std::string("atscMode"), std::string("enable")); EXPECT_TRUE(ret.isOk()); ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_TRUE(propertySet->use_atsc_mode()); EXPECT_EQ(mCdmIdAtscModeSet.app_package_name, wvcdm::ATSC_APP_PACKAGE_NAME); ret = plugin->setPropertyString(std::string("storeAtscLicense"), atscLicenseData); EXPECT_TRUE(ret.isOk()); ret = plugin->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); } TEST_F(WVDrmPluginHalTest, FailsToStoreAtscLicense) { android::sp> cdm1 = new StrictMock(); android::sp> cdm2 = new StrictMock(); android::sp> cdm3 = new StrictMock(); StrictMock crypto; std::string appPackageName = "com.test.package"; const CdmClientPropertySet *propertySet = nullptr; CdmIdentifier mCdmIdAtscModeSet; // Generate ATSC keyset and license data uint8_t atscKeySetIdRaw[kKeySetIdSize]; std::vector licenseDataRaw; const size_t kLicenseDataSize = 512; licenseDataRaw.resize(kLicenseDataSize); FILE *fp = fopen("/dev/urandom", "r"); ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; fread(atscKeySetIdRaw, sizeof(uint8_t), sizeof(atscKeySetIdRaw), fp); fread(&licenseDataRaw[0], sizeof(uint8_t), licenseDataRaw.size(), fp); fclose(fp); std::string atscKeySetId(wvcdm::ATSC_KEY_SET_ID_PREFIX); atscKeySetId.append(wvutil::HexEncode(&atscKeySetIdRaw[0], kKeySetIdSize)); atscKeySetId.resize(kKeySetIdSize); memcpy(atscKeySetIdRaw, atscKeySetId.c_str(), sizeof(atscKeySetIdRaw)); std::string licenseDataBase64 = Base64Encode(licenseDataRaw); std::string atscLicenseData = atscKeySetId + ":" + licenseDataBase64; // Provide expected mock behavior { // No mock behavior expected for plugin1/cdm1 // For plugin2/cdm2 // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*cdm2, OpenSession(_, _, _, _, _)) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), SaveArg<2>(&mCdmIdAtscModeSet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when plugin2 requests session control info EXPECT_CALL(*cdm2, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*cdm2, CloseSession(_)).Times(1); // For plugin3/cdm3 // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(*cdm3, OpenSession(_, _, _, _, _)) .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), SaveArg<2>(&mCdmIdAtscModeSet), testing::Return(CdmResponseType(wvcdm::NO_ERROR)))); // Provide expected behavior when plugin3 requests session control info EXPECT_CALL(*cdm3, QueryOemCryptoSessionId(mCdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(*cdm3, CloseSession(_)).Times(1); } // Try storing an ATSC license without opening a session. Should fail. std::shared_ptr plugin1 = ::ndk::SharedRefBase::make( cdm1.get(), appPackageName.c_str(), &crypto, false); auto ret = plugin1->setPropertyString(std::string("storeAtscLicense"), atscLicenseData); EXPECT_TRUE(!ret.isOk()); EXPECT_EQ(static_cast(Status::ERROR_DRM_CANNOT_HANDLE), ret.getServiceSpecificError()); // Try storing an ATSC license without setting ATSC mode. Should fail. std::shared_ptr plugin2 = ::ndk::SharedRefBase::make( cdm2.get(), appPackageName.c_str(), &crypto, false); ret = plugin2->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_FALSE(propertySet->use_atsc_mode()); EXPECT_NE(mCdmIdAtscModeSet.app_package_name, wvcdm::ATSC_APP_PACKAGE_NAME); ret = plugin2->setPropertyString(std::string("storeAtscLicense"), atscLicenseData); EXPECT_TRUE(!ret.isOk()); EXPECT_EQ(static_cast(Status::ERROR_DRM_CANNOT_HANDLE), ret.getServiceSpecificError()); ret = plugin2->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); // Try storing an ATSC license // (a) Without ATSC key set prefix // (b) Only ATSC key set // (c) Only license data // (d) Invalid Base64 license data // (e) No ATSC key set // (f) No license data // All these should fail. std::shared_ptr plugin3 = ::ndk::SharedRefBase::make( cdm3.get(), appPackageName.c_str(), &crypto, false); // Test turning on ATSC mode ret = plugin3->setPropertyString(std::string("atscMode"), std::string("enable")); EXPECT_TRUE(ret.isOk()); ret = plugin3->openSession(SecurityLevel::DEFAULT, &sessionId); EXPECT_TRUE(ret.isOk()); ASSERT_THAT(propertySet, NotNull()); EXPECT_TRUE(propertySet->use_atsc_mode()); EXPECT_EQ(mCdmIdAtscModeSet.app_package_name, wvcdm::ATSC_APP_PACKAGE_NAME); // (a) No ATSC key set prefix atscKeySetId.assign(wvutil::HexEncode(&atscKeySetIdRaw[0], kKeySetIdSize)); atscKeySetId.resize(kKeySetIdSize); atscLicenseData = atscKeySetId + ":" + licenseDataBase64; ret = plugin3->setPropertyString(std::string("storeAtscLicense"), atscLicenseData); EXPECT_TRUE(!ret.isOk()); EXPECT_EQ(static_cast(Status::BAD_VALUE), ret.getServiceSpecificError()); // (b) Only ATSC key set atscKeySetId.assign(wvcdm::ATSC_KEY_SET_ID_PREFIX); atscKeySetId.append(wvutil::HexEncode(&atscKeySetIdRaw[0], kKeySetIdSize)); atscKeySetId.resize(kKeySetIdSize); ret = plugin3->setPropertyString(std::string("storeAtscLicense"), atscKeySetId); EXPECT_TRUE(!ret.isOk()); // (c) Only license data ret = plugin3->setPropertyString(std::string("storeAtscLicense"), licenseDataBase64); EXPECT_TRUE(!ret.isOk()); // (d) Invalid Base64 license data licenseDataBase64.append("$"); atscLicenseData = atscKeySetId + ":" + licenseDataBase64; ret = plugin3->setPropertyString(std::string("storeAtscLicense"), atscLicenseData); EXPECT_TRUE(!ret.isOk()); // (e) No ATSC key set licenseDataBase64 = Base64Encode(licenseDataRaw); atscLicenseData = ":" + licenseDataBase64; ret = plugin3->setPropertyString(std::string("storeAtscLicense"), atscLicenseData); EXPECT_TRUE(!ret.isOk()); // (f) No license data atscLicenseData = atscKeySetId + ":"; ret = plugin3->setPropertyString(std::string("storeAtscLicense"), atscLicenseData); EXPECT_TRUE(!ret.isOk()); ret = plugin3->closeSession(sessionId); EXPECT_TRUE(ret.isOk()); } } // namespace widevine } // namespace drm } // namespace hardware } // namespace wvdrm