Below are a set of CLs being merged from the wv cdm repo to the android repo. * Fix handling of OEM Cert public key. Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/27921 ] This is a potential fix for b/36656190. Set aside public key on first call to get the public key, and use it afterwards. This gets rid of extra calls to OEMCrypto_GetOEMPublicCertificate(), which has side-effect of staging the OEM private key. This also fixes a problem where the public cert string was not being trimmed to match the size returned by OEMCrypto_GetOEMPublicCertificate(). * Complete provisioning request/response for Provisioning 3.0 Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/27780 ] Fix bug on provisioning request path where GenerateDerivedKeys() was being called when preparing to generate the signature. Add message signature verification, and call correct OEMCrypto routine to rewrap the private key (OEMCrypto_RewrapDeviceRSAKey30). * Implement Cdm::deleteAllUsageRecords() Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/27780 ] Delete all usage records for current origin. Removes usage records from file system and retains the PSTs. The deletes any usage entries matching those PSTs held by OEMCrypto. BUG: 35319024 * Remove stringencoders library from third_party. Author: Jacob Trimble <modmaker@google.com> [ Merge of http://go/wvgerrit/27585 ] We have a fork of the stringencoders library that we use for base64 encoding. This reimplements base64 encoding to remove the extra dependency and to reduce the amount of code. * Add Cdm::deleteUsageRecord() based on key_set_id. Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/27605 ] Delete specified usage record from file system usage info and from OEMCrypto. BUG: 35319024 * Modifiable OEMCrypto Author: Fred Gylys-Colwell <fredgc@google.com> [ Merge of http://go/wvgerrit/24729 ] This CL adds a new variant of the OEMCrypto mock code that adjusts its behavior based on a configuration file. This is intended for testing. For example, a tester can set current_hdcp to 2 in the options.txt file, push it to the device, and verify that a license is granted for HDCP 2.0. Then the tester can edit the value of current_hdcp to 1 and push the file to the device. Playback should stop because the license is no longer valid. This variant uses a real level 1 liboemcrypto.so to push data to a secure buffer. That means we can test playback for a license that requires secure buffers on an Android device with real secure buffers. BUG: 35141278 BUG: 37353534 BUG: 71650075 Test: Not currently passing. Will be addressed in a subsequent commit in the chain. Change-Id: I58443c510919e992bb455192e70373490a00e2b6
2272 lines
81 KiB
C++
2272 lines
81 KiB
C++
//
|
|
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
//
|
|
//#define LOG_NDEBUG 0
|
|
#define LOG_TAG "WVDrmPluginTest"
|
|
#include <utils/Log.h>
|
|
|
|
#include <android/hardware/drm/1.0/types.h>
|
|
#include <android/hardware/drm/1.0/IDrmPlugin.h>
|
|
#include <android/hardware/drm/1.0/IDrmPluginListener.h>
|
|
|
|
#include <stdio.h>
|
|
#include <ostream>
|
|
#include <string>
|
|
#include <list>
|
|
|
|
#include "cdm_client_property_set.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include "media/stagefright/foundation/ABase.h"
|
|
#include "media/stagefright/MediaErrors.h"
|
|
#include "wv_cdm_constants.h"
|
|
#include "wv_cdm_types.h"
|
|
#include "wv_content_decryption_module.h"
|
|
#include "TypeConvert.h"
|
|
#include "WVDrmPlugin.h"
|
|
#include "WVErrors.h"
|
|
|
|
namespace wvdrm {
|
|
namespace hardware {
|
|
namespace drm {
|
|
namespace V1_0 {
|
|
namespace widevine {
|
|
|
|
using ::android::hardware::drm::V1_0::EventType;
|
|
using ::android::hardware::drm::V1_0::KeyStatus;
|
|
using ::android::hardware::drm::V1_0::KeyStatusType;
|
|
using ::android::hardware::drm::V1_0::Status;
|
|
using ::android::hardware::drm::V1_0::widevine::toHidlVec;
|
|
using ::android::hardware::hidl_vec;
|
|
|
|
using ::testing::_;
|
|
using ::testing::AllOf;
|
|
using ::testing::Args;
|
|
using ::testing::AtLeast;
|
|
using ::testing::DefaultValue;
|
|
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::StrictMock;
|
|
using ::testing::StrEq;
|
|
using ::testing::Test;
|
|
using ::testing::Values;
|
|
using ::testing::WithParamInterface;
|
|
using ::testing::internal::ElementsAreArrayMatcher;
|
|
|
|
using wvcdm::kCertificateWidevine;
|
|
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::CdmAppParameterMap;
|
|
using wvcdm::CdmCertificateType;
|
|
using wvcdm::CdmClientPropertySet;
|
|
using wvcdm::CdmIdentifier;
|
|
using wvcdm::CdmInitData;
|
|
using wvcdm::CdmKeyMessage;
|
|
using wvcdm::CdmKeyResponse;
|
|
using wvcdm::CdmKeyRequest;
|
|
using wvcdm::CdmKeySetId;
|
|
using wvcdm::CdmKeySystem;
|
|
using wvcdm::CdmLicenseType;
|
|
using wvcdm::CdmProvisioningRequest;
|
|
using wvcdm::CdmProvisioningResponse;
|
|
using wvcdm::CdmQueryMap;
|
|
using wvcdm::CdmSecureStopId;
|
|
using wvcdm::CdmSecurityLevel;
|
|
using wvcdm::CdmUsageInfo;
|
|
using wvcdm::CdmUsageInfoReleaseMessage;
|
|
using wvcdm::EMPTY_ORIGIN;
|
|
using wvcdm::KEY_ID_SIZE;
|
|
using wvcdm::KEY_IV_SIZE;
|
|
using wvcdm::KEY_SET_ID_PREFIX;
|
|
using wvcdm::NEVER_EXPIRES;
|
|
using wvcdm::QUERY_KEY_DEVICE_ID;
|
|
using wvcdm::QUERY_KEY_MAX_NUMBER_OF_SESSIONS;
|
|
using wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS;
|
|
using wvcdm::QUERY_KEY_OEMCRYPTO_API_VERSION;
|
|
using wvcdm::QUERY_KEY_OEMCRYPTO_SESSION_ID;
|
|
using wvcdm::QUERY_KEY_PROVISIONING_ID;
|
|
using wvcdm::QUERY_KEY_SECURITY_LEVEL;
|
|
using wvcdm::QUERY_KEY_SYSTEM_ID;
|
|
using wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1;
|
|
using wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3;
|
|
using wvcdm::SecurityLevel;
|
|
using wvcdm::SESSION_ID_PREFIX;
|
|
using wvcdm::WvCdmEventListener;
|
|
|
|
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;
|
|
}
|
|
|
|
class MockCDM : public WvContentDecryptionModule {
|
|
public:
|
|
MOCK_METHOD5(OpenSession, CdmResponseType(const CdmKeySystem&,
|
|
CdmClientPropertySet*,
|
|
const CdmIdentifier&,
|
|
WvCdmEventListener*,
|
|
CdmSessionId*));
|
|
|
|
MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&));
|
|
|
|
MOCK_METHOD9(GenerateKeyRequest,
|
|
CdmResponseType(const CdmSessionId&, const CdmKeySetId&,
|
|
const std::string&, const CdmInitData&,
|
|
const CdmLicenseType, CdmAppParameterMap&,
|
|
CdmClientPropertySet*, const CdmIdentifier&,
|
|
CdmKeyRequest*));
|
|
|
|
MOCK_METHOD3(AddKey, CdmResponseType(const CdmSessionId&,
|
|
const CdmKeyResponse&,
|
|
CdmKeySetId*));
|
|
|
|
MOCK_METHOD1(RemoveKeys, CdmResponseType(const CdmSessionId&));
|
|
|
|
MOCK_METHOD2(RestoreKey, CdmResponseType(const CdmSessionId&,
|
|
const CdmKeySetId&));
|
|
|
|
MOCK_METHOD3(QueryStatus, CdmResponseType(SecurityLevel, const std::string&,
|
|
std::string*));
|
|
|
|
MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&,
|
|
CdmQueryMap*));
|
|
|
|
MOCK_METHOD2(QueryOemCryptoSessionId, CdmResponseType(const CdmSessionId&,
|
|
CdmQueryMap*));
|
|
|
|
MOCK_METHOD5(GetProvisioningRequest, CdmResponseType(CdmCertificateType,
|
|
const std::string&,
|
|
const CdmIdentifier&,
|
|
CdmProvisioningRequest*,
|
|
std::string*));
|
|
|
|
MOCK_METHOD4(HandleProvisioningResponse,
|
|
CdmResponseType(const CdmIdentifier&, CdmProvisioningResponse&,
|
|
std::string*, std::string*));
|
|
|
|
MOCK_METHOD2(Unprovision, CdmResponseType(CdmSecurityLevel,
|
|
const CdmIdentifier&));
|
|
|
|
MOCK_METHOD2(GetUsageInfo, CdmResponseType(const std::string&,
|
|
CdmUsageInfo*));
|
|
|
|
MOCK_METHOD3(GetUsageInfo, CdmResponseType(const std::string&,
|
|
const CdmSecureStopId&,
|
|
CdmUsageInfo*));
|
|
|
|
MOCK_METHOD1(ReleaseAllUsageInfo, CdmResponseType(const std::string&));
|
|
|
|
MOCK_METHOD1(ReleaseUsageInfo,
|
|
CdmResponseType(const CdmUsageInfoReleaseMessage&));
|
|
|
|
MOCK_METHOD1(IsValidServiceCertificate, bool(const std::string&));
|
|
};
|
|
|
|
class MockCrypto : public WVGenericCryptoInterface {
|
|
public:
|
|
MOCK_METHOD3(selectKey, OEMCryptoResult(const OEMCrypto_SESSION,
|
|
const uint8_t*, size_t));
|
|
|
|
MOCK_METHOD6(encrypt, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*,
|
|
size_t, const uint8_t*,
|
|
OEMCrypto_Algorithm, uint8_t*));
|
|
|
|
MOCK_METHOD6(decrypt, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*,
|
|
size_t, const uint8_t*,
|
|
OEMCrypto_Algorithm, uint8_t*));
|
|
|
|
MOCK_METHOD6(sign, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*, size_t,
|
|
OEMCrypto_Algorithm, uint8_t*, size_t*));
|
|
|
|
MOCK_METHOD6(verify, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*,
|
|
size_t, OEMCrypto_Algorithm,
|
|
const uint8_t*, size_t));
|
|
|
|
MOCK_METHOD3(loadDeviceRSAKey, OEMCryptoResult(OEMCrypto_SESSION,
|
|
const uint8_t*, size_t));
|
|
|
|
MOCK_METHOD6(generateRSASignature, OEMCryptoResult(OEMCrypto_SESSION,
|
|
const uint8_t*, size_t,
|
|
uint8_t*, size_t*,
|
|
RSA_Padding_Scheme));
|
|
};
|
|
|
|
class MockDrmPluginListener : public IDrmPluginListener {
|
|
public:
|
|
MOCK_METHOD3(sendEvent, Return<void>(EventType, const hidl_vec<uint8_t>&,
|
|
const hidl_vec<uint8_t>&));
|
|
|
|
MOCK_METHOD2(sendExpirationUpdate,
|
|
Return<void>(const hidl_vec<uint8_t>&, int64_t));
|
|
|
|
MOCK_METHOD3(sendKeysChange, Return<void>(const hidl_vec<uint8_t>&,
|
|
const hidl_vec<KeyStatus>&, bool));
|
|
|
|
};
|
|
|
|
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 wvcdm::NO_ERROR;
|
|
}
|
|
|
|
MATCHER_P(HasOrigin, origin, "") {
|
|
return arg.origin == origin;
|
|
}
|
|
|
|
class WVDrmPluginTest : public Test {
|
|
protected:
|
|
static const uint32_t kSessionIdSize = 16;
|
|
uint8_t sessionIdRaw[kSessionIdSize];
|
|
std::vector<uint8_t> sessionId;
|
|
CdmSessionId cdmSessionId;
|
|
|
|
virtual void SetUp() {
|
|
// Fill the session ID
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp);
|
|
fclose(fp);
|
|
|
|
memcpy(sessionIdRaw, SESSION_ID_PREFIX, sizeof(SESSION_ID_PREFIX) - 1);
|
|
sessionId.assign(sessionIdRaw, sessionIdRaw + kSessionIdSize);
|
|
cdmSessionId.assign(sessionId.begin(), sessionId.end());
|
|
|
|
// Set default return values for gMock
|
|
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
|
|
DefaultValue<OEMCryptoResult>::Set(OEMCrypto_SUCCESS);
|
|
DefaultValue<bool>::Set(true);
|
|
}
|
|
};
|
|
|
|
struct OriginTestVariant {
|
|
// For a test that does not expect any follow-up queries
|
|
OriginTestVariant(const std::string& nameValue,
|
|
const 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& param, ::std::ostream* os) {
|
|
*os << param.name << " Variant";
|
|
|
|
}
|
|
|
|
class WVDrmPluginOriginTest : public WVDrmPluginTest,
|
|
public WithParamInterface<OriginTestVariant> {};
|
|
|
|
TEST_F(WVDrmPluginTest, OpensSessions) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
EXPECT_CALL(*cdm,
|
|
OpenSession(StrEq("com.widevine"), _, HasOrigin(EMPTY_ORIGIN), _, _))
|
|
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected behavior when plugin requests session control info
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
|
|
EXPECT_THAT(sessionId, ElementsAreArray(sessionIdRaw, kSessionIdSize));
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, ClosesSessions) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(cdmSessionId))
|
|
.Times(1);
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
Status status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, ClosesSessionWithoutReturningError) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(cdmSessionId))
|
|
.WillOnce(testing::Return(wvcdm::SESSION_NOT_FOUND_1));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
Status status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
}
|
|
|
|
// TODO b/35325611 Fix this disabled test
|
|
TEST_F(WVDrmPluginTest, DISABLED_GeneratesKeyRequests) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const size_t kInitDataSize = 128;
|
|
uint8_t initDataRaw[kInitDataSize];
|
|
static const size_t kRequestSize = 256;
|
|
uint8_t requestRaw[kRequestSize];
|
|
static const uint32_t kKeySetIdSize = 32;
|
|
uint8_t keySetIdRaw[kKeySetIdSize];
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(initDataRaw, sizeof(uint8_t), kInitDataSize, fp);
|
|
fread(requestRaw, sizeof(uint8_t), kRequestSize, fp);
|
|
fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp);
|
|
fclose(fp);
|
|
|
|
memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1);
|
|
CdmKeySetId cdmKeySetId(reinterpret_cast<char*>(keySetIdRaw), kKeySetIdSize);
|
|
std::vector<uint8_t> keySetId;
|
|
keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize);
|
|
|
|
CdmInitData cdmInitData(reinterpret_cast<char*>(initDataRaw), kInitDataSize);
|
|
std::vector<uint8_t> initData;
|
|
initData.assign(initDataRaw, initDataRaw + kInitDataSize);
|
|
|
|
static const uint8_t psshPrefix[] = {
|
|
0, 0, 0, 32 + kInitDataSize, // Total size
|
|
'p', 's', 's', 'h', // "PSSH"
|
|
0, 0, 0, 0, // Flags - must be zero
|
|
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, // Widevine UUID
|
|
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
|
|
0, 0, 0, kInitDataSize // Size of initData
|
|
};
|
|
static const size_t kPsshPrefixSize = sizeof(psshPrefix);
|
|
static const size_t kPsshBoxSize = kPsshPrefixSize + kInitDataSize;
|
|
uint8_t psshBoxRaw[kPsshBoxSize];
|
|
memcpy(psshBoxRaw, psshPrefix, kPsshPrefixSize);
|
|
memcpy(psshBoxRaw + kPsshPrefixSize, initDataRaw, kInitDataSize);
|
|
CdmInitData cdmPsshBox(reinterpret_cast<char*>(psshBoxRaw), kPsshBoxSize);
|
|
std::vector<uint8_t> psshBox;
|
|
psshBox.assign(psshBoxRaw, psshBoxRaw + kPsshBoxSize);
|
|
|
|
CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize);
|
|
|
|
std::map<std::string, std::string> parameters;
|
|
CdmAppParameterMap cdmParameters;
|
|
parameters.insert(
|
|
std::pair<std::string, std::string>("paddingScheme", "BUBBLE WRAP"));
|
|
cdmParameters["paddingScheme"] = "BUBBLE WRAP";
|
|
parameters.insert(
|
|
std::pair<std::string, std::string>("favorite-particle", "tetraquark"));
|
|
cdmParameters["favorite-particle"] = "tetraquark";
|
|
parameters.insert(
|
|
std::pair<std::string, std::string>("answer", "6 * 9"));
|
|
cdmParameters["answer"] = "6 * 9";
|
|
|
|
std::vector<KeyValue> optionalParameters;
|
|
KeyValue keyValue;
|
|
for (std::map<std::string, std::string>::iterator 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, cdmPsshBox}, // ISO-BMFF, EME passing style
|
|
{kIsoBmffMimeType, initData, cdmPsshBox}, // ISO-BMFF, old passing style
|
|
{kWebmMimeType, initData, cdmInitData} // WebM
|
|
};
|
|
size_t testSetCount = sizeof(testSets) / sizeof(TestSet);
|
|
|
|
// Set up the expected calls. Per gMock rules, this must be done for all test
|
|
// sets prior to testing any of them.
|
|
{
|
|
InSequence calls;
|
|
|
|
for (size_t i = 0; i < testSetCount; ++i)
|
|
{
|
|
const char* mimeType = testSets[i].mimeType;
|
|
const CdmInitData& initData = testSets[i].initDataOut;
|
|
|
|
CdmKeyRequest initialRequest = {
|
|
cdmRequest,
|
|
kKeyRequestTypeInitial,
|
|
kDefaultUrl
|
|
};
|
|
|
|
CdmKeyRequest renewalRequest = {
|
|
cdmRequest,
|
|
kKeyRequestTypeRenewal,
|
|
kDefaultUrl
|
|
};
|
|
|
|
CdmKeyRequest releaseRequest = {
|
|
cdmRequest,
|
|
kKeyRequestTypeRelease,
|
|
kDefaultUrl
|
|
};
|
|
|
|
EXPECT_CALL(*cdm, GenerateKeyRequest(cdmSessionId, "", mimeType, initData,
|
|
kLicenseTypeOffline, cdmParameters,
|
|
NotNull(), HasOrigin(EMPTY_ORIGIN),
|
|
_))
|
|
.WillOnce(DoAll(SetArgPointee<8>(initialRequest),
|
|
testing::Return(wvcdm::KEY_MESSAGE)));
|
|
|
|
EXPECT_CALL(*cdm, GenerateKeyRequest(cdmSessionId, "", mimeType, initData,
|
|
kLicenseTypeStreaming, cdmParameters,
|
|
NotNull(), HasOrigin(EMPTY_ORIGIN),
|
|
_))
|
|
.WillOnce(DoAll(SetArgPointee<8>(renewalRequest),
|
|
testing::Return(wvcdm::KEY_MESSAGE)));
|
|
|
|
EXPECT_CALL(*cdm, GenerateKeyRequest("", cdmKeySetId, mimeType, initData,
|
|
kLicenseTypeRelease, cdmParameters,
|
|
NotNull(), HasOrigin(EMPTY_ORIGIN),
|
|
_))
|
|
|
|
.WillOnce(DoAll(SetArgPointee<8>(releaseRequest),
|
|
testing::Return(wvcdm::KEY_MESSAGE)));
|
|
}
|
|
}
|
|
|
|
// Performs the actual tests
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
for (size_t i = 0; i < testSetCount; ++i)
|
|
{
|
|
const std::string mimeType(testSets[i].mimeType);
|
|
const std::vector<uint8_t>& initData = testSets[i].initDataIn;
|
|
|
|
plugin.getKeyRequest(
|
|
toHidlVec(sessionId), toHidlVec(initData),
|
|
hidl_string(mimeType), KeyType::OFFLINE, toHidlVec(optionalParameters),
|
|
[&](Status status, hidl_vec<uint8_t> hRequest,
|
|
KeyRequestType keyRequestType, hidl_string defaultUrl) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
std::vector<uint8_t> request(hRequest);
|
|
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
|
EXPECT_EQ(KeyRequestType::INITIAL, keyRequestType);
|
|
EXPECT_STREQ(kDefaultUrl, defaultUrl.c_str());
|
|
});
|
|
|
|
plugin.getKeyRequest(
|
|
toHidlVec(sessionId), toHidlVec(initData),
|
|
hidl_string(mimeType), KeyType::STREAMING, toHidlVec(optionalParameters),
|
|
[&](Status status, hidl_vec<uint8_t> hRequest,
|
|
KeyRequestType keyRequestType, hidl_string defaultUrl) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
std::vector<uint8_t> request(hRequest);
|
|
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
|
EXPECT_EQ(KeyRequestType::RENEWAL, keyRequestType);
|
|
EXPECT_STREQ(kDefaultUrl, defaultUrl.c_str());
|
|
});
|
|
|
|
plugin.getKeyRequest(
|
|
toHidlVec(sessionId), toHidlVec(initData),
|
|
hidl_string(mimeType), KeyType::RELEASE, toHidlVec(optionalParameters),
|
|
[&](Status status, hidl_vec<uint8_t> hRequest,
|
|
KeyRequestType keyRequestType, hidl_string defaultUrl) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
std::vector<uint8_t> request(hRequest);
|
|
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
|
EXPECT_EQ(KeyRequestType::RELEASE, keyRequestType);
|
|
EXPECT_STREQ(kDefaultUrl, defaultUrl.c_str());
|
|
});
|
|
}
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, AddsKeys) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const uint32_t kResponseSize = 256;
|
|
uint8_t responseRaw[kResponseSize];
|
|
static const uint32_t kKeySetIdSize = 32;
|
|
uint8_t keySetIdRaw[kKeySetIdSize];
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(responseRaw, sizeof(uint8_t), kResponseSize, fp);
|
|
fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp);
|
|
fclose(fp);
|
|
|
|
std::vector<uint8_t> response;
|
|
response.assign(responseRaw, responseRaw + kResponseSize);
|
|
|
|
memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1);
|
|
CdmKeySetId cdmKeySetId(reinterpret_cast<char *>(keySetIdRaw), kKeySetIdSize);
|
|
|
|
std::vector<uint8_t> keySetId;
|
|
std::vector<uint8_t> emptyKeySetId;
|
|
|
|
EXPECT_CALL(*cdm, AddKey(cdmSessionId,
|
|
ElementsAreArray(responseRaw, kResponseSize), _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetId),
|
|
testing::Return(wvcdm::KEY_ADDED)));
|
|
|
|
EXPECT_CALL(*cdm, AddKey("", ElementsAreArray(responseRaw, kResponseSize),
|
|
Pointee(cdmKeySetId)))
|
|
.Times(1);
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.provideKeyResponse(
|
|
toHidlVec(sessionId), toHidlVec(response),
|
|
[&](Status status, hidl_vec<uint8_t> hKeySetId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
std::vector<uint8_t> id(hKeySetId);
|
|
keySetId.assign(id.data(), id.data() + id.size());
|
|
ASSERT_THAT(keySetId, ElementsAreArray(keySetIdRaw, kKeySetIdSize));
|
|
});
|
|
|
|
plugin.provideKeyResponse(
|
|
toHidlVec(keySetId), toHidlVec(response),
|
|
[&](Status status, hidl_vec<uint8_t> hKeySetId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
EXPECT_EQ(0u, hKeySetId.size());
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, HandlesPrivacyCertCaseOfAddKey) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
sp<StrictMock<MockDrmPluginListener> > listener =
|
|
new StrictMock<MockDrmPluginListener>();
|
|
|
|
const CdmClientPropertySet* propertySet = NULL;
|
|
|
|
// Provide expected behavior in response to OpenSession and store the
|
|
// property set
|
|
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
SaveArg<1>(&propertySet),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected behavior when plugin requests session control info
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
|
|
static const uint32_t kResponseSize = 256;
|
|
uint8_t responseRaw[kResponseSize];
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(responseRaw, sizeof(uint8_t), kResponseSize, fp);
|
|
fclose(fp);
|
|
|
|
hidl_vec<uint8_t> hSessionId;
|
|
hSessionId.setToExternal(sessionIdRaw, kSessionIdSize);
|
|
hidl_vec<uint8_t> hEmptyData;
|
|
|
|
EXPECT_CALL(*listener,
|
|
sendEvent(EventType::KEY_NEEDED, hSessionId, hEmptyData))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(*cdm, AddKey(_, _, _))
|
|
.WillRepeatedly(testing::Return(wvcdm::NEED_KEY));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
|
|
plugin.setListener(listener);
|
|
|
|
Status status = plugin.setPropertyString(hidl_string("privacyMode"),
|
|
hidl_string("enable"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_TRUE(propertySet->use_privacy_mode());
|
|
|
|
std::vector<uint8_t> response;
|
|
response.assign(responseRaw, responseRaw + kResponseSize);
|
|
std::vector<uint8_t> keySetId;
|
|
|
|
plugin.provideKeyResponse(
|
|
toHidlVec(sessionId), toHidlVec(response),
|
|
[&](Status status, hidl_vec<uint8_t> /* keySetId */) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, RemovesKeys) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
EXPECT_CALL(*cdm, RemoveKeys(cdmSessionId))
|
|
.Times(1);
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
Status status = plugin.removeKeys(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, RestoresKeys) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const size_t kKeySetIdSize = 32;
|
|
uint8_t keySetIdRaw[kKeySetIdSize];
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp);
|
|
fclose(fp);
|
|
|
|
std::vector<uint8_t> keySetId;
|
|
keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize);
|
|
|
|
EXPECT_CALL(*cdm, RestoreKey(cdmSessionId,
|
|
ElementsAreArray(keySetIdRaw, kKeySetIdSize)))
|
|
.Times(1);
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
Status status = plugin.restoreKeys(toHidlVec(sessionId), toHidlVec(keySetId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, QueriesKeyStatus) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
std::map<std::string, std::string> expectedLicenseStatus;
|
|
CdmQueryMap cdmLicenseStatus;
|
|
|
|
expectedLicenseStatus.insert(
|
|
std::pair<std::string, std::string>("areTheKeysAllRight", "yes"));
|
|
cdmLicenseStatus["areTheKeysAllRight"] = "yes";
|
|
expectedLicenseStatus.insert(
|
|
std::pair<std::string, std::string>("isGMockAwesome", "ohhhhhhYeah"));
|
|
cdmLicenseStatus["isGMockAwesome"] = "ohhhhhhYeah";
|
|
expectedLicenseStatus.insert(
|
|
std::pair<std::string, std::string>("answer", "42"));
|
|
cdmLicenseStatus["answer"] = "42";
|
|
|
|
EXPECT_CALL(*cdm, QueryKeyStatus(cdmSessionId, _))
|
|
.WillOnce(DoAll(SetArgPointee<1>(cdmLicenseStatus),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
plugin.queryKeyStatus(toHidlVec(sessionId),
|
|
[&](Status status, hidl_vec<KeyValue>(hLicenseStatus)) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
ASSERT_EQ(expectedLicenseStatus.size(), hLicenseStatus.size());
|
|
|
|
KeyValue keyValuePair;
|
|
size_t i = 0;
|
|
for (std::map<std::string, std::string>::iterator itr =
|
|
expectedLicenseStatus.begin();
|
|
itr != expectedLicenseStatus.end(); ++itr) {
|
|
const std::string& key = itr->first;
|
|
keyValuePair.value = hLicenseStatus[i++].value;
|
|
EXPECT_EQ(itr->second.c_str(), std::string(keyValuePair.value.c_str()));
|
|
}
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, GetsProvisioningRequests) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const uint32_t kRequestSize = 256;
|
|
uint8_t requestRaw[kRequestSize];
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(requestRaw, sizeof(uint8_t), kRequestSize, fp);
|
|
fclose(fp);
|
|
|
|
CdmProvisioningRequest cdmRequest(requestRaw, requestRaw + kRequestSize);
|
|
|
|
static const char* kDefaultUrl = "http://google.com/";
|
|
|
|
EXPECT_CALL(*cdm, GetProvisioningRequest(kCertificateWidevine, IsEmpty(),
|
|
HasOrigin(EMPTY_ORIGIN), _, _))
|
|
.WillOnce(DoAll(SetArgPointee<3>(cdmRequest),
|
|
SetArgPointee<4>(kDefaultUrl),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
plugin.getProvisionRequest(
|
|
hidl_string(""), hidl_string(""),
|
|
[&](Status status, hidl_vec<uint8_t> hRequest, hidl_string defaultUrl) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
std::vector<uint8_t> request(hRequest);
|
|
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
|
EXPECT_STREQ(kDefaultUrl, defaultUrl.c_str());
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const uint32_t kResponseSize = 512;
|
|
uint8_t responseRaw[kResponseSize];
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(responseRaw, sizeof(uint8_t), kResponseSize, fp);
|
|
fclose(fp);
|
|
|
|
std::vector<uint8_t> response;
|
|
response.assign(responseRaw, responseRaw + kResponseSize);
|
|
|
|
EXPECT_CALL(*cdm, HandleProvisioningResponse(HasOrigin(EMPTY_ORIGIN),
|
|
ElementsAreArray(responseRaw,
|
|
kResponseSize),
|
|
_, _))
|
|
.Times(1);
|
|
|
|
std::vector<uint8_t> cert;
|
|
std::vector<uint8_t> key;
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
plugin.provideProvisionResponse(
|
|
toHidlVec(response),
|
|
[&](Status status, hidl_vec<uint8_t> /* cert */,
|
|
hidl_vec<uint8_t> /* key */) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, UnprovisionsDevice) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN)))
|
|
.Times(1);
|
|
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN)))
|
|
.Times(1);
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
status_t res = plugin.unprovisionDevice();
|
|
ASSERT_EQ(android::OK, res);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, MuxesUnprovisioningErrors) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
// Tests that both Unprovisions are called even if one fails. Also tests that
|
|
// no matter which fails, the function always propagates the error.
|
|
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN)))
|
|
.WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR))
|
|
.WillOnce(testing::Return(wvcdm::NO_ERROR))
|
|
.WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR));
|
|
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN)))
|
|
.WillOnce(testing::Return(wvcdm::NO_ERROR))
|
|
.WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR))
|
|
.WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
status_t res = plugin.unprovisionDevice();
|
|
ASSERT_NE(android::OK, res);
|
|
res = plugin.unprovisionDevice();
|
|
ASSERT_NE(android::OK, res);
|
|
res = plugin.unprovisionDevice();
|
|
ASSERT_NE(android::OK, res);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, UnprovisionsOrigin) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
std::vector<uint8_t> cert;
|
|
std::vector<uint8_t> key;
|
|
std::vector<uint8_t> specialResponse;
|
|
specialResponse.assign(
|
|
kUnprovisionResponse, kUnprovisionResponse + kUnprovisionResponseSize);
|
|
|
|
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, HasOrigin(kOrigin.c_str())))
|
|
.Times(1);
|
|
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(kOrigin.c_str())))
|
|
.Times(1);
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
Status status = plugin.setPropertyString(hidl_string("origin"),
|
|
hidl_string(kOrigin));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.provideProvisionResponse(
|
|
toHidlVec(specialResponse),
|
|
[&](Status status, hidl_vec<uint8_t> /* cert */,
|
|
hidl_vec<uint8_t> /* key */) {
|
|
EXPECT_EQ(Status::OK, status);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, WillNotUnprovisionWithoutOrigin) {
|
|
// This test is only valid on SPOID-free devices. SPOID devices can
|
|
// unprovision without an origin because the empty-origin provisioning is
|
|
// not global.
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
std::vector<uint8_t> cert;
|
|
std::vector<uint8_t> key;
|
|
std::vector<uint8_t> specialResponse;
|
|
specialResponse.assign(
|
|
kUnprovisionResponse, kUnprovisionResponse + kUnprovisionResponseSize);
|
|
|
|
EXPECT_CALL(*cdm, Unprovision(_, _))
|
|
.Times(0);
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
plugin.provideProvisionResponse(
|
|
toHidlVec(specialResponse),
|
|
[&](Status status, hidl_vec<uint8_t> /* cert */,
|
|
hidl_vec<uint8_t> /* key */) {
|
|
EXPECT_NE(Status::OK, status);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, MuxesOriginUnprovisioningErrors) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
std::vector<uint8_t> cert;
|
|
std::vector<uint8_t> key;
|
|
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(*cdm, Unprovision(kSecurityLevelL1, HasOrigin(kOrigin.c_str())))
|
|
.WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR))
|
|
.WillOnce(testing::Return(wvcdm::NO_ERROR))
|
|
.WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR));
|
|
EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(kOrigin.c_str())))
|
|
.WillOnce(testing::Return(wvcdm::NO_ERROR))
|
|
.WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR))
|
|
.WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
Status status = plugin.setPropertyString(hidl_string("origin"),
|
|
hidl_string(kOrigin));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.provideProvisionResponse(
|
|
toHidlVec(specialResponse),
|
|
[&](Status status, hidl_vec<uint8_t> /* cert */,
|
|
hidl_vec<uint8_t> /* key */) {
|
|
EXPECT_NE(Status::OK, status);
|
|
});
|
|
|
|
plugin.provideProvisionResponse(
|
|
toHidlVec(specialResponse),
|
|
[&](Status status, hidl_vec<uint8_t> /* cert */,
|
|
hidl_vec<uint8_t> /* key */) {
|
|
EXPECT_NE(Status::OK, status);
|
|
});
|
|
|
|
plugin.provideProvisionResponse(
|
|
toHidlVec(specialResponse),
|
|
[&](Status status, hidl_vec<uint8_t> /* cert */,
|
|
hidl_vec<uint8_t> /* key */) {
|
|
EXPECT_NE(Status::OK, status);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, GetsSecureStops) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const uint32_t kStopSize = 53;
|
|
static const uint32_t kStopCount = 7;
|
|
uint8_t stopsRaw[kStopCount][kStopSize];
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
for (uint32_t i = 0; i < kStopCount; ++i) {
|
|
fread(stopsRaw[i], sizeof(uint8_t), kStopSize, fp);
|
|
}
|
|
fclose(fp);
|
|
|
|
CdmUsageInfo cdmStops;
|
|
for (uint32_t i = 0; i < kStopCount; ++i) {
|
|
cdmStops.push_back(std::string(stopsRaw[i], stopsRaw[i] + kStopSize));
|
|
}
|
|
|
|
const char* app_id = "my_app_id";
|
|
EXPECT_CALL(*cdm, GetUsageInfo(StrEq(app_id), _))
|
|
.WillOnce(DoAll(SetArgPointee<1>(cdmStops),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
std::list<std::vector<uint8_t> > stops;
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
Status status = plugin.setPropertyString(hidl_string("appId"),
|
|
hidl_string(app_id));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.getSecureStops([&](Status status, hidl_vec<SecureStop> hSecureStops) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
std::vector<SecureStop> secureStops(hSecureStops);
|
|
std::vector<SecureStop>::iterator iter = secureStops.begin();
|
|
std::vector<uint8_t> stop;
|
|
while (iter != secureStops.end()) {
|
|
stop = (*iter++).opaqueData;
|
|
stops.push_back(stop);
|
|
}
|
|
});
|
|
|
|
std::list<std::vector<uint8_t> >::iterator iter = stops.begin();
|
|
uint32_t rawIter = 0;
|
|
while (rawIter < kStopCount && iter != stops.end()) {
|
|
EXPECT_THAT(*iter, ElementsAreArray(stopsRaw[rawIter], kStopSize));
|
|
|
|
++iter;
|
|
++rawIter;
|
|
}
|
|
// Assert that both lists are the same length
|
|
EXPECT_EQ(kStopCount, rawIter);
|
|
EXPECT_EQ(stops.end(), iter);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, ReleasesAllSecureStops) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
EXPECT_CALL(*cdm, ReleaseAllUsageInfo(StrEq("")))
|
|
.Times(1);
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
Status status = plugin.setPropertyString(hidl_string("appId"),
|
|
hidl_string(""));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
status = plugin.releaseAllSecureStops();
|
|
ASSERT_EQ(Status::OK, status);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, ReleasesSecureStop) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const uint32_t kMessageSize = 128;
|
|
uint8_t messageRaw[kMessageSize];
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(messageRaw, sizeof(uint8_t), kMessageSize, fp);
|
|
fclose(fp);
|
|
|
|
std::vector<uint8_t> message;
|
|
message.assign(messageRaw, messageRaw + kMessageSize);
|
|
|
|
EXPECT_CALL(*cdm, ReleaseUsageInfo(ElementsAreArray(messageRaw,
|
|
kMessageSize)))
|
|
.Times(1);
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
Status status = plugin.releaseSecureStop(toHidlVec(message));
|
|
ASSERT_EQ(Status::OK, status);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
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 deviceId("0123456789\0ABCDEF", 17);
|
|
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 oemCryptoApiVersion = "13";
|
|
|
|
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1),
|
|
testing::Return(wvcdm::NO_ERROR)))
|
|
.WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(deviceId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SYSTEM_ID, _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(systemId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_PROVISIONING_ID, _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(provisioningId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_NUMBER_OF_OPEN_SESSIONS, _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(openSessions),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_MAX_NUMBER_OF_SESSIONS, _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(maxSessions),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_API_VERSION, _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(oemCryptoApiVersion),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
std::string stringResult;
|
|
std::vector<uint8_t> vectorResult;
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("vendor"), [&](Status status, hidl_string stringResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_STREQ("Google", stringResult.c_str());
|
|
});
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("version"), [&](Status status, hidl_string stringResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_STREQ("1.0", stringResult.c_str());
|
|
});
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("description"), [&](Status status, hidl_string stringResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_STREQ("Widevine CDM", stringResult.c_str());
|
|
});
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("algorithms"), [&](Status status, hidl_string stringResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_STREQ("AES/CBC/NoPadding,HmacSHA256", stringResult.c_str());
|
|
});
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("securityLevel"),
|
|
[&](Status status, hidl_string stringResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L1.c_str(), stringResult.c_str());
|
|
});
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("securityLevel"),
|
|
[&](Status status, hidl_string stringResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L3.c_str(), stringResult.c_str());
|
|
});
|
|
|
|
plugin.getPropertyByteArray(
|
|
hidl_string("deviceUniqueId"),
|
|
[&](Status status, hidl_vec<uint8_t> vectorResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
std::vector<uint8_t> id(vectorResult);
|
|
EXPECT_THAT(id, ElementsAreArray(deviceId.data(), deviceId.size()));
|
|
});
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("systemId"),
|
|
[&](Status status, hidl_string stringResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_STREQ(systemId.c_str(), stringResult.c_str());
|
|
});
|
|
|
|
plugin.getPropertyByteArray(
|
|
hidl_string("provisioningUniqueId"),
|
|
[&](Status status, hidl_vec<uint8_t> vectorResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
std::vector<uint8_t> id(vectorResult);
|
|
EXPECT_THAT(id, ElementsAreArray(provisioningId.data(),
|
|
provisioningId.size()));
|
|
});
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("numberOfOpenSessions"),
|
|
[&](Status status, hidl_string stringResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_EQ(openSessions, stringResult.c_str());
|
|
});
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("maxNumberOfSessions"),
|
|
[&](Status status, hidl_string stringResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_EQ(maxSessions, stringResult.c_str());
|
|
});
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("oemCryptoApiVersion"),
|
|
[&](Status status, hidl_string stringResult) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_EQ(oemCryptoApiVersion, stringResult.c_str());
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
std::string stringResult;
|
|
std::vector<uint8_t> vectorResult;
|
|
|
|
plugin.getPropertyString(
|
|
hidl_string("unknownProperty"),
|
|
[&](Status status, hidl_string stringResult) {
|
|
ASSERT_NE(Status::OK, status);
|
|
EXPECT_TRUE(stringResult.empty());
|
|
});
|
|
|
|
plugin.getPropertyByteArray(
|
|
hidl_string("unknownProperty"),
|
|
[&](Status status, hidl_vec<uint8_t> vectorResult) {
|
|
ASSERT_NE(Status::OK, status);
|
|
EXPECT_EQ(0u, vectorResult.size());
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, DoesNotSetUnknownProperties) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const uint32_t kValueSize = 32;
|
|
uint8_t valueRaw[kValueSize];
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(valueRaw, sizeof(uint8_t), kValueSize, fp);
|
|
fclose(fp);
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
std::vector<uint8_t> value;
|
|
value.assign(valueRaw, valueRaw + kValueSize);
|
|
|
|
Status status = plugin.setPropertyString(hidl_string("unknownProperty"),
|
|
hidl_string("ignored"));
|
|
ASSERT_NE(Status::OK, status);
|
|
|
|
status = plugin.setPropertyByteArray(hidl_string("unknownProperty"),
|
|
toHidlVec(value));
|
|
ASSERT_NE(Status::OK, status);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, FailsGenericMethodsWithoutAnAlgorithmSet) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
std::vector<uint8_t> keyId;
|
|
std::vector<uint8_t> input;
|
|
std::vector<uint8_t> iv;
|
|
std::vector<uint8_t> output;
|
|
bool match;
|
|
|
|
// Provide expected behavior to support session creation
|
|
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
|
|
// Note that we do not set the algorithms. This should cause these methods
|
|
// to fail.
|
|
|
|
plugin.encrypt(toHidlVec(sessionId), toHidlVec(keyId), toHidlVec(input),
|
|
toHidlVec(iv), [&](Status status, hidl_vec<uint8_t> /* output */) {
|
|
// NO_INIT is converted to Status::ERROR_DRM_UNKNOWN
|
|
EXPECT_EQ(Status::ERROR_DRM_UNKNOWN, status);
|
|
});
|
|
|
|
plugin.decrypt(toHidlVec(sessionId), toHidlVec(keyId), toHidlVec(input),
|
|
toHidlVec(iv), [&](Status status, hidl_vec<uint8_t> /* output */) {
|
|
EXPECT_EQ(Status::ERROR_DRM_UNKNOWN, status);
|
|
});
|
|
|
|
plugin.sign(toHidlVec(sessionId), toHidlVec(keyId), toHidlVec(input),
|
|
[&](Status status, hidl_vec<uint8_t> /* signature */) {
|
|
EXPECT_EQ(Status::ERROR_DRM_UNKNOWN, status);
|
|
});
|
|
|
|
plugin.verify(toHidlVec(sessionId), toHidlVec(keyId), toHidlVec(input),
|
|
toHidlVec(output), [&](Status status, bool /* match */) {
|
|
EXPECT_EQ(Status::ERROR_DRM_UNKNOWN, status);
|
|
});
|
|
}
|
|
|
|
MATCHER_P(IsIV, iv, "") {
|
|
for (size_t i = 0; i < KEY_IV_SIZE; ++i) {
|
|
if (iv[i] != arg[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, CallsGenericEncrypt) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const size_t kDataSize = 256;
|
|
uint8_t keyIdRaw[KEY_ID_SIZE];
|
|
uint8_t inputRaw[kDataSize];
|
|
uint8_t ivRaw[KEY_IV_SIZE];
|
|
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(inputRaw, sizeof(uint8_t), kDataSize, fp);
|
|
fread(ivRaw, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
|
fclose(fp);
|
|
|
|
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;
|
|
|
|
{
|
|
InSequence calls;
|
|
|
|
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
|
|
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(crypto, encrypt(4, _, kDataSize, IsIV(ivRaw),
|
|
OEMCrypto_AES_CBC_128_NO_PADDING, _))
|
|
.With(Args<1, 2>(ElementsAreArray(inputRaw, kDataSize)))
|
|
.Times(1);
|
|
}
|
|
|
|
// Provide expected behavior to support session creation
|
|
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
|
|
Status status = plugin.setCipherAlgorithm(toHidlVec(sessionId),
|
|
hidl_string("AES/CBC/NoPadding"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.encrypt(toHidlVec(sessionId), toHidlVec(keyId), toHidlVec(input),
|
|
toHidlVec(iv), [&](Status status, hidl_vec<uint8_t> /* output */) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, CallsGenericDecrypt) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const size_t kDataSize = 256;
|
|
uint8_t keyIdRaw[KEY_ID_SIZE];
|
|
uint8_t inputRaw[kDataSize];
|
|
uint8_t ivRaw[KEY_IV_SIZE];
|
|
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(inputRaw, sizeof(uint8_t), kDataSize, fp);
|
|
fread(ivRaw, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
|
fclose(fp);
|
|
|
|
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;
|
|
|
|
{
|
|
InSequence calls;
|
|
|
|
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
|
|
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(crypto, decrypt(4, _, kDataSize, IsIV(ivRaw),
|
|
OEMCrypto_AES_CBC_128_NO_PADDING, _))
|
|
.With(Args<1, 2>(ElementsAreArray(inputRaw, kDataSize)))
|
|
.Times(1);
|
|
}
|
|
|
|
// Provide expected behavior to support session creation
|
|
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
|
|
Status status = plugin.setCipherAlgorithm(toHidlVec(sessionId),
|
|
hidl_string("AES/CBC/NoPadding"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.decrypt(toHidlVec(sessionId), toHidlVec(keyId), toHidlVec(input),
|
|
toHidlVec(iv), [&](Status status, hidl_vec<uint8_t> /* output */) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, CallsGenericSign) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const size_t kDataSize = 256;
|
|
uint8_t keyIdRaw[KEY_ID_SIZE];
|
|
uint8_t messageRaw[kDataSize];
|
|
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(messageRaw, sizeof(uint8_t), kDataSize, fp);
|
|
fclose(fp);
|
|
|
|
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;
|
|
|
|
{
|
|
InSequence calls;
|
|
|
|
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
|
|
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(crypto, sign(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
|
|
Pointee(0)))
|
|
.With(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)))
|
|
.WillOnce(DoAll(SetArgPointee<5>(64),
|
|
testing::Return(OEMCrypto_ERROR_SHORT_BUFFER)));
|
|
|
|
EXPECT_CALL(crypto, sign(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
|
|
Pointee(64)))
|
|
.With(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)))
|
|
.Times(1);
|
|
}
|
|
|
|
// Provide expected behavior to support session creation
|
|
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
|
|
Status status = plugin.setMacAlgorithm(toHidlVec(sessionId),
|
|
hidl_string("HmacSHA256"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.sign(toHidlVec(sessionId), toHidlVec(keyId), toHidlVec(message),
|
|
[&](Status status, hidl_vec<uint8_t> signature) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
ASSERT_NE(0u, signature.size());
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, CallsGenericVerify) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
static const size_t kDataSize = 256;
|
|
static const size_t kSignatureSize = 16;
|
|
uint8_t keyIdRaw[KEY_ID_SIZE];
|
|
uint8_t messageRaw[kDataSize];
|
|
uint8_t signatureRaw[kSignatureSize];
|
|
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(messageRaw, sizeof(uint8_t), kDataSize, fp);
|
|
fread(signatureRaw, sizeof(uint8_t), kSignatureSize, fp);
|
|
fclose(fp);
|
|
|
|
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);
|
|
bool match;
|
|
|
|
{
|
|
InSequence calls;
|
|
|
|
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
|
|
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(crypto, verify(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
|
|
kSignatureSize))
|
|
.With(AllOf(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)),
|
|
Args<4, 5>(ElementsAreArray(signatureRaw, kSignatureSize))))
|
|
.WillOnce(testing::Return(OEMCrypto_SUCCESS));
|
|
|
|
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
|
|
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(crypto, verify(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
|
|
kSignatureSize))
|
|
.With(AllOf(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)),
|
|
Args<4, 5>(ElementsAreArray(signatureRaw, kSignatureSize))))
|
|
.WillOnce(testing::Return(OEMCrypto_ERROR_SIGNATURE_FAILURE));
|
|
}
|
|
|
|
// Provide expected behavior to support session creation
|
|
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
|
|
Status status = plugin.setMacAlgorithm(toHidlVec(sessionId),
|
|
hidl_string("HmacSHA256"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.verify(toHidlVec(sessionId), toHidlVec(keyId), toHidlVec(message),
|
|
toHidlVec(signature), [&](Status status, bool match) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_TRUE(match);
|
|
});
|
|
|
|
plugin.verify(toHidlVec(sessionId), toHidlVec(keyId), toHidlVec(message),
|
|
toHidlVec(signature), [&](Status status, bool match) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_FALSE(match);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, RegistersForEvents) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
// Provide expected behavior to support session creation
|
|
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> /* hSessionId */) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, UnregistersForAllEventsOnDestruction) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
uint8_t sessionIdRaw1[kSessionIdSize];
|
|
uint8_t sessionIdRaw2[kSessionIdSize];
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(sessionIdRaw1, sizeof(uint8_t), kSessionIdSize, fp);
|
|
fread(sessionIdRaw2, sizeof(uint8_t), kSessionIdSize, fp);
|
|
fclose(fp);
|
|
|
|
CdmSessionId cdmSessionId1(sessionIdRaw1, sessionIdRaw1 + kSessionIdSize);
|
|
CdmSessionId cdmSessionId2(sessionIdRaw2, sessionIdRaw2 + kSessionIdSize);
|
|
|
|
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
|
|
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId1),
|
|
testing::Return(wvcdm::NO_ERROR)))
|
|
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId2),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId1, _))
|
|
.WillOnce(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId2, _))
|
|
.WillOnce(Invoke(setSessionIdOnMap<5>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
|
|
{
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> /* hSessionId */) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
});
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> /* hSessionId */) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
});
|
|
}
|
|
}
|
|
|
|
// TODO b/35325611 Fix this disabled test
|
|
TEST_F(WVDrmPluginTest, DISABLED_MarshalsEvents) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
sp<StrictMock<MockDrmPluginListener> > listener =
|
|
new StrictMock<MockDrmPluginListener>();
|
|
|
|
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 ";
|
|
|
|
{
|
|
InSequence calls;
|
|
|
|
hidl_vec<uint8_t> hEmptyData;
|
|
hidl_vec<uint8_t> hSessionId;
|
|
hSessionId.setToExternal(sessionIdRaw, kSessionIdSize);
|
|
|
|
std::vector<uint8_t> keyId;
|
|
std::vector<KeyStatus> keyStatusList;
|
|
|
|
KeyStatus keyStatus;
|
|
keyId.assign(kKeyId1.begin(), kKeyId1.end());
|
|
keyStatus.keyId = toHidlVec(keyId);
|
|
keyStatus.type = KeyStatusType::EXPIRED;
|
|
keyStatusList.push_back(keyStatus);
|
|
|
|
hidl_vec<KeyStatus> hKeyStatusList = toHidlVec(keyStatusList);
|
|
EXPECT_CALL(*listener,
|
|
sendKeysChange(hSessionId, hKeyStatusList, false));
|
|
|
|
EXPECT_CALL(
|
|
*listener,
|
|
sendEvent(EventType::KEY_EXPIRED, hSessionId, hEmptyData));
|
|
|
|
EXPECT_CALL(*listener,
|
|
sendEvent(EventType::KEY_NEEDED, hSessionId, hEmptyData));
|
|
|
|
// Expiry Time in Java API is in milliseconds.
|
|
EXPECT_CALL(*listener, sendExpirationUpdate(hSessionId, NEVER_EXPIRES));
|
|
EXPECT_CALL(*listener,
|
|
sendExpirationUpdate(hSessionId, kExpiryTimeInSeconds * 1000));
|
|
|
|
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::OUTPUTNOTALLOWED;
|
|
keyStatusList.push_back(keyStatus);
|
|
keyId.assign(kKeyId3.begin(), kKeyId3.end());
|
|
keyStatus.type = KeyStatusType::INTERNALERROR;
|
|
keyStatusList.push_back(keyStatus);
|
|
keyId.assign(kKeyId4.begin(), kKeyId4.end());
|
|
keyStatus.type = KeyStatusType::STATUSPENDING;
|
|
keyStatusList.push_back(keyStatus);
|
|
|
|
hidl_vec<KeyStatus> hKeyStatusList2 = toHidlVec(keyStatusList);
|
|
EXPECT_CALL(*listener, sendKeysChange(hSessionId, hKeyStatusList2, false));
|
|
}
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
plugin.setListener(listener);
|
|
|
|
CdmKeyStatusMap cdmKeysStatus;
|
|
cdmKeysStatus[kKeyId1] = wvcdm::kKeyStatusExpired;
|
|
plugin.OnSessionKeysChange(cdmSessionId, cdmKeysStatus, false);
|
|
|
|
plugin.OnSessionRenewalNeeded(cdmSessionId);
|
|
plugin.OnExpirationUpdate(cdmSessionId, NEVER_EXPIRES);
|
|
plugin.OnExpirationUpdate(cdmSessionId, kExpiryTimeInSeconds);
|
|
|
|
cdmKeysStatus[kKeyId1] = wvcdm::kKeyStatusUsable;
|
|
cdmKeysStatus[kKeyId2] = wvcdm::kKeyStatusOutputNotAllowed;
|
|
cdmKeysStatus[kKeyId3] = wvcdm::kKeyStatusInternalError;
|
|
cdmKeysStatus[kKeyId4] = wvcdm::kKeyStatusPending;
|
|
plugin.OnSessionKeysChange(cdmSessionId, cdmKeysStatus, true);
|
|
}
|
|
|
|
// TODO b/35325611 Fix this disabled test
|
|
TEST_F(WVDrmPluginTest, DISABLED_GeneratesProvisioningNeededEvent) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
sp<StrictMock<MockDrmPluginListener> > listener =
|
|
new StrictMock<MockDrmPluginListener>();
|
|
|
|
hidl_vec<uint8_t> hEmptyData;
|
|
hidl_vec<uint8_t> hSessionId;
|
|
hSessionId.setToExternal(sessionIdRaw, kSessionIdSize);
|
|
|
|
EXPECT_CALL(*listener,
|
|
sendEvent(EventType::PROVISION_REQUIRED, hSessionId, hEmptyData))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
|
|
.Times(AtLeast(1))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
testing::Return(wvcdm::NEED_PROVISIONING)));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
plugin.setListener(listener);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> /* hSessionId */) {
|
|
ASSERT_EQ(Status::ERROR_DRM_NOT_PROVISIONED, status);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, ProvidesExpectedDefaultPropertiesToCdm) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
const CdmClientPropertySet* propertySet = NULL;
|
|
|
|
// Provide expected mock behavior
|
|
{
|
|
// Provide expected behavior in response to OpenSession and store the
|
|
// property set
|
|
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
SaveArg<1>(&propertySet),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected behavior when plugin requests session control info
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
}
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> /* hSessionId */) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
});
|
|
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
EXPECT_STREQ("", propertySet->security_level().c_str());
|
|
EXPECT_FALSE(propertySet->use_privacy_mode());
|
|
EXPECT_EQ(0u, propertySet->service_certificate().size());
|
|
EXPECT_FALSE(propertySet->is_session_sharing_enabled());
|
|
EXPECT_EQ(0u, propertySet->session_sharing_id());
|
|
EXPECT_STREQ("", propertySet->app_id().c_str());
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, CanSetAppId) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
const CdmClientPropertySet* propertySet = NULL;
|
|
|
|
// Provide expected mock behavior
|
|
{
|
|
// Provide expected behavior in response to OpenSession and store the
|
|
// property set
|
|
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
SaveArg<1>(&propertySet),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected behavior when plugin queries for the security level
|
|
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _))
|
|
.WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected behavior when plugin requests session control info
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
}
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
// Test setting an empty string
|
|
Status status = plugin.setPropertyString(hidl_string("appId"),
|
|
hidl_string(""));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
// Test setting an application id before a session is opened.
|
|
status = plugin.setPropertyString(hidl_string("appId"),
|
|
hidl_string(kAppId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> /* hSessionId */) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
});
|
|
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.
|
|
status = plugin.setPropertyString(hidl_string("appId"),
|
|
hidl_string(kAppId));
|
|
ASSERT_EQ(Status::ERROR_DRM_UNKNOWN, status);
|
|
}
|
|
|
|
TEST_P(WVDrmPluginOriginTest, CanSetOrigin) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
OriginTestVariant params = GetParam();
|
|
|
|
// Provide expected mock behavior
|
|
{
|
|
// Provide expected behavior when plugin requests session control info
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
// Provide expected behavior when plugin closes a session
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
}
|
|
|
|
// Note which mock calls we expect
|
|
EXPECT_CALL(*cdm, OpenSession(_, _, HasOrigin(params.expectedOrigin), _, _))
|
|
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Set the properties & run the test
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
if (!params.origin.empty()) {
|
|
ASSERT_EQ(Status::OK,
|
|
plugin.setPropertyString(hidl_string("origin"),
|
|
hidl_string(params.origin)));
|
|
}
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> /* hSessionId */) {
|
|
EXPECT_EQ(Status::OK, status);
|
|
});
|
|
// Test setting an origin while sessions are opened. This should fail.
|
|
EXPECT_NE(Status::OK,
|
|
plugin.setPropertyString(hidl_string("origin"),
|
|
hidl_string(kOrigin)));
|
|
EXPECT_EQ(Status::OK, plugin.closeSession(toHidlVec(sessionId)));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(OriginTests, WVDrmPluginOriginTest, Values(
|
|
OriginTestVariant("No Origin", kEmptyString, EMPTY_ORIGIN),
|
|
OriginTestVariant("With an Origin", kOrigin, kOrigin.c_str())));
|
|
|
|
TEST_F(WVDrmPluginTest, CanSetSecurityLevel) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
const CdmClientPropertySet* propertySet = NULL;
|
|
|
|
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3),
|
|
testing::Return(wvcdm::NO_ERROR)))
|
|
.WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected mock behavior
|
|
{
|
|
// Provide expected behavior in response to OpenSession and store the
|
|
// property set
|
|
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
SaveArg<1>(&propertySet),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected behavior when plugin requests session control info
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
}
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
// Test forcing L3
|
|
Status status = plugin.setPropertyString(hidl_string("securityLevel"),
|
|
hidl_string("L3"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
EXPECT_STREQ("L3", propertySet->security_level().c_str());
|
|
status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
// Test returning to L1 on an L3 device (Should Fail)
|
|
status = plugin.setPropertyString(hidl_string("securityLevel"),
|
|
hidl_string("L1"));
|
|
ASSERT_NE(Status::OK, status);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
EXPECT_STREQ("L3", propertySet->security_level().c_str());
|
|
status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
// Test returning to L1 on an L1 device
|
|
status = plugin.setPropertyString(hidl_string("securityLevel"),
|
|
hidl_string("L1"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
EXPECT_STREQ("", propertySet->security_level().c_str());
|
|
status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
// Test un-forcing a level (first forcing to L3 so we have something to reset)
|
|
status = plugin.setPropertyString(hidl_string("securityLevel"),
|
|
hidl_string("L3"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
EXPECT_STREQ("L3", propertySet->security_level().c_str());
|
|
status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
status = plugin.setPropertyString(hidl_string("securityLevel"),
|
|
hidl_string(""));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
EXPECT_STREQ("", propertySet->security_level().c_str());
|
|
status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
// Test nonsense (Should Fail)
|
|
status = plugin.setPropertyString(hidl_string("securityLevel"),
|
|
hidl_string("nonsense"));
|
|
ASSERT_NE(Status::OK, status);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
EXPECT_STREQ("", propertySet->security_level().c_str());
|
|
status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
// Test attempting to force a level with a session open (Should Fail)
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> /* hSessionId */) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
});
|
|
status = plugin.setPropertyString(hidl_string("securityLevel"),
|
|
hidl_string("L3"));
|
|
ASSERT_NE(Status::OK, status);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, CanSetPrivacyMode) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
const CdmClientPropertySet* propertySet = NULL;
|
|
|
|
// Provide expected mock behavior
|
|
{
|
|
// Provide expected behavior in response to OpenSession and store the
|
|
// property set
|
|
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
SaveArg<1>(&propertySet),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected behavior when plugin requests session control info
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
}
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
|
|
// Test turning on privacy mode
|
|
Status status = plugin.setPropertyString(hidl_string("privacyMode"),
|
|
hidl_string("enable"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_TRUE(propertySet->use_privacy_mode());
|
|
|
|
// Test turning off privacy mode
|
|
status = plugin.setPropertyString(hidl_string("privacyMode"),
|
|
hidl_string("disable"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_FALSE(propertySet->use_privacy_mode());
|
|
|
|
// Test nonsense (Should Fail)
|
|
status = plugin.setPropertyString(hidl_string("privacyMode"),
|
|
hidl_string("nonsense"));
|
|
ASSERT_NE(Status::OK, status);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, CanSetServiceCertificate) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
const CdmClientPropertySet* propertySet = NULL;
|
|
|
|
static const size_t kPrivacyCertSize = 256;
|
|
uint8_t privacyCertRaw[kPrivacyCertSize];
|
|
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(privacyCertRaw, sizeof(uint8_t), kPrivacyCertSize, fp);
|
|
fclose(fp);
|
|
|
|
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(*cdm, OpenSession(_, _, _, _, _))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
SaveArg<1>(&propertySet),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected behavior when plugin requests session control info
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
}
|
|
|
|
// Validate that the certificate is validated. Accept it once and reject it
|
|
// once. Note that there is no expected call for when the certificate is
|
|
// cleared.
|
|
EXPECT_CALL(*cdm, IsValidServiceCertificate(strPrivacyCert))
|
|
.WillOnce(testing::Return(true))
|
|
.WillOnce(testing::Return(false));
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
|
|
// Test setting a certificate
|
|
Status status = plugin.setPropertyByteArray(hidl_string("serviceCertificate"),
|
|
toHidlVec(privacyCert));
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_THAT(propertySet->service_certificate(),
|
|
ElementsAreArray(privacyCertRaw, kPrivacyCertSize));
|
|
|
|
// Test clearing a certificate
|
|
status = plugin.setPropertyByteArray(hidl_string("serviceCertificate"),
|
|
toHidlVec(emptyVector));
|
|
ASSERT_EQ(Status::OK, status);
|
|
EXPECT_EQ(0u, propertySet->service_certificate().size());
|
|
|
|
// Test setting a certificate and having it fail
|
|
status = plugin.setPropertyByteArray(hidl_string("serviceCertificate"),
|
|
toHidlVec(privacyCert));
|
|
ASSERT_NE(Status::OK, status);
|
|
EXPECT_EQ(0u, propertySet->service_certificate().size());
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, CanSetSessionSharing) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
const CdmClientPropertySet* propertySet = NULL;
|
|
|
|
// Provide expected mock behavior
|
|
{
|
|
// Provide expected behavior in response to OpenSession and store the
|
|
// property set
|
|
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
SaveArg<1>(&propertySet),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected behavior when plugin requests session control info
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
}
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
status_t res;
|
|
|
|
// Test turning on session sharing
|
|
Status status = plugin.setPropertyString(hidl_string("sessionSharing"),
|
|
hidl_string("enable"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
EXPECT_TRUE(propertySet->is_session_sharing_enabled());
|
|
status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
// Test turning off session sharing
|
|
status = plugin.setPropertyString(hidl_string("sessionSharing"),
|
|
hidl_string("disable"));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
EXPECT_FALSE(propertySet->is_session_sharing_enabled());
|
|
status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
|
|
// Test nonsense (Should Fail)
|
|
status = plugin.setPropertyString(hidl_string("sessionSharing"),
|
|
hidl_string("nonsense"));
|
|
ASSERT_NE(Status::OK, status);
|
|
|
|
// Test changing sharing with a session open (Should Fail)
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
status = plugin.setPropertyString(hidl_string("sessionSharing"),
|
|
hidl_string("enable"));
|
|
ASSERT_NE(Status::OK, status);
|
|
|
|
status = plugin.closeSession(toHidlVec(sessionId));
|
|
ASSERT_EQ(Status::OK, status);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginTest, AllowsStoringOfSessionSharingId) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
StrictMock<MockCrypto> crypto;
|
|
std::string appPackageName;
|
|
|
|
CdmClientPropertySet* propertySet = NULL;
|
|
|
|
uint32_t sharingId;
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(&sharingId, sizeof(uint32_t), 1, fp);
|
|
fclose(fp);
|
|
|
|
// Provide expected mock behavior
|
|
{
|
|
// Provide expected behavior in response to OpenSession and store the
|
|
// property set
|
|
EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _))
|
|
.WillRepeatedly(DoAll(SetArgPointee<4>(cdmSessionId),
|
|
SaveArg<1>(&propertySet),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
// Provide expected behavior when plugin requests session control info
|
|
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
|
|
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
|
|
|
|
EXPECT_CALL(*cdm, CloseSession(_))
|
|
.Times(AtLeast(0));
|
|
}
|
|
|
|
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
|
|
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
|
|
ASSERT_EQ(Status::OK, status);
|
|
sessionId.clear();
|
|
sessionId.assign(hSessionId.data(), hSessionId.data() + hSessionId.size());
|
|
});
|
|
|
|
ASSERT_THAT(propertySet, NotNull());
|
|
propertySet->set_session_sharing_id(sharingId);
|
|
EXPECT_EQ(sharingId, propertySet->session_sharing_id());
|
|
}
|
|
|
|
} // namespace widevine
|
|
} // namespace V1_0
|
|
} // namespace drm
|
|
} // namespace hardware
|
|
} // namespace wvdrm
|
|
|