(This is a merge of http://go/wvgerrit/93829, http://go/wvgerrit/93830, http://go/wvgerrit/93832, http://go/wvgerrit/93833, and http://go/wvgerrit/93834 from the Widevine repo.) This implements the CDM code changes necessary to take advantage of Combined Decrypt Calls on OEMCrypto v16. The result of this is that WVCryptoPlugin is much lighter now because it can pass the full sample down to the core in one call, but CryptoSession is heavier, as it now has to handle more complex fallback logic when devices can't handle multiple subsamples at once. This patch also removes support for the 'cens' and 'cbc1' schema, which are being dropped in OEMCrypto v16. This fixes an overflow in the code for handling those schemas by removing it entirely. This patch also fixes the "in chunks" legacy decrypt path to use larger chunk sizes on devices with higher resource rating tiers. Bug: 135285640 Bug: 123435824 Bug: 138584971 Bug: 139257871 Bug: 78289910 Bug: 149361893 Test: no new CE CDM Unit Test failures Test: Google Play plays Test: Netflix plays Test: no new GTS failures Change-Id: Ic4952c9fa3bc7fd5ed08698e88254380a7a18514
654 lines
22 KiB
C++
654 lines
22 KiB
C++
//
|
|
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine Master
|
|
// License Agreement.
|
|
//
|
|
|
|
//#define LOG_NDEBUG 0
|
|
#define LOG_TAG "WVCryptoPluginTest"
|
|
#include <utils/Log.h>
|
|
|
|
#include <map>
|
|
#include <stdio.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <binder/MemoryDealer.h>
|
|
#include <hidl/Status.h>
|
|
#include <hidlmemory/mapping.h>
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include "wv_cdm_constants.h"
|
|
#include "wv_cdm_types.h"
|
|
#include "wv_content_decryption_module.h"
|
|
#include "HidlTypes.h"
|
|
#include "OEMCryptoCENC.h"
|
|
#include "TypeConvert.h"
|
|
#include "WVCryptoPlugin.h"
|
|
|
|
namespace wvdrm {
|
|
namespace hardware {
|
|
namespace drm {
|
|
namespace V1_2 {
|
|
namespace widevine {
|
|
|
|
using ::android::MemoryDealer;
|
|
|
|
using ::testing::_;
|
|
using ::testing::DefaultValue;
|
|
using ::testing::DoAll;
|
|
using ::testing::ElementsAreArray;
|
|
using ::testing::Field;
|
|
using ::testing::InSequence;
|
|
using ::testing::Matcher;
|
|
using ::testing::SetArgPointee;
|
|
using ::testing::StrictMock;
|
|
using ::testing::Test;
|
|
using ::testing::Truly;
|
|
using ::testing::Value;
|
|
using ::testing::internal::ElementsAreArrayMatcher;
|
|
|
|
using wvcdm::kCipherModeCtr;
|
|
using wvcdm::kCipherModeCbc;
|
|
using wvcdm::CdmCipherMode;
|
|
using wvcdm::CdmDecryptionParametersV16;
|
|
using wvcdm::CdmDecryptionSample;
|
|
using wvcdm::CdmDecryptionSubsample;
|
|
using wvcdm::CdmQueryMap;
|
|
using wvcdm::CdmResponseType;
|
|
using wvcdm::CdmSessionId;
|
|
using wvcdm::KeyId;
|
|
using wvcdm::KEY_ID_SIZE;
|
|
using wvcdm::KEY_IV_SIZE;
|
|
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_METHOD1(IsOpenSession, bool(const CdmSessionId&));
|
|
|
|
MOCK_METHOD3(DecryptV16, CdmResponseType(const CdmSessionId&, bool,
|
|
const CdmDecryptionParametersV16&));
|
|
|
|
MOCK_METHOD2(QuerySessionStatus, CdmResponseType(const CdmSessionId&,
|
|
CdmQueryMap*));
|
|
};
|
|
|
|
class WVCryptoPluginTest : public Test {
|
|
protected:
|
|
static const uint32_t kSessionIdSize = 16;
|
|
uint8_t* pDest = nullptr;
|
|
uint8_t* pSrc = nullptr;
|
|
uint8_t sessionId[kSessionIdSize];
|
|
uint32_t nextBufferId = 0;
|
|
std::map<void *, uint32_t> heapBases;
|
|
|
|
virtual void SetUp() {
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(sessionId, sizeof(uint8_t), kSessionIdSize, fp);
|
|
fclose(fp);
|
|
|
|
// Set default CdmResponseType value for gMock
|
|
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
|
|
heapBases.clear();
|
|
}
|
|
|
|
void setHeapBase(WVCryptoPlugin& plugin,
|
|
const sp<android::IMemoryHeap>& heap) {
|
|
ASSERT_NE(heap, nullptr);
|
|
|
|
void* heapBase = heap->getBase();
|
|
ASSERT_NE(heapBase, nullptr);
|
|
|
|
native_handle_t* nativeHandle = native_handle_create(1, 0);
|
|
ASSERT_NE(nativeHandle, nullptr);
|
|
|
|
nativeHandle->data[0] = heap->getHeapID();
|
|
|
|
auto hidlHandle = hidl_handle(nativeHandle);
|
|
auto hidlMemory = hidl_memory("ashmem", hidlHandle, heap->getSize());
|
|
heapBases.insert(
|
|
std::pair<void*, uint32_t>(heapBase, nextBufferId));
|
|
Return<void> hResult =
|
|
plugin.setSharedBufferBase(hidlMemory, nextBufferId++);
|
|
|
|
ALOGE_IF(!hResult.isOk(), "setHeapBase failed setSharedBufferBase");
|
|
}
|
|
|
|
void toSharedBuffer(WVCryptoPlugin& plugin,
|
|
const sp<android::IMemory>& memory,
|
|
SharedBuffer* buffer) {
|
|
ssize_t offset;
|
|
size_t size;
|
|
|
|
ASSERT_NE(memory, nullptr);
|
|
ASSERT_NE(buffer, nullptr);
|
|
|
|
sp<android::IMemoryHeap> heap = memory->getMemory(&offset, &size);
|
|
ASSERT_NE(heap, nullptr);
|
|
|
|
setHeapBase(plugin, heap);
|
|
buffer->bufferId = heapBases[heap->getBase()];
|
|
buffer->offset = offset >= 0 ? offset : 0;
|
|
buffer->size = size;
|
|
}
|
|
};
|
|
|
|
TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
|
|
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(*cdm, IsOpenSession(_))
|
|
.WillRepeatedly(testing::Return(true));
|
|
|
|
// Specify the expected calls to QuerySessionStatus
|
|
EXPECT_CALL(*cdm, QuerySessionStatus(_, _))
|
|
.WillOnce(DoAll(SetArgPointee<1>(l1Map),
|
|
testing::Return(wvcdm::NO_ERROR)))
|
|
.WillOnce(DoAll(SetArgPointee<1>(l3Map),
|
|
testing::Return(wvcdm::NO_ERROR)));
|
|
|
|
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
|
|
|
|
EXPECT_TRUE(plugin.requiresSecureDecoderComponent("video/mp4")) <<
|
|
"WVCryptoPlugin incorrectly allows an insecure video decoder on L1";
|
|
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("video/mp4")) <<
|
|
"WVCryptoPlugin incorrectly expects a secure video decoder on L3";
|
|
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("audio/aac")) <<
|
|
"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 HIDL 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& params) 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 (// TODO(b/35259313): Convert from a HIDL access to a physical address.
|
|
// sample.encrypt_buffer != mInput ||
|
|
sample.encrypt_buffer_length != mInputLength ||
|
|
// TODO(b/35259313): Convert from a HIDL access to a physical address.
|
|
// sample.decrypt_buffer != mOutput ||
|
|
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;
|
|
// TODO(b/35259313): Uncomment this field once the matcher can convert this
|
|
// from a HIDL access to a physical address.
|
|
// const uint8_t* const mInput;
|
|
const size_t mInputLength;
|
|
// TODO(b/35259313): Uncomment this field once the matcher can convert this
|
|
// from a HIDL access to a physical address.
|
|
// const uint8_t* const mOutput;
|
|
const size_t mOutputLength;
|
|
const std::vector<uint8_t> mIv;
|
|
const std::vector<SubSample> mSubsamples;
|
|
};
|
|
|
|
TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
|
|
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> subSamplesVector(
|
|
subSamples, subSamples + sizeof(subSamples) / sizeof(subSamples[0]));
|
|
auto hSubSamples = hidl_vec<SubSample>(subSamplesVector);
|
|
|
|
uint8_t keyId[KEY_ID_SIZE];
|
|
uint8_t iv[KEY_IV_SIZE];
|
|
|
|
static const size_t kDataSize = 185;
|
|
uint8_t inputData[kDataSize];
|
|
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
|
fread(inputData, sizeof(uint8_t), kDataSize, fp);
|
|
fclose(fp);
|
|
|
|
sp<MemoryDealer> memDealer = new MemoryDealer(
|
|
kDataSize * 2, "WVCryptoPlugin_test");
|
|
sp<android::IMemory> source = memDealer->allocate(kDataSize);
|
|
ASSERT_NE(source, nullptr);
|
|
pSrc = static_cast<uint8_t*>(
|
|
static_cast<void*>(source->unsecurePointer()));
|
|
ASSERT_NE(pSrc, nullptr);
|
|
memcpy(pSrc, inputData, source->size());
|
|
|
|
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
|
|
ASSERT_NE(destination, nullptr);
|
|
pDest = static_cast<uint8_t*>(
|
|
static_cast<void*>(destination->unsecurePointer()));
|
|
ASSERT_NE(pDest, nullptr);
|
|
|
|
Pattern noPattern = { 0, 0 };
|
|
|
|
// Provide the expected behavior for IsOpenSession
|
|
EXPECT_CALL(*cdm, IsOpenSession(_))
|
|
.WillRepeatedly(testing::Return(true));
|
|
|
|
// Specify the expected calls to Decrypt
|
|
CDPMatcher paramsMatcher(keyId, false, Mode::AES_CTR, noPattern, pSrc,
|
|
kDataSize, pDest, kDataSize, iv, subSamples,
|
|
kSubSampleCount);
|
|
|
|
EXPECT_CALL(*cdm, DecryptV16(ElementsAreArray(sessionId, kSessionIdSize),
|
|
true,
|
|
Truly(paramsMatcher)))
|
|
.Times(1);
|
|
|
|
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
|
|
|
|
uint32_t bytesWritten = 0;
|
|
std::string errorDetailMessage;
|
|
DestinationBuffer hDestination;
|
|
hDestination.type = BufferType::SHARED_MEMORY;
|
|
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
|
|
|
|
SharedBuffer sourceBuffer;
|
|
toSharedBuffer(plugin, source, &sourceBuffer);
|
|
|
|
plugin.decrypt(
|
|
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
|
|
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
|
|
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
|
|
EXPECT_EQ(status, Status::OK);
|
|
|
|
bytesWritten = hBytesWritten;
|
|
errorDetailMessage.assign(hDetailedError.c_str());
|
|
});
|
|
|
|
EXPECT_EQ(kDataSize, bytesWritten) <<
|
|
"WVCryptoPlugin decrypted the wrong number of bytes";
|
|
EXPECT_EQ(0u, errorDetailMessage.size()) <<
|
|
"WVCryptoPlugin reported a detailed error message.";
|
|
}
|
|
|
|
TEST_F(WVCryptoPluginTest, RejectsCensAndCbc1) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
|
|
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> subSamplesVector(
|
|
subSamples, subSamples + sizeof(subSamples) / sizeof(subSamples[0]));
|
|
auto hSubSamples = hidl_vec<SubSample>(subSamplesVector);
|
|
|
|
uint8_t keyId[KEY_ID_SIZE];
|
|
uint8_t iv[KEY_IV_SIZE];
|
|
|
|
static const size_t kDataSize = 48;
|
|
uint8_t inputData[kDataSize];
|
|
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
|
fread(inputData, sizeof(uint8_t), kDataSize, fp);
|
|
fclose(fp);
|
|
|
|
sp<MemoryDealer> memDealer = new MemoryDealer(
|
|
kDataSize * 2, "WVCryptoPlugin_test");
|
|
sp<android::IMemory> source = memDealer->allocate(kDataSize);
|
|
ASSERT_NE(source, nullptr);
|
|
pSrc = static_cast<uint8_t*>(
|
|
static_cast<void*>(source->unsecurePointer()));
|
|
ASSERT_NE(pSrc, nullptr);
|
|
memcpy(pSrc, inputData, source->size());
|
|
|
|
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
|
|
ASSERT_NE(destination, nullptr);
|
|
pDest = static_cast<uint8_t*>(
|
|
static_cast<void*>(destination->unsecurePointer()));
|
|
ASSERT_NE(pDest, nullptr);
|
|
|
|
Pattern noPattern = { 0, 0 };
|
|
Pattern recommendedPattern = { 1, 9 };
|
|
|
|
// Provide the expected behavior for IsOpenSession
|
|
EXPECT_CALL(*cdm, IsOpenSession(_))
|
|
.WillRepeatedly(testing::Return(true));
|
|
|
|
// Refuse calls to Decrypt
|
|
EXPECT_CALL(*cdm, DecryptV16(_, _, _))
|
|
.Times(0);
|
|
|
|
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
|
|
|
|
DestinationBuffer hDestination;
|
|
hDestination.type = BufferType::SHARED_MEMORY;
|
|
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
|
|
|
|
SharedBuffer sourceBuffer;
|
|
toSharedBuffer(plugin, source, &sourceBuffer);
|
|
|
|
plugin.decrypt(
|
|
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
|
|
Mode::AES_CTR, recommendedPattern, hSubSamples, sourceBuffer, 0,
|
|
hDestination,
|
|
[&](Status status, uint32_t bytesWritten,
|
|
hidl_string /* errorDetailMessage */) {
|
|
EXPECT_EQ(status, Status::BAD_VALUE);
|
|
EXPECT_EQ(bytesWritten, 0);
|
|
});
|
|
|
|
plugin.decrypt(
|
|
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
|
|
Mode::AES_CBC, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
|
|
[&](Status status, uint32_t bytesWritten,
|
|
hidl_string /* errorDetailMessage */) {
|
|
EXPECT_EQ(status, Status::BAD_VALUE);
|
|
EXPECT_EQ(bytesWritten, 0);
|
|
});
|
|
}
|
|
|
|
TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
|
|
uint8_t keyId[KEY_ID_SIZE];
|
|
uint8_t iv[KEY_IV_SIZE];
|
|
|
|
static const size_t kDataSize = 32;
|
|
uint8_t in[kDataSize];
|
|
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
|
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
|
fread(in, sizeof(uint8_t), kDataSize, fp);
|
|
fclose(fp);
|
|
|
|
SubSample subSample;
|
|
subSample.numBytesOfClearData = 16;
|
|
subSample.numBytesOfEncryptedData = 16;
|
|
std::vector<SubSample> subSampleVector;
|
|
subSampleVector.push_back(subSample);
|
|
auto hSubSamples = hidl_vec<SubSample>(subSampleVector);
|
|
|
|
// Provide the expected behavior for IsOpenSession
|
|
EXPECT_CALL(*cdm, IsOpenSession(_))
|
|
.WillRepeatedly(testing::Return(true));
|
|
|
|
// Specify the expected calls to Decrypt
|
|
{
|
|
InSequence calls;
|
|
|
|
typedef CdmDecryptionParametersV16 CDP;
|
|
|
|
EXPECT_CALL(*cdm, DecryptV16(_, _, Field(&CDP::is_secure, false)))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(*cdm, DecryptV16(_, _, Field(&CDP::is_secure, true)))
|
|
.Times(1);
|
|
}
|
|
|
|
sp<MemoryDealer> memDealer = new MemoryDealer(
|
|
kDataSize * 2, "WVCryptoPlugin_test");
|
|
sp<android::IMemory> source = memDealer->allocate(kDataSize);
|
|
ASSERT_NE(source, nullptr);
|
|
pSrc = static_cast<uint8_t*>(
|
|
static_cast<void*>(source->unsecurePointer()));
|
|
ASSERT_NE(pSrc, nullptr);
|
|
memcpy(pSrc, in, source->size());
|
|
|
|
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
|
|
ASSERT_NE(destination, nullptr);
|
|
pDest = static_cast<uint8_t*>(
|
|
static_cast<void*>(destination->unsecurePointer()));
|
|
ASSERT_NE(pDest, nullptr);
|
|
|
|
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
|
|
|
|
uint32_t bytesWritten = 0;
|
|
std::string errorDetailMessage;
|
|
DestinationBuffer hDestination;
|
|
hDestination.type = BufferType::SHARED_MEMORY;
|
|
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
|
|
Pattern noPattern = { 0, 0 };
|
|
|
|
SharedBuffer sourceBuffer;
|
|
toSharedBuffer(plugin, source, &sourceBuffer);
|
|
|
|
plugin.decrypt(
|
|
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
|
|
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
|
|
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
|
|
EXPECT_EQ(status, Status::OK);
|
|
|
|
bytesWritten = hBytesWritten;
|
|
errorDetailMessage.assign(hDetailedError.c_str());
|
|
});
|
|
|
|
EXPECT_EQ(0u, errorDetailMessage.size()) <<
|
|
"WVCryptoPlugin reported a detailed error message.";
|
|
|
|
plugin.decrypt(
|
|
true, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
|
|
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
|
|
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
|
|
EXPECT_EQ(status, Status::OK);
|
|
|
|
bytesWritten = hBytesWritten;
|
|
errorDetailMessage.assign(hDetailedError.c_str());
|
|
});
|
|
|
|
EXPECT_EQ(0u, errorDetailMessage.size()) <<
|
|
"WVCryptoPlugin reported a detailed error message.";
|
|
}
|
|
|
|
TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
|
|
uint8_t keyId[KEY_ID_SIZE];
|
|
uint8_t iv[KEY_IV_SIZE];
|
|
uint8_t sessionId2[kSessionIdSize];
|
|
|
|
static const size_t kDataSize = 32;
|
|
uint8_t in[kDataSize];
|
|
|
|
FILE* fp = fopen("/dev/urandom", "r");
|
|
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);
|
|
fread(in, sizeof(uint8_t), kDataSize, fp);
|
|
fclose(fp);
|
|
|
|
SubSample subSample;
|
|
subSample.numBytesOfClearData = 16;
|
|
subSample.numBytesOfEncryptedData = 16;
|
|
std::vector<SubSample> subSampleVector;
|
|
subSampleVector.push_back(subSample);
|
|
auto hSubSamples = hidl_vec<SubSample>(subSampleVector);
|
|
|
|
std::vector<uint8_t> sessionIdVector(sessionId, sessionId + kSessionIdSize);
|
|
std::vector<uint8_t> sessionId2Vector(sessionId2,
|
|
sessionId2 + kSessionIdSize);
|
|
|
|
// Provide the expected behavior for IsOpenSession
|
|
EXPECT_CALL(*cdm, IsOpenSession(_))
|
|
.WillRepeatedly(testing::Return(true));
|
|
|
|
// Specify the expected calls to Decrypt
|
|
{
|
|
InSequence calls;
|
|
|
|
EXPECT_CALL(*cdm,
|
|
DecryptV16(ElementsAreArray(sessionId, kSessionIdSize), _, _))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(*cdm,
|
|
DecryptV16(ElementsAreArray(sessionId2, kSessionIdSize), _, _))
|
|
.Times(1);
|
|
}
|
|
|
|
sp<MemoryDealer> memDealer = new MemoryDealer(
|
|
kDataSize * 2, "WVCryptoPlugin_test");
|
|
sp<android::IMemory> source = memDealer->allocate(kDataSize);
|
|
ASSERT_NE(source, nullptr);
|
|
pSrc = static_cast<uint8_t*>(
|
|
static_cast<void*>(source->unsecurePointer()));
|
|
ASSERT_NE(pSrc, nullptr);
|
|
memcpy(pSrc, in, source->size());
|
|
|
|
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
|
|
ASSERT_NE(destination, nullptr);
|
|
pDest = static_cast<uint8_t*>(
|
|
static_cast<void*>(destination->unsecurePointer()));
|
|
ASSERT_NE(pDest, nullptr);
|
|
|
|
uint8_t blank[1]; // Some compilers will not accept 0.
|
|
WVCryptoPlugin plugin(blank, 0, cdm.get());
|
|
|
|
uint32_t bytesWritten = 0;
|
|
std::string errorDetailMessage;
|
|
DestinationBuffer hDestination;
|
|
hDestination.type = BufferType::SHARED_MEMORY;
|
|
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
|
|
Pattern noPattern = { 0, 0 };
|
|
|
|
SharedBuffer sourceBuffer;
|
|
toSharedBuffer(plugin, source, &sourceBuffer);
|
|
|
|
Status status = plugin.setMediaDrmSession(sessionIdVector);
|
|
EXPECT_EQ(status, Status::OK);
|
|
|
|
plugin.decrypt(
|
|
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
|
|
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
|
|
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
|
|
EXPECT_EQ(status, Status::OK);
|
|
|
|
bytesWritten = hBytesWritten;
|
|
errorDetailMessage.assign(hDetailedError.c_str());
|
|
});
|
|
|
|
EXPECT_EQ(0u, errorDetailMessage.size()) <<
|
|
"WVCryptoPlugin reported a detailed error message.";
|
|
|
|
status = plugin.setMediaDrmSession(sessionId2Vector);
|
|
EXPECT_EQ(status, Status::OK);
|
|
|
|
plugin.decrypt(
|
|
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
|
|
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
|
|
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
|
|
EXPECT_EQ(status, Status::OK);
|
|
|
|
bytesWritten = hBytesWritten;
|
|
errorDetailMessage.assign(hDetailedError.c_str());
|
|
});
|
|
|
|
EXPECT_EQ(0u, errorDetailMessage.size()) <<
|
|
"WVCryptoPlugin reported a detailed error message.";
|
|
}
|
|
|
|
TEST_F(WVCryptoPluginTest, DisallowsUnopenedSessionIdChanges) {
|
|
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
|
|
|
|
uint8_t blank[1]; // Some compilers will not accept 0.
|
|
std::vector<uint8_t> sessionIdVector(sessionId, sessionId + kSessionIdSize);
|
|
|
|
// Specify the expected calls to IsOpenSession
|
|
{
|
|
InSequence calls;
|
|
|
|
EXPECT_CALL(*cdm, IsOpenSession(ElementsAreArray(blank, 0)))
|
|
.WillOnce(testing::Return(false));
|
|
|
|
EXPECT_CALL(*cdm, IsOpenSession(ElementsAreArray(sessionId, kSessionIdSize)))
|
|
.WillOnce(testing::Return(false))
|
|
.WillOnce(testing::Return(true));
|
|
}
|
|
|
|
WVCryptoPlugin plugin(blank, 0, cdm.get());
|
|
|
|
Status status = plugin.setMediaDrmSession(sessionIdVector);
|
|
EXPECT_EQ(status, Status::ERROR_DRM_SESSION_NOT_OPENED);
|
|
|
|
status = plugin.setMediaDrmSession(sessionIdVector);
|
|
EXPECT_EQ(status, Status::OK);
|
|
}
|
|
|
|
} // namespace widevine
|
|
} // namespace V1_2
|
|
} // namespace drm
|
|
} // namespace hardware
|
|
} // namespace wvdrm
|