[ Partial cherry-pick of http://go/wvgerrit/186230 ] The removeOfflineLicense() API in the Media DRM plug would attempt to remove the specified license from L1, then retry L3 if L1 failed for any reason. This causes error emitted by L1 to be masked by errors emitted from L3. In particular, if an internal error occurs on L1 when removing the license, because the plugin would then try L3 which does not contain the license, the app will receive either a "does not exist" or "needs provisioning" error from L3. This CL changes the plugin to first determines which security level the license exists for. Then only attempts removal on that security level. Bug: 301910628 Bug: 291181955 Bug: 296300842 Bug: 302612540 Test: MediaDrmParameterizedTests GTS on bluejay Merged from https://widevine-internal-review.googlesource.com/187611 Merged from https://widevine-internal-review.googlesource.com/187832 Change-Id: I3d3975f945d2e97cfa9d866baf6ca5cf901f8af5
3299 lines
127 KiB
C++
3299 lines
127 KiB
C++
//
|
|
// 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 <stdio.h>
|
|
#include <utils/Log.h>
|
|
|
|
#include <list>
|
|
#include <ostream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#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<uint8_t> 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<const uint8_t *>("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<std::string> *),
|
|
(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 <uint8_t DIGIT>
|
|
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<uint8_t> sessionId;
|
|
uint8_t keySetIdRaw[kKeySetIdSize];
|
|
uint8_t sessionIdRaw[kSessionIdSize];
|
|
CdmSessionId mCdmSessionId;
|
|
KeySetId keySetId;
|
|
|
|
android::sp<StrictMock<MockCDM>> mCdm = nullptr;
|
|
std::shared_ptr<WVDrmPlugin> mPlugin = nullptr;
|
|
std::string mAppPackageName;
|
|
StrictMock<MockCrypto> 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<char *>(keySetIdRaw),
|
|
kKeySetIdSize);
|
|
keySetId.keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize);
|
|
|
|
// Set default return values for gMock
|
|
DefaultValue<CdmResponseType>::Set(CdmResponseType(wvcdm::NO_ERROR));
|
|
DefaultValue<OEMCryptoResult>::Set(OEMCrypto_SUCCESS);
|
|
DefaultValue<bool>::Set(true);
|
|
|
|
mCdm = new StrictMock<MockCDM>();
|
|
ASSERT_TRUE(mCdm) << "Failed to create mocked CDM";
|
|
mPlugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
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<OriginTestVariant> {
|
|
};
|
|
|
|
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<int>(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<char *>(keySetIdRaw),
|
|
kKeySetIdSize);
|
|
std::vector<uint8_t> keySetId;
|
|
keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize);
|
|
|
|
CdmInitData mCdmInitData(reinterpret_cast<char *>(initDataRaw),
|
|
kInitDataSize);
|
|
std::vector<uint8_t> 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<char *>(psshBoxRaw), kPsshBoxSize);
|
|
std::vector<uint8_t> psshBox;
|
|
psshBox.assign(psshBoxRaw, psshBoxRaw + kPsshBoxSize);
|
|
|
|
CdmKeyMessage mCdmRequest(requestRaw, requestRaw + kRequestSize);
|
|
|
|
std::map<std::string, std::string> parameters = {
|
|
{"paddingScheme", "BUBBLE WRAP"},
|
|
{"favorite-particle", "tetraquark"},
|
|
{"answer", "6 * 9"},
|
|
};
|
|
CdmAppParameterMap mCdmParameters;
|
|
for (const auto &pair : parameters) {
|
|
mCdmParameters[pair.first] = pair.second;
|
|
}
|
|
|
|
std::vector<KeyValue> 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<uint8_t> &initDataIn;
|
|
const CdmInitData &initDataOut;
|
|
};
|
|
|
|
// We run the same set of operations on several sets of data, simulating
|
|
// different valid calling patterns.
|
|
TestSet testSets[] = {
|
|
{kIsoBmffMimeType, psshBox, 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<uint8_t> &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<uint8_t> response(responseRaw, responseRaw + kResponseSize);
|
|
|
|
memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1);
|
|
CdmKeySetId mCdmKeySetId(reinterpret_cast<char *>(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<uint8_t> response;
|
|
response.assign(responseRaw, responseRaw + kResponseSize);
|
|
std::vector<uint8_t> 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<std::string, std::string> 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<KeyValue> 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<std::string, std::string>::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<uint8_t> 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<uint8_t> 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<uint8_t> 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<int>(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<uint8_t> 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<uint8_t> cert;
|
|
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> specialResponse;
|
|
specialResponse.assign(kUnprovisionResponse,
|
|
kUnprovisionResponse + kUnprovisionResponseSize);
|
|
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
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<WVDrmPlugin> plugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
cdm.get(), mAppPackageName.c_str(), &mCrypto, true);
|
|
ASSERT_TRUE(plugin) << "Failed to create drm plugin";
|
|
|
|
StrictMock<MockCrypto> crypto;
|
|
ProvideProvisionResponseResult result;
|
|
auto ret = plugin->provideProvisionResponse(specialResponse, &result);
|
|
EXPECT_TRUE(ret.isOk());
|
|
}
|
|
|
|
TEST_F(WVDrmPluginHalTest, WillNotUnprovisionWithoutOriginOrSpoid) {
|
|
std::vector<uint8_t> 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<uint8_t> 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<SecureStop> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> spoids[kDeviceCount][kAppCount][kOriginCount]
|
|
[kPluginCount];
|
|
|
|
StrictMock<MockCrypto> crypto;
|
|
|
|
for (size_t deviceIndex = 0; deviceIndex < kDeviceCount; ++deviceIndex) {
|
|
const std::string &deviceId = kDeviceIds[deviceIndex];
|
|
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
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<WVDrmPlugin> plugin =
|
|
::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
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<uint8_t> 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<uint8_t> &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<uint8_t> &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<uint8_t> spoid[kSpoidQuery];
|
|
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
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<MockCrypto> crypto;
|
|
|
|
for (int i = 0; i < kSpoidQuery; ++i) {
|
|
std::shared_ptr<WVDrmPlugin> plugin =
|
|
::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
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<uint8_t> spoid[kSpoidQuery];
|
|
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
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<MockCrypto> crypto;
|
|
|
|
for (size_t i = 0; i < kSpoidQuery; ++i) {
|
|
std::shared_ptr<WVDrmPlugin> plugin =
|
|
::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
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<uint8_t> 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<uint8_t> spoid[kSpoidQuery];
|
|
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
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<MockCrypto> crypto;
|
|
std::shared_ptr<WVDrmPlugin> plugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
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<int>(Status::ERROR_DRM_NOT_PROVISIONED),
|
|
ret.getServiceSpecificError());
|
|
|
|
std::vector<uint8_t> 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<WVDrmPlugin>(
|
|
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<uint8_t> spoidL1;
|
|
std::vector<uint8_t> spoidL3[kSpoidQuery];
|
|
|
|
// Set expectations for the first mPlugin instance
|
|
android::sp<StrictMock<MockCDM>> cdm1 = new StrictMock<MockCDM>();
|
|
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<MockCrypto> crypto;
|
|
std::shared_ptr<WVDrmPlugin> plugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
cdm1.get(), kAppPackageName.c_str(), &crypto, true);
|
|
ASSERT_TRUE(plugin) << "Failed to create drm plugin";
|
|
|
|
std::vector<uint8_t> sessionId;
|
|
auto ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId);
|
|
EXPECT_TRUE(ret.isOk());
|
|
|
|
ret = plugin->closeSession(sessionId);
|
|
EXPECT_TRUE(ret.isOk());
|
|
|
|
std::vector<uint8_t> 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<int>(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<StrictMock<MockCDM>> cdm2 = new StrictMock<MockCDM>();
|
|
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<WVDrmPlugin>(
|
|
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<uint8_t> keyId;
|
|
std::vector<uint8_t> input;
|
|
std::vector<uint8_t> iv;
|
|
std::vector<uint8_t> 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<uint8_t> result;
|
|
ret = mPlugin->encrypt(sessionId, keyId, input, iv, &result);
|
|
// NO_INIT is converted to Status::ERROR_DRM_UNKNOWN
|
|
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_UNKNOWN),
|
|
ret.getServiceSpecificError());
|
|
|
|
ret = mPlugin->decrypt(sessionId, keyId, input, iv, &result);
|
|
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_UNKNOWN),
|
|
ret.getServiceSpecificError());
|
|
|
|
ret = mPlugin->sign(sessionId, keyId, input, &result);
|
|
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_UNKNOWN),
|
|
ret.getServiceSpecificError());
|
|
|
|
bool match = false;
|
|
ret = mPlugin->verify(sessionId, keyId, input, signature, &match);
|
|
EXPECT_EQ(static_cast<int>(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<uint8_t> keyId;
|
|
keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE);
|
|
std::vector<uint8_t> input;
|
|
input.assign(inputRaw, inputRaw + kDataSize);
|
|
std::vector<uint8_t> iv;
|
|
iv.assign(ivRaw, ivRaw + KEY_IV_SIZE);
|
|
std::vector<uint8_t> 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<uint8_t> keyId;
|
|
keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE);
|
|
std::vector<uint8_t> input;
|
|
input.assign(inputRaw, inputRaw + kDataSize);
|
|
std::vector<uint8_t> iv;
|
|
iv.assign(ivRaw, ivRaw + KEY_IV_SIZE);
|
|
std::vector<uint8_t> 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<uint8_t> keyId;
|
|
keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE);
|
|
std::vector<uint8_t> 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<uint8_t> signature;
|
|
ret = mPlugin->sign(sessionId, keyId, message, &signature);
|
|
EXPECT_TRUE(ret.isOk());
|
|
|
|
const std::vector<uint8_t> 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<uint8_t> keyId;
|
|
keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE);
|
|
std::vector<uint8_t> message;
|
|
message.assign(messageRaw, messageRaw + kDataSize);
|
|
std::vector<uint8_t> 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<uint8_t> keyId;
|
|
std::vector<KeyStatus> keyStatusList;
|
|
|
|
KeyStatus keyStatus;
|
|
keyId.assign(kKeyId1.begin(), kKeyId1.end());
|
|
keyStatus.keyId = keyId;
|
|
keyStatus.type = KeyStatusType::EXPIRED;
|
|
keyStatusList.push_back(keyStatus);
|
|
|
|
std::vector<KeyStatus> 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<KeyStatus> 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<int>(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<int>(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<uint8_t> privacyCert;
|
|
privacyCert.assign(privacyCertRaw, privacyCertRaw + kPrivacyCertSize);
|
|
std::string strPrivacyCert(reinterpret_cast<char *>(privacyCertRaw),
|
|
kPrivacyCertSize);
|
|
std::vector<uint8_t> 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<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> 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<WVDrmPlugin> plugin1 =
|
|
::ndk::SharedRefBase::make<WVDrmPlugin>(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<WVDrmPlugin> plugin2 =
|
|
::ndk::SharedRefBase::make<WVDrmPlugin>(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<WVDrmPlugin> plugin3 =
|
|
::ndk::SharedRefBase::make<WVDrmPlugin>(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<uint8_t> 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:
|
|
// "<sessionId>,<frameNumber>,<base64encodedHash>"
|
|
static const std::string frameNumber = ",1";
|
|
uint32_t hash = 0xbeef; // crc32 hash
|
|
const std::vector<uint8_t> hashVector(reinterpret_cast<uint8_t *>(&hash),
|
|
reinterpret_cast<uint8_t *>(&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<uint8_t> 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:
|
|
// "<sessionId>,<frameNumber>,<base64encodedHash>"
|
|
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, GetOfflineLicenseKeySetIds_NonAtscMode) {
|
|
const std::vector<CdmKeySetId> cdmKeySetIdsL1 = {
|
|
// Non-ATSC key set IDs
|
|
"ksid1111", "ksid2222", "ksid3333", "ksid4444",
|
|
// ATSC key set IDs.
|
|
"atscksid1111", "atscksid2222", "atscksid3333", "atsc_group1_profile1",
|
|
"atsc_group1_profile7"};
|
|
const std::vector<CdmKeySetId> cdmKeySetIdsL3 = {
|
|
// Non-ATSC key set IDs
|
|
"ksid5555", "ksid6666", "ksid7777", "ksid8888",
|
|
// ATSC key set IDs.
|
|
"atscksid5555", "atscksid6666", "atscksid7777", "atsc_group3_profile1",
|
|
"atsc_group3_profile7"};
|
|
// Expect non-ATSC key set IDs (order is important).
|
|
const std::vector<CdmKeySetId> expectedCdmKeySetIds = {
|
|
// From L1
|
|
"ksid1111", "ksid2222", "ksid3333", "ksid4444",
|
|
// From L3
|
|
"ksid5555", "ksid6666", "ksid7777", "ksid8888"};
|
|
|
|
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, _, NotNull()))
|
|
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL1),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
|
|
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL3, _, NotNull()))
|
|
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL3),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
|
|
std::vector<KeySetId> offlineKeySetIds;
|
|
const auto ret = mPlugin->getOfflineLicenseKeySetIds(&offlineKeySetIds);
|
|
ASSERT_TRUE(ret.isOk());
|
|
std::vector<CdmKeySetId> offlineCdmKeySetIds;
|
|
for (const auto &keySetId : offlineKeySetIds) {
|
|
offlineCdmKeySetIds.emplace_back(keySetId.keySetId.begin(),
|
|
keySetId.keySetId.end());
|
|
}
|
|
|
|
EXPECT_EQ(expectedCdmKeySetIds.size(), offlineCdmKeySetIds.size());
|
|
EXPECT_EQ(expectedCdmKeySetIds, offlineCdmKeySetIds);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginHalTest, GetOfflineLicenseKeySetIds_AtscMode) {
|
|
const std::vector<CdmKeySetId> cdmKeySetIdsL1 = {
|
|
// Non-ATSC key set IDs
|
|
"ksid1111", "ksid2222", "ksid3333", "ksid4444",
|
|
// ATSC key set IDs.
|
|
"atscksid1111", "atscksid2222", "atscksid3333", "atsc_group1_profile1",
|
|
"atsc_group1_profile7"};
|
|
const std::vector<CdmKeySetId> cdmKeySetIdsL3 = {
|
|
// Non-ATSC key set IDs
|
|
"ksid5555", "ksid6666", "ksid7777", "ksid8888",
|
|
// ATSC key set IDs.
|
|
"atscksid5555", "atscksid6666", "atscksid7777", "atsc_group3_profile1",
|
|
"atsc_group3_profile7"};
|
|
// Expect ATSC key set IDs (order is important).
|
|
const std::vector<CdmKeySetId> expectedCdmKeySetIds = {
|
|
// From L1
|
|
"atscksid1111", "atscksid2222", "atscksid3333", "atsc_group1_profile1",
|
|
"atsc_group1_profile7",
|
|
// From L3
|
|
"atscksid5555", "atscksid6666", "atscksid7777", "atsc_group3_profile1",
|
|
"atsc_group3_profile7"};
|
|
|
|
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, _, NotNull()))
|
|
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL1),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
|
|
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL3, _, NotNull()))
|
|
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL3),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
|
|
mPlugin->setPropertyString("atscMode", "enable");
|
|
|
|
std::vector<KeySetId> offlineKeySetIds;
|
|
const auto ret = mPlugin->getOfflineLicenseKeySetIds(&offlineKeySetIds);
|
|
ASSERT_TRUE(ret.isOk());
|
|
std::vector<CdmKeySetId> offlineCdmKeySetIds;
|
|
for (const auto &keySetId : offlineKeySetIds) {
|
|
offlineCdmKeySetIds.emplace_back(keySetId.keySetId.begin(),
|
|
keySetId.keySetId.end());
|
|
}
|
|
|
|
EXPECT_EQ(expectedCdmKeySetIds.size(), offlineCdmKeySetIds.size());
|
|
EXPECT_EQ(expectedCdmKeySetIds, offlineCdmKeySetIds);
|
|
}
|
|
|
|
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_L1) {
|
|
// Key set to remove.
|
|
const CdmKeySetId cdmKeySetId = "ksidDEADBEAF";
|
|
const KeySetId keySetId{
|
|
std::vector<uint8_t>(cdmKeySetId.begin(), cdmKeySetId.end())};
|
|
// Desired key set ID found in L1.
|
|
const std::vector<CdmKeySetId> cdmKeySetIdsL1 = {"ksid1234", "ksid9876",
|
|
"ksid9999", cdmKeySetId,
|
|
"ksidBAD", "ksidCAFEB0BA"};
|
|
|
|
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, _, NotNull()))
|
|
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL1),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
// Only call L1.
|
|
EXPECT_CALL(*mCdm, RemoveOfflineLicense(cdmKeySetId, kSecurityLevelL1, _))
|
|
.WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR)));
|
|
EXPECT_CALL(*mCdm, RemoveOfflineLicense(_, kSecurityLevelL3, _)).Times(0);
|
|
|
|
const auto status = mPlugin->removeOfflineLicense(keySetId);
|
|
ASSERT_TRUE(status.isOk());
|
|
}
|
|
|
|
TEST_F(WVDrmPluginHalTest, RemoveOfflineLicense_L3) {
|
|
// Key set to remove.
|
|
const CdmKeySetId cdmKeySetId = "ksidDEADBEAF";
|
|
const KeySetId keySetId{
|
|
std::vector<uint8_t>(cdmKeySetId.begin(), cdmKeySetId.end())};
|
|
// Desired key set ID is not found in L1.
|
|
const std::vector<CdmKeySetId> cdmKeySetIdsL1 = {"ksid1234", "ksid9876",
|
|
"ksid9999"};
|
|
// Desired key set ID found in L3.
|
|
const std::vector<CdmKeySetId> cdmKeySetIdsL3 = {
|
|
"ksidDEADC0DE", "ksid1337", cdmKeySetId, "ksidBAD", "ksidCAFEB0BA"};
|
|
|
|
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, _, NotNull()))
|
|
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL1),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL3, _, NotNull()))
|
|
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL3),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
// Only call L3.
|
|
EXPECT_CALL(*mCdm, RemoveOfflineLicense(_, kSecurityLevelL1, _)).Times(0);
|
|
EXPECT_CALL(*mCdm, RemoveOfflineLicense(cdmKeySetId, kSecurityLevelL3, _))
|
|
.WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR)));
|
|
|
|
const auto status = mPlugin->removeOfflineLicense(keySetId);
|
|
ASSERT_TRUE(status.isOk());
|
|
}
|
|
|
|
TEST_F(WVDrmPluginHalTest, RemoveOfflineLicense_NotFound) {
|
|
// Key set to remove.
|
|
const CdmKeySetId cdmKeySetId = "ksidDEADBEAF";
|
|
const KeySetId keySetId{
|
|
std::vector<uint8_t>(cdmKeySetId.begin(), cdmKeySetId.end())};
|
|
// Desired key set ID is not found in L1.
|
|
const std::vector<CdmKeySetId> cdmKeySetIdsL1 = {"ksid1234", "ksid9876",
|
|
"ksid9999"};
|
|
// Desired key set ID is not found in L3.
|
|
const std::vector<CdmKeySetId> cdmKeySetIdsL3 = {"ksidDEADC0DE", "ksid1337",
|
|
"ksidBAD", "ksidCAFEB0BA"};
|
|
|
|
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, _, NotNull()))
|
|
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL1),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL3, _, NotNull()))
|
|
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL3),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
// No call to RemoveOfflineLicense should be made.
|
|
EXPECT_CALL(*mCdm, RemoveOfflineLicense(_, _, _)).Times(0);
|
|
|
|
const auto status = mPlugin->removeOfflineLicense(keySetId);
|
|
ASSERT_FALSE(status.isOk());
|
|
}
|
|
|
|
TEST_F(WVDrmPluginHalTest, CanStoreAtscLicense) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> 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<uint8_t> 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 <ATSC prefix>+<random hex>
|
|
// 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<WVDrmPlugin> plugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
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<StrictMock<MockCDM>> cdm1 = new StrictMock<MockCDM>();
|
|
android::sp<StrictMock<MockCDM>> cdm2 = new StrictMock<MockCDM>();
|
|
android::sp<StrictMock<MockCDM>> cdm3 = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> 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<uint8_t> 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<WVDrmPlugin> plugin1 =
|
|
::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
cdm1.get(), appPackageName.c_str(), &crypto, false);
|
|
|
|
auto ret = plugin1->setPropertyString(std::string("storeAtscLicense"),
|
|
atscLicenseData);
|
|
EXPECT_TRUE(!ret.isOk());
|
|
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_CANNOT_HANDLE),
|
|
ret.getServiceSpecificError());
|
|
|
|
// Try storing an ATSC license without setting ATSC mode. Should fail.
|
|
std::shared_ptr<WVDrmPlugin> plugin2 =
|
|
::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
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<int>(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<WVDrmPlugin> plugin3 =
|
|
::ndk::SharedRefBase::make<WVDrmPlugin>(
|
|
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<int>(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
|