1. "Change CdmResponseType from enum into a struct" Merged from http://go/wvgerrit/163199 Bug: 253271674 2. "Log request information when server returns 401" Bug: 260760387 Bug: 186031735 Merged from http://go/wvgerrit/162798 3. "Specify server version on the command line" Bug: 251599048 Merged from http://go/wvgerrit/158897 Test: build android.hardware.drm-service.widevine Test: Netflix and Play Movies & TV Test: build_and_run_all_unit_tests.sh Bug: 253271674 Change-Id: I70c950acce070609ee0343920ec68e66b058bc23
662 lines
21 KiB
C++
662 lines
21 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 "WVCryptoPluginTest"
|
|
#include <utils/Log.h>
|
|
|
|
#include <map>
|
|
#include <random>
|
|
#include <stdio.h>
|
|
#include <string>
|
|
#include <sys/mman.h>
|
|
#include <vector>
|
|
|
|
#include <aidlcommonsupport/NativeHandle.h>
|
|
|
|
#include <cutils/ashmem.h>
|
|
|
|
#include "OEMCryptoCENC.h"
|
|
#include "WVCryptoPlugin.h"
|
|
#include "wv_cdm_constants.h"
|
|
#include "wv_cdm_types.h"
|
|
#include "wv_content_decryption_module.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace wvdrm {
|
|
namespace hardware {
|
|
namespace drm {
|
|
namespace widevine {
|
|
|
|
using ::aidl::android::hardware::common::Ashmem;
|
|
using ::aidl::android::hardware::drm::DecryptArgs;
|
|
using ::aidl::android::hardware::drm::DestinationBuffer;
|
|
using ::aidl::android::hardware::drm::Mode;
|
|
using ::aidl::android::hardware::drm::Pattern;
|
|
using ::aidl::android::hardware::drm::SharedBuffer;
|
|
using ::aidl::android::hardware::drm::Status;
|
|
using ::aidl::android::hardware::drm::SubSample;
|
|
|
|
using ::android::sp;
|
|
|
|
using ::testing::_;
|
|
using ::testing::DefaultValue;
|
|
using ::testing::DoAll;
|
|
using ::testing::ElementsAreArray;
|
|
using ::testing::Field;
|
|
using ::testing::InSequence;
|
|
using ::testing::Matcher;
|
|
using ::testing::NiceMock;
|
|
using ::testing::SetArgPointee;
|
|
using ::testing::Test;
|
|
using ::testing::Truly;
|
|
using ::testing::Value;
|
|
using ::testing::internal::ElementsAreArrayMatcher;
|
|
|
|
using wvcdm::CdmCipherMode;
|
|
using wvcdm::CdmDecryptionParametersV16;
|
|
using wvcdm::CdmDecryptionSample;
|
|
using wvcdm::CdmDecryptionSubsample;
|
|
using wvcdm::CdmQueryMap;
|
|
using wvcdm::CdmResponseType;
|
|
using wvcdm::CdmSessionId;
|
|
using wvcdm::kCipherModeCbc;
|
|
using wvcdm::kCipherModeCtr;
|
|
using wvcdm::KEY_ID_SIZE;
|
|
using wvcdm::KEY_IV_SIZE;
|
|
using wvcdm::KeyId;
|
|
using wvcdm::QUERY_KEY_SECURITY_LEVEL;
|
|
using wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1;
|
|
using wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3;
|
|
|
|
class MockCDM : public wvcdm::WvContentDecryptionModule {
|
|
public:
|
|
MOCK_METHOD(bool, IsOpenSession, (const CdmSessionId &), (override));
|
|
|
|
MOCK_METHOD(CdmResponseType, DecryptV16,
|
|
(const CdmSessionId &, bool, const CdmDecryptionParametersV16 &),
|
|
(override));
|
|
|
|
MOCK_METHOD(CdmResponseType, QuerySessionStatus,
|
|
(const CdmSessionId &, CdmQueryMap *), (override));
|
|
};
|
|
|
|
class WVCryptoPluginTest : public Test {
|
|
protected:
|
|
static constexpr uint32_t kSessionIdSize = 16;
|
|
uint8_t *pDest = nullptr;
|
|
uint8_t *pSrc = nullptr;
|
|
uint8_t blank[1] = {}; // Some compilers will not accept 0.
|
|
uint8_t sessionId[kSessionIdSize];
|
|
uint32_t nextBufferId = 0;
|
|
std::map<void *, uint32_t> heapBases;
|
|
android::sp<NiceMock<MockCDM>> mCdm = nullptr;
|
|
std::shared_ptr<WVCryptoPlugin> mPlugin = nullptr;
|
|
std::shared_ptr<WVCryptoPlugin> mPluginNoSid = nullptr;
|
|
|
|
public:
|
|
void SetUp() override {
|
|
FILE *fp = fopen("/dev/urandom", "r");
|
|
ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom";
|
|
fread(sessionId, sizeof(uint8_t), kSessionIdSize, fp);
|
|
fclose(fp);
|
|
|
|
// Set default CdmResponseType value for gMock
|
|
DefaultValue<CdmResponseType>::Set(CdmResponseType(wvcdm::NO_ERROR));
|
|
heapBases.clear();
|
|
|
|
mCdm = new NiceMock<MockCDM>();
|
|
ASSERT_TRUE(mCdm) << "Failed to create mocked CDM";
|
|
mPlugin = ::ndk::SharedRefBase::make<WVCryptoPlugin>(
|
|
sessionId, kSessionIdSize, mCdm.get());
|
|
ASSERT_TRUE(mPlugin) << "Failed to create crypto plugin";
|
|
|
|
mPluginNoSid =
|
|
::ndk::SharedRefBase::make<WVCryptoPlugin>(blank, 0, mCdm.get());
|
|
ASSERT_TRUE(mPluginNoSid) << "Failed to create crypto plugin w/o sid";
|
|
}
|
|
|
|
void TearDown() override {
|
|
if (mCdm)
|
|
mCdm.clear();
|
|
if (mPlugin)
|
|
mPlugin.reset();
|
|
if (mPluginNoSid)
|
|
mPluginNoSid.reset();
|
|
}
|
|
|
|
void getDecryptMemory(std::shared_ptr<WVCryptoPlugin> plugin, size_t size,
|
|
size_t index, SharedBuffer &out) {
|
|
out.bufferId = static_cast<int32_t>(index);
|
|
out.offset = 0;
|
|
out.size = static_cast<int64_t>(size);
|
|
|
|
int fd = ashmem_create_region("mediacryptoTestSharedMemory", size);
|
|
EXPECT_GE(fd, 0);
|
|
EXPECT_EQ(size, ashmem_get_size_region(fd));
|
|
auto handle = native_handle_create(1, 0);
|
|
handle->data[0] = fd;
|
|
out.handle = ::android::makeToAidl(handle);
|
|
|
|
auto ret = plugin->setSharedBufferBase(out);
|
|
EXPECT_TRUE(ret.isOk()) << "WVCryptoPlugin failed to setSharedBufferBase";
|
|
native_handle_delete(handle);
|
|
}
|
|
|
|
uint8_t *fillRandom(const ::aidl::android::hardware::drm::SharedBuffer &buf) {
|
|
std::random_device rd;
|
|
std::mt19937 rand(rd());
|
|
|
|
auto fd = buf.handle.fds[0].get();
|
|
size_t size = buf.size;
|
|
uint8_t *base = static_cast<uint8_t *>(
|
|
mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
|
EXPECT_NE(MAP_FAILED, base);
|
|
auto p = reinterpret_cast<uint32_t *>(base);
|
|
for (size_t i = 0; i < size / sizeof(uint32_t); i++) {
|
|
p[i] = rand();
|
|
}
|
|
return base;
|
|
}
|
|
};
|
|
|
|
TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) {
|
|
CdmQueryMap l1Map;
|
|
l1Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
|
|
CdmQueryMap l3Map;
|
|
l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
|
|
|
|
// Provide the expected behavior for IsOpenSession
|
|
EXPECT_CALL(*mCdm, IsOpenSession(_)).WillRepeatedly(testing::Return(true));
|
|
|
|
// Specify the expected calls to QuerySessionStatus
|
|
EXPECT_CALL(*mCdm, QuerySessionStatus(_, _))
|
|
.WillOnce(DoAll(SetArgPointee<1>(l1Map),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))))
|
|
.WillOnce(DoAll(SetArgPointee<1>(l3Map),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
|
|
bool isSecure = false;
|
|
auto ret = mPlugin->requiresSecureDecoderComponent("video/mp4", &isSecure);
|
|
EXPECT_TRUE(isSecure)
|
|
<< "WVCryptoPlugin incorrectly allows an insecure video decoder on L1";
|
|
ret = mPlugin->requiresSecureDecoderComponent("video/mp4", &isSecure);
|
|
EXPECT_FALSE(isSecure)
|
|
<< "WVCryptoPlugin incorrectly expects a secure video decoder on L3";
|
|
ret = mPlugin->requiresSecureDecoderComponent("audio/aac", &isSecure);
|
|
EXPECT_FALSE(isSecure)
|
|
<< "WVCryptoPlugin incorrectly expects a secure audio decoder";
|
|
}
|
|
|
|
// TODO(b/28295739):
|
|
// Add New MediaCrypto Unit Tests for CBC & Pattern Mode in cdmPatternDesc.
|
|
|
|
// Predicate that validates that the fields of a passed-in
|
|
// CdmDecryptionParametersV16 match the values it was given at construction
|
|
// time.
|
|
//
|
|
// This could be done with a huge pile of gMock matchers, but it is ugly and
|
|
// unmaintainable, particularly once you get into validating the subsamples. The
|
|
// logic here is complex enough to warrant a custom matcher for this one test.
|
|
class CDPMatcher {
|
|
public:
|
|
// TODO(b/35259313): Uncomment the removed parameters once the matcher can
|
|
// convert them from AIDL accesses to physical addresses.
|
|
CDPMatcher(const uint8_t *keyId, bool isSecure, Mode cipherMode,
|
|
const Pattern &pattern, const uint8_t * /* input */,
|
|
size_t inputLength, const uint8_t * /* output */,
|
|
size_t outputLength, const uint8_t *iv,
|
|
const SubSample *subsamples, size_t subsamplesLength)
|
|
: mKeyId(keyId, keyId + KEY_ID_SIZE), mIsSecure(isSecure),
|
|
mCipherMode(cipherMode), mPattern(pattern), /* mInput(input), */
|
|
mInputLength(inputLength), /* mOutput(output), */
|
|
mOutputLength(outputLength), mIv(iv, iv + KEY_IV_SIZE),
|
|
mSubsamples(subsamples, subsamples + subsamplesLength) {}
|
|
|
|
bool operator()(const CdmDecryptionParametersV16 ¶ms) const {
|
|
if (mCipherMode == Mode::AES_CTR && params.cipher_mode != kCipherModeCtr) {
|
|
return false;
|
|
} else if (mCipherMode == Mode::AES_CBC &&
|
|
params.cipher_mode != kCipherModeCbc) {
|
|
return false;
|
|
}
|
|
|
|
if (params.key_id != mKeyId || params.is_secure != mIsSecure ||
|
|
params.pattern.encrypt_blocks != mPattern.encryptBlocks ||
|
|
params.pattern.skip_blocks != mPattern.skipBlocks ||
|
|
params.samples.size() != 1) {
|
|
return false;
|
|
}
|
|
|
|
const CdmDecryptionSample &sample = params.samples[0];
|
|
if (sample.encrypt_buffer_length != mInputLength ||
|
|
sample.decrypt_buffer_size != mOutputLength ||
|
|
sample.decrypt_buffer_offset != 0 || sample.iv != mIv ||
|
|
sample.subsamples.size() != mSubsamples.size()) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < mSubsamples.size(); ++i) {
|
|
const SubSample &androidSubsample = mSubsamples[i];
|
|
const CdmDecryptionSubsample &cdmSubsample = sample.subsamples[i];
|
|
|
|
if (cdmSubsample.clear_bytes != androidSubsample.numBytesOfClearData ||
|
|
cdmSubsample.protected_bytes !=
|
|
androidSubsample.numBytesOfEncryptedData) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const KeyId mKeyId;
|
|
const bool mIsSecure;
|
|
const Mode mCipherMode;
|
|
const Pattern mPattern;
|
|
const size_t mInputLength;
|
|
const size_t mOutputLength;
|
|
const std::vector<uint8_t> mIv;
|
|
const std::vector<SubSample> mSubsamples;
|
|
};
|
|
|
|
TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
|
|
constexpr size_t kSubSampleCount = 6;
|
|
SubSample subSamples[kSubSampleCount];
|
|
memset(subSamples, 0, sizeof(subSamples));
|
|
subSamples[0].numBytesOfEncryptedData = 16;
|
|
subSamples[1].numBytesOfClearData = 16;
|
|
subSamples[1].numBytesOfEncryptedData = 16;
|
|
subSamples[2].numBytesOfEncryptedData = 8;
|
|
subSamples[3].numBytesOfClearData = 29;
|
|
subSamples[3].numBytesOfEncryptedData = 24;
|
|
subSamples[4].numBytesOfEncryptedData = 60;
|
|
subSamples[5].numBytesOfEncryptedData = 16;
|
|
|
|
std::vector<SubSample> subSamplesVec(
|
|
subSamples, subSamples + sizeof(subSamples) / sizeof(subSamples[0]));
|
|
|
|
int64_t totalSize = 0;
|
|
for (size_t i = 0; i < subSamplesVec.size(); ++i) {
|
|
totalSize += subSamples[i].numBytesOfClearData;
|
|
totalSize += subSamples[i].numBytesOfEncryptedData;
|
|
}
|
|
|
|
uint8_t keyId[KEY_ID_SIZE];
|
|
uint8_t iv[KEY_IV_SIZE];
|
|
uint8_t sessionId[kSessionIdSize];
|
|
|
|
static const size_t kDataSize = 185;
|
|
|
|
FILE *fp = fopen("/dev/urandom", "r");
|
|
ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom";
|
|
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
|
fclose(fp);
|
|
|
|
SharedBuffer sourceBuffer;
|
|
getDecryptMemory(mPlugin, kDataSize, 0, sourceBuffer);
|
|
auto sourceBase = fillRandom(sourceBuffer);
|
|
|
|
SharedBuffer destBuffer;
|
|
getDecryptMemory(mPlugin, kDataSize, 0, destBuffer);
|
|
auto destBase = fillRandom(destBuffer);
|
|
|
|
SharedBuffer sourceRange;
|
|
sourceRange.bufferId = 0;
|
|
sourceRange.offset = 0;
|
|
sourceRange.size = totalSize;
|
|
|
|
SharedBuffer destRange;
|
|
destRange.bufferId = 0;
|
|
destRange.offset = 0;
|
|
destRange.size = totalSize;
|
|
|
|
Pattern noPattern = {0, 0};
|
|
|
|
// Provide the expected behavior for IsOpenSession
|
|
EXPECT_CALL(*mCdm, IsOpenSession(_)).WillRepeatedly(testing::Return(true));
|
|
|
|
// TODO, initialize pSrc and pDest
|
|
// Specify the expected calls to Decrypt
|
|
CDPMatcher paramsMatcher(keyId, false, Mode::AES_CTR, noPattern, pSrc,
|
|
kDataSize, pDest, kDataSize, iv, subSamples,
|
|
kSubSampleCount);
|
|
|
|
EXPECT_CALL(*mCdm, DecryptV16(ElementsAreArray(sessionId, kSessionIdSize),
|
|
true, Truly(paramsMatcher)))
|
|
.Times(1);
|
|
|
|
DecryptArgs args;
|
|
args.secure = false;
|
|
args.keyId = std::vector(keyId, keyId + sizeof(keyId) / sizeof(keyId[0]));
|
|
args.iv = std::vector(iv, iv + sizeof(iv) / sizeof(iv[0]));
|
|
args.mode = Mode::AES_CTR;
|
|
args.pattern = noPattern;
|
|
args.subSamples = subSamplesVec;
|
|
args.source = std::move(sourceRange);
|
|
args.offset = 0;
|
|
args.destination = std::move(destRange);
|
|
|
|
std::vector<uint8_t> sessionIdVector(sessionId, sessionId + kSessionIdSize);
|
|
auto ret = mPlugin->setMediaDrmSession(sessionIdVector);
|
|
EXPECT_TRUE(ret.isOk());
|
|
|
|
int32_t bytesWritten = 0;
|
|
ret = mPlugin->decrypt(args, &bytesWritten);
|
|
EXPECT_TRUE(ret.isOk()) << ret.getMessage();
|
|
std::string errorMessage(ret.getMessage());
|
|
EXPECT_EQ(kDataSize, bytesWritten)
|
|
<< "WVCryptoPlugin decrypted the wrong number of bytes";
|
|
EXPECT_EQ(0u, errorMessage.size())
|
|
<< "WVCryptoPlugin reported a detailed error message.";
|
|
|
|
munmap(sourceBase, totalSize);
|
|
munmap(destBase, totalSize);
|
|
}
|
|
|
|
TEST_F(WVCryptoPluginTest, RejectsCens) {
|
|
constexpr size_t kSubSampleCount = 2;
|
|
SubSample subSamples[kSubSampleCount];
|
|
memset(subSamples, 0, sizeof(subSamples));
|
|
subSamples[0].numBytesOfEncryptedData = 16;
|
|
subSamples[1].numBytesOfClearData = 16;
|
|
subSamples[1].numBytesOfEncryptedData = 16;
|
|
|
|
std::vector<SubSample> subSamplesVec(
|
|
subSamples, subSamples + sizeof(subSamples) / sizeof(subSamples[0]));
|
|
|
|
int64_t totalSize = 0;
|
|
for (size_t i = 0; i < subSamplesVec.size(); ++i) {
|
|
totalSize += subSamples[i].numBytesOfClearData;
|
|
totalSize += subSamples[i].numBytesOfEncryptedData;
|
|
}
|
|
|
|
uint8_t keyId[KEY_ID_SIZE];
|
|
uint8_t iv[KEY_IV_SIZE];
|
|
|
|
static const size_t kDataSize = 48;
|
|
|
|
FILE *fp = fopen("/dev/urandom", "r");
|
|
ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom";
|
|
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
|
fread(sessionId, sizeof(uint8_t), kSessionIdSize, fp);
|
|
fclose(fp);
|
|
|
|
// Provide the expected behavior for IsOpenSession
|
|
EXPECT_CALL(*mCdm, IsOpenSession(_)).WillRepeatedly(testing::Return(true));
|
|
|
|
// Refuse calls to Decrypt
|
|
EXPECT_CALL(*mCdm, DecryptV16(_, _, _)).Times(0);
|
|
|
|
SharedBuffer sourceBuffer;
|
|
getDecryptMemory(mPlugin, kDataSize, 0, sourceBuffer);
|
|
auto sourceBase = fillRandom(sourceBuffer);
|
|
|
|
SharedBuffer destBuffer;
|
|
getDecryptMemory(mPlugin, kDataSize, 0, destBuffer);
|
|
auto destBase = fillRandom(destBuffer);
|
|
|
|
SharedBuffer sourceRange;
|
|
sourceRange.bufferId = 0;
|
|
sourceRange.offset = 0;
|
|
sourceRange.size = totalSize;
|
|
|
|
SharedBuffer destRange;
|
|
destRange.bufferId = 0;
|
|
destRange.offset = 0;
|
|
destRange.size = totalSize;
|
|
|
|
Pattern recommendedPattern = {1, 9};
|
|
std::vector<uint8_t> keyIdVec(keyId, keyId + KEY_ID_SIZE);
|
|
std::vector<uint8_t> ivVec(iv, iv + KEY_IV_SIZE);
|
|
|
|
DecryptArgs args;
|
|
args.secure = false;
|
|
args.keyId = keyIdVec;
|
|
args.iv = ivVec;
|
|
args.mode = Mode::AES_CTR;
|
|
args.pattern = recommendedPattern;
|
|
args.subSamples = subSamplesVec;
|
|
args.source = std::move(sourceRange);
|
|
args.offset = 0;
|
|
args.destination = std::move(destRange);
|
|
|
|
int32_t bytesWritten = -1;
|
|
auto ret = mPlugin->decrypt(args, &bytesWritten);
|
|
EXPECT_EQ(static_cast<int>(Status::BAD_VALUE), ret.getServiceSpecificError());
|
|
EXPECT_EQ(bytesWritten, 0);
|
|
|
|
munmap(sourceBase, totalSize);
|
|
munmap(destBase, totalSize);
|
|
}
|
|
|
|
TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
|
|
uint8_t keyId[KEY_ID_SIZE];
|
|
uint8_t iv[KEY_IV_SIZE];
|
|
|
|
static const size_t kDataSize = 32;
|
|
|
|
FILE *fp = fopen("/dev/urandom", "r");
|
|
ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom";
|
|
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
|
fclose(fp);
|
|
|
|
SubSample subSample;
|
|
subSample.numBytesOfClearData = 16;
|
|
subSample.numBytesOfEncryptedData = 16;
|
|
std::vector<SubSample> subSamples;
|
|
subSamples.push_back(subSample);
|
|
|
|
int64_t totalSize = 0;
|
|
for (size_t i = 0; i < subSamples.size(); ++i) {
|
|
totalSize += subSamples[i].numBytesOfClearData;
|
|
totalSize += subSamples[i].numBytesOfEncryptedData;
|
|
}
|
|
|
|
// Provide the expected behavior for IsOpenSession
|
|
EXPECT_CALL(*mCdm, IsOpenSession(_)).WillRepeatedly(testing::Return(true));
|
|
|
|
// Specify the expected calls to Decrypt
|
|
{
|
|
InSequence calls;
|
|
|
|
typedef CdmDecryptionParametersV16 CDP;
|
|
|
|
EXPECT_CALL(*mCdm, DecryptV16(_, _, Field(&CDP::is_secure, false)))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(*mCdm, DecryptV16(_, _, Field(&CDP::is_secure, true))).Times(1);
|
|
}
|
|
|
|
SharedBuffer sourceBuffer;
|
|
getDecryptMemory(mPlugin, kDataSize, 0, sourceBuffer);
|
|
auto sourceBase = fillRandom(sourceBuffer);
|
|
|
|
SharedBuffer destBuffer;
|
|
getDecryptMemory(mPlugin, kDataSize, 0, destBuffer);
|
|
auto destBase = fillRandom(destBuffer);
|
|
|
|
SharedBuffer sourceRange;
|
|
sourceRange.bufferId = 0;
|
|
sourceRange.offset = 0;
|
|
sourceRange.size = totalSize;
|
|
|
|
SharedBuffer destRange;
|
|
destRange.bufferId = 0;
|
|
destRange.offset = 0;
|
|
destRange.size = totalSize;
|
|
|
|
Pattern noPattern = {0, 0};
|
|
std::vector<uint8_t> keyIdVec(keyId, keyId + KEY_ID_SIZE);
|
|
std::vector<uint8_t> ivVec(iv, iv + KEY_IV_SIZE);
|
|
|
|
DecryptArgs args;
|
|
args.secure = false;
|
|
args.keyId = keyIdVec;
|
|
args.iv = ivVec;
|
|
args.mode = Mode::AES_CTR;
|
|
args.pattern = noPattern;
|
|
args.subSamples = subSamples;
|
|
args.source = std::move(sourceRange);
|
|
args.offset = 0;
|
|
args.destination = std::move(destRange);
|
|
|
|
int32_t bytesWritten = 0;
|
|
auto ret = mPlugin->decrypt(args, &bytesWritten);
|
|
EXPECT_TRUE(ret.isOk());
|
|
std::string errorMessage(ret.getMessage());
|
|
EXPECT_EQ(0u, errorMessage.size())
|
|
<< "WVCryptoPlugin reported a detailed error message.";
|
|
|
|
args.secure = true;
|
|
bytesWritten = 0;
|
|
ret = mPlugin->decrypt(args, &bytesWritten);
|
|
EXPECT_TRUE(ret.isOk());
|
|
std::string errorMessage2(ret.getMessage());
|
|
EXPECT_EQ(0u, errorMessage2.size())
|
|
<< "WVCryptoPlugin reported a detailed error message.";
|
|
|
|
munmap(sourceBase, totalSize);
|
|
munmap(destBase, totalSize);
|
|
}
|
|
|
|
TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
|
|
uint8_t keyId[KEY_ID_SIZE];
|
|
uint8_t iv[KEY_IV_SIZE];
|
|
uint8_t sessionId2[kSessionIdSize];
|
|
|
|
static constexpr size_t kDataSize = 32;
|
|
|
|
FILE *fp = fopen("/dev/urandom", "r");
|
|
ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom";
|
|
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
|
fread(sessionId2, sizeof(uint8_t), kSessionIdSize, fp);
|
|
fclose(fp);
|
|
|
|
SubSample subSample;
|
|
subSample.numBytesOfClearData = 16;
|
|
subSample.numBytesOfEncryptedData = 16;
|
|
std::vector<SubSample> subSamples;
|
|
subSamples.push_back(subSample);
|
|
|
|
int64_t totalSize = 0;
|
|
for (size_t i = 0; i < subSamples.size(); ++i) {
|
|
totalSize += subSamples[i].numBytesOfClearData;
|
|
totalSize += subSamples[i].numBytesOfEncryptedData;
|
|
}
|
|
|
|
std::vector<uint8_t> sessionIdVector(sessionId, sessionId + kSessionIdSize);
|
|
std::vector<uint8_t> sessionId2Vector(sessionId2,
|
|
sessionId2 + kSessionIdSize);
|
|
|
|
// Provide the expected behavior for IsOpenSession
|
|
EXPECT_CALL(*mCdm, IsOpenSession(_)).WillRepeatedly(testing::Return(true));
|
|
|
|
// Specify the expected calls to Decrypt
|
|
{
|
|
InSequence calls;
|
|
|
|
EXPECT_CALL(*mCdm,
|
|
DecryptV16(ElementsAreArray(sessionId, kSessionIdSize), _, _))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(*mCdm,
|
|
DecryptV16(ElementsAreArray(sessionId2, kSessionIdSize), _, _))
|
|
.Times(1);
|
|
}
|
|
|
|
SharedBuffer sourceBuffer;
|
|
getDecryptMemory(mPluginNoSid, kDataSize, 0, sourceBuffer);
|
|
auto sourceBase = fillRandom(sourceBuffer);
|
|
|
|
SharedBuffer destBuffer;
|
|
getDecryptMemory(mPluginNoSid, kDataSize, 0, destBuffer);
|
|
auto destBase = fillRandom(destBuffer);
|
|
|
|
SharedBuffer sourceRange;
|
|
sourceRange.bufferId = 0;
|
|
sourceRange.offset = 0;
|
|
sourceRange.size = totalSize;
|
|
|
|
SharedBuffer destRange;
|
|
destRange.bufferId = 0;
|
|
destRange.offset = 0;
|
|
destRange.size = totalSize;
|
|
|
|
Pattern noPattern = {0, 0};
|
|
std::vector<uint8_t> keyIdVec(keyId, keyId + KEY_ID_SIZE);
|
|
std::vector<uint8_t> ivVec(iv, iv + KEY_IV_SIZE);
|
|
|
|
DecryptArgs args;
|
|
args.secure = false;
|
|
args.keyId = keyIdVec;
|
|
args.iv = ivVec;
|
|
args.mode = Mode::AES_CTR;
|
|
args.pattern = noPattern;
|
|
args.subSamples = subSamples;
|
|
args.source = std::move(sourceRange);
|
|
args.offset = 0;
|
|
args.destination = std::move(destRange);
|
|
|
|
auto ret = mPluginNoSid->setMediaDrmSession(sessionIdVector);
|
|
EXPECT_TRUE(ret.isOk());
|
|
|
|
int32_t bytesWritten = 0;
|
|
ret = mPluginNoSid->decrypt(args, &bytesWritten);
|
|
std::string errorMessage(ret.getMessage());
|
|
EXPECT_TRUE(ret.isOk());
|
|
EXPECT_EQ(0u, errorMessage.size())
|
|
<< "WVCryptoPlugin reported a detailed error message.";
|
|
|
|
ret = mPluginNoSid->setMediaDrmSession(sessionId2Vector);
|
|
EXPECT_TRUE(ret.isOk());
|
|
|
|
bytesWritten = 0;
|
|
ret = mPluginNoSid->decrypt(args, &bytesWritten);
|
|
EXPECT_TRUE(ret.isOk());
|
|
std::string errorMessage2(ret.getMessage());
|
|
EXPECT_EQ(0u, errorMessage2.size())
|
|
<< "WVCryptoPlugin reported a detailed error message.";
|
|
|
|
munmap(sourceBase, totalSize);
|
|
munmap(destBase, totalSize);
|
|
}
|
|
|
|
TEST_F(WVCryptoPluginTest, DisallowsUnopenedSessionIdChanges) {
|
|
std::vector<uint8_t> sessionIdVector(sessionId, sessionId + kSessionIdSize);
|
|
|
|
// Specify the expected calls to IsOpenSession
|
|
{
|
|
InSequence calls;
|
|
|
|
EXPECT_CALL(*mCdm, IsOpenSession(ElementsAreArray(blank, 0)))
|
|
.WillRepeatedly(testing::Return(false));
|
|
|
|
EXPECT_CALL(*mCdm,
|
|
IsOpenSession(ElementsAreArray(sessionId, kSessionIdSize)))
|
|
.WillOnce(testing::Return(false))
|
|
.WillOnce(testing::Return(true));
|
|
}
|
|
|
|
auto ret = mPluginNoSid->setMediaDrmSession(sessionIdVector);
|
|
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_SESSION_NOT_OPENED),
|
|
ret.getServiceSpecificError());
|
|
|
|
ret = mPluginNoSid->setMediaDrmSession(sessionIdVector);
|
|
EXPECT_TRUE(ret.isOk());
|
|
}
|
|
|
|
} // namespace widevine
|
|
} // namespace drm
|
|
} // namespace hardware
|
|
} // namespace wvdrm
|