Add vts tests to verify DRM AIDL interface

The DRM AIDL interface is in change 15329852.
The default implementation of the interface is in the clearkey
HAL in change 15958954.

[TODO] APIs pending vts coverage:
+ ICryptoFactory
   + isCryptoSchemeSupported
+ ICryptoPlugin
   + getLogMessages
   + notifyResolution
   + requiresSecureDecoderComponent
+ IDrmFactory
   + getSupportedCryptoSchemes
   + isContentTypeSupported
+ IDrmPlugin
   + decrypt
   + encrypt
   + getLogMessages
   + getMetrics
   + getNumberOfSessions
   + getPropertyByteArray
   + getPropertyString
   + getSecureStop
   + getSecureStopIds
   + getSecureStops
   + queryKeyStatus
   + releaseAllSecureStops
   + releaseSecureStop
   + releaseSecureStops
   + removeAllSecureStops
   + removeKeys
   + removeSecureStop
   + requiresSecureDecoder
   + requiresSecureDecoderDefault
   + restoreKeys
   + setCipherAlgorithm
   + setMacAlgorithm
   + setPlaybackId
   + setPropertyByteArray
   + sign
   + signRSA
   + verify

Bug: 170964303
Bug: 200055138
Test: atest VtsAidlHalDrmTargetTest
Change-Id: If8b582796fdbc34d3d7720fa45df8291f72cd46a
This commit is contained in:
Robert Shih 2022-01-14 12:43:22 -08:00
parent 395e94b74b
commit 5904a72fc3
9 changed files with 1596 additions and 0 deletions

View file

@ -23,6 +23,14 @@ package {
default_applicable_licenses: ["hardware_interfaces_license"],
}
cc_library_headers {
name: "drm_hal_vendor_module_headers",
vendor_available: true,
export_include_dirs: [
"include",
],
}
cc_library_static {
name: "libdrmvtshelper",
defaults: ["VtsHalTargetTestDefaults"],
@ -36,6 +44,7 @@ cc_library_static {
"android.hardware.drm@1.0-helper",
],
export_include_dirs: ["include"],
export_static_lib_headers: ["android.hardware.drm@1.0-helper"],
}
cc_library_static {

View file

@ -23,6 +23,19 @@ package {
default_applicable_licenses: ["hardware_interfaces_license"],
}
cc_library_static {
name: "libvtsclearkey",
srcs: [
"drm_hal_clearkey_module.cpp",
],
static_libs: [
"libgtest",
],
header_libs: ["drm_hal_vendor_module_headers"],
export_header_lib_headers: ["drm_hal_vendor_module_headers"],
export_include_dirs: ["."],
}
cc_library_static {
name: "android.hardware.drm@1.2-vts",
defaults: ["VtsHalTargetTestDefaults"],

73
drm/aidl/vts/Android.bp Normal file
View file

@ -0,0 +1,73 @@
//
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "hardware_interfaces_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["hardware_interfaces_license"],
}
cc_test {
name: "VtsAidlHalDrmTargetTest",
defaults: [
"VtsHalTargetTestDefaults",
"use_libaidlvintf_gtest_helper_static",
],
srcs: [
"drm_hal_common.cpp",
"drm_hal_test.cpp",
"drm_hal_test_main.cpp",
],
local_include_dirs: [
"include",
],
header_libs: [
"drm_hal_vendor_module_headers",
],
shared_libs: [
"libandroid",
"libbinder_ndk",
"libcrypto",
"libnativehelper",
],
static_libs: [
"android.hardware.drm@1.0-helper",
"android.hardware.drm-V1-ndk",
"android.hardware.common-V2-ndk",
"libdrmvtshelper",
"libvtsclearkey",
],
arch: {
arm: {
data: [":libvtswidevine-arm-prebuilts"],
},
arm64: {
data: [":libvtswidevine-arm64-prebuilts"],
},
x86: {
data: [":libvtswidevine-x86-prebuilts"],
},
x86_64: {
data: [":libvtswidevine-x86_64-prebuilts"],
},
},
test_suites: [
"general-tests",
"vts",
],
}

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration description="Runs VtsAidlHalDrmTargetTest.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-native" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.WifiPreparer" >
<option name="verify-only" value="true" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push-file" key="VtsAidlHalDrmTargetTest" value="/data/local/tmp/VtsAidlHalDrmTargetTest" />
<option name="push-file" key="libvtswidevine64.so" value="/data/local/tmp/64/lib/libvtswidevine.so" />
<option name="push-file" key="libvtswidevine32.so" value="/data/local/tmp/32/lib/libvtswidevine.so" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="VtsAidlHalDrmTargetTest" />
</test>
</configuration>

4
drm/aidl/vts/OWNERS Normal file
View file

@ -0,0 +1,4 @@
edwinwong@google.com
jtinker@google.com
kelzhan@google.com
robertshih@google.com

View file

@ -0,0 +1,602 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "drm_hal_common"
#include <gtest/gtest.h>
#include <log/log.h>
#include <openssl/aes.h>
#include <sys/mman.h>
#include <random>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <android/sharedmem.h>
#include "drm_hal_clearkey_module.h"
#include "drm_hal_common.h"
namespace aidl {
namespace android {
namespace hardware {
namespace drm {
namespace vts {
namespace clearkeydrm = ::android::hardware::drm::V1_2::vts;
using std::vector;
using ::aidl::android::hardware::common::Ashmem;
using ::aidl::android::hardware::drm::BufferType;
using ::aidl::android::hardware::drm::DecryptResult;
using ::aidl::android::hardware::drm::DestinationBuffer;
using ::aidl::android::hardware::drm::EventType;
using ::aidl::android::hardware::drm::ICryptoPlugin;
using ::aidl::android::hardware::drm::IDrmPlugin;
using ::aidl::android::hardware::drm::KeyRequest;
using ::aidl::android::hardware::drm::KeyRequestType;
using ::aidl::android::hardware::drm::KeySetId;
using ::aidl::android::hardware::drm::KeyType;
using ::aidl::android::hardware::drm::KeyValue;
using ::aidl::android::hardware::drm::Mode;
using ::aidl::android::hardware::drm::Pattern;
using ::aidl::android::hardware::drm::ProvisionRequest;
using ::aidl::android::hardware::drm::ProvideProvisionResponseResult;
using ::aidl::android::hardware::drm::SecurityLevel;
using ::aidl::android::hardware::drm::Status;
using ::aidl::android::hardware::drm::SubSample;
using ::aidl::android::hardware::drm::Uuid;
Status DrmErr(const ::ndk::ScopedAStatus& ret) {
return static_cast<Status>(ret.getServiceSpecificError());
}
std::string HalBaseName(const std::string& fullname) {
auto idx = fullname.find('/');
if (idx == std::string::npos) {
return fullname;
}
return fullname.substr(idx + 1);
}
const char* kDrmIface = "android.hardware.drm.IDrmFactory";
const char* kCryptoIface = "android.hardware.drm.ICryptoFactory";
std::string HalFullName(const std::string& iface, const std::string& basename) {
return iface + '/' + basename;
}
testing::AssertionResult IsOk(const ::ndk::ScopedAStatus& ret) {
if (ret.isOk()) {
return testing::AssertionSuccess();
}
return testing::AssertionFailure() << "ex: " << ret.getExceptionCode()
<< "; svc err: " << ret.getServiceSpecificError()
<< "; desc: " << ret.getDescription();
}
const char* kCallbackLostState = "LostState";
const char* kCallbackKeysChange = "KeysChange";
drm_vts::VendorModules* DrmHalTest::gVendorModules = nullptr;
/**
* DrmHalPluginListener
*/
::ndk::ScopedAStatus DrmHalPluginListener::onEvent(
EventType eventType,
const vector<uint8_t>& sessionId,
const vector<uint8_t>& data) {
ListenerArgs args{};
args.eventType = eventType;
args.sessionId = sessionId;
args.data = data;
eventPromise.set_value(args);
return ::ndk::ScopedAStatus::ok();
}
::ndk::ScopedAStatus DrmHalPluginListener::onExpirationUpdate(
const vector<uint8_t>& sessionId,
int64_t expiryTimeInMS) {
ListenerArgs args{};
args.sessionId = sessionId;
args.expiryTimeInMS = expiryTimeInMS;
expirationUpdatePromise.set_value(args);
return ::ndk::ScopedAStatus::ok();
}
::ndk::ScopedAStatus DrmHalPluginListener::onSessionLostState(const vector<uint8_t>& sessionId) {
ListenerArgs args{};
args.sessionId = sessionId;
sessionLostStatePromise.set_value(args);
return ::ndk::ScopedAStatus::ok();
}
::ndk::ScopedAStatus DrmHalPluginListener::onKeysChange(
const std::vector<uint8_t>& sessionId,
const std::vector<::aidl::android::hardware::drm::KeyStatus>& keyStatusList,
bool hasNewUsableKey) {
ListenerArgs args{};
args.sessionId = sessionId;
args.keyStatusList = keyStatusList;
args.hasNewUsableKey = hasNewUsableKey;
keysChangePromise.set_value(args);
return ::ndk::ScopedAStatus::ok();
}
ListenerArgs DrmHalPluginListener::getListenerArgs(std::promise<ListenerArgs>& promise) {
auto future = promise.get_future();
auto timeout = std::chrono::milliseconds(500);
EXPECT_EQ(future.wait_for(timeout), std::future_status::ready);
return future.get();
}
ListenerArgs DrmHalPluginListener::getEventArgs() {
return getListenerArgs(eventPromise);
}
ListenerArgs DrmHalPluginListener::getExpirationUpdateArgs() {
return getListenerArgs(expirationUpdatePromise);
}
ListenerArgs DrmHalPluginListener::getSessionLostStateArgs() {
return getListenerArgs(sessionLostStatePromise);
}
ListenerArgs DrmHalPluginListener::getKeysChangeArgs() {
return getListenerArgs(keysChangePromise);
}
static DrmHalVTSVendorModule_V1* getModuleForInstance(const std::string& instance) {
if (instance.find("clearkey") != std::string::npos ||
instance.find("default") != std::string::npos) {
return new clearkeydrm::DrmHalVTSClearkeyModule();
}
return static_cast<DrmHalVTSVendorModule_V1*>(
DrmHalTest::gVendorModules->getModuleByName(instance));
}
/**
* DrmHalTest
*/
DrmHalTest::DrmHalTest() : vendorModule(getModuleForInstance(GetParamService())) {}
void DrmHalTest::SetUp() {
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("Running test %s.%s from (vendor) module %s", test_info->test_case_name(),
test_info->name(), GetParamService().c_str());
auto svc = GetParamService();
const string cryptoInstance = HalFullName(kCryptoIface, svc);
const string drmInstance = HalFullName(kDrmIface, svc);
if (drmInstance.find("IDrmFactory") != std::string::npos) {
drmFactory = IDrmFactory::fromBinder(
::ndk::SpAIBinder(AServiceManager_waitForService(drmInstance.c_str())));
ASSERT_NE(drmFactory, nullptr);
drmPlugin = createDrmPlugin();
}
if (cryptoInstance.find("ICryptoFactory") != std::string::npos) {
cryptoFactory = ICryptoFactory::fromBinder(
::ndk::SpAIBinder(AServiceManager_waitForService(cryptoInstance.c_str())));
ASSERT_NE(cryptoFactory, nullptr);
cryptoPlugin = createCryptoPlugin();
}
if (!vendorModule) {
ASSERT_NE(drmInstance, "widevine") << "Widevine requires vendor module.";
ASSERT_NE(drmInstance, "clearkey") << "Clearkey requires vendor module.";
GTEST_SKIP() << "No vendor module installed";
}
ASSERT_EQ(HalBaseName(drmInstance), vendorModule->getServiceName());
contentConfigurations = vendorModule->getContentConfigurations();
// If drm scheme not installed skip subsequent tests
bool result = false;
drmFactory->isCryptoSchemeSupported({getUUID()}, "cenc", SecurityLevel::SW_SECURE_CRYPTO,
&result);
if (!result) {
if (GetParamUUID() == std::array<uint8_t, 16>()) {
GTEST_SKIP() << "vendor module drm scheme not supported";
} else {
FAIL() << "param scheme must not supported";
}
}
ASSERT_NE(nullptr, drmPlugin.get())
<< "Can't find " << vendorModule->getServiceName() << " drm aidl plugin";
ASSERT_NE(nullptr, cryptoPlugin.get())
<< "Can't find " << vendorModule->getServiceName() << " crypto aidl plugin";
}
std::shared_ptr<::aidl::android::hardware::drm::IDrmPlugin> DrmHalTest::createDrmPlugin() {
if (drmFactory == nullptr) {
return nullptr;
}
std::string packageName("aidl.android.hardware.drm.test");
std::shared_ptr<::aidl::android::hardware::drm::IDrmPlugin> result;
auto ret = drmFactory->createPlugin({getUUID()}, packageName, &result);
EXPECT_OK(ret) << "createDrmPlugin remote call failed";
return result;
}
std::shared_ptr<::aidl::android::hardware::drm::ICryptoPlugin> DrmHalTest::createCryptoPlugin() {
if (cryptoFactory == nullptr) {
return nullptr;
}
vector<uint8_t> initVec;
std::shared_ptr<::aidl::android::hardware::drm::ICryptoPlugin> result;
auto ret = cryptoFactory->createPlugin({getUUID()}, initVec, &result);
EXPECT_OK(ret) << "createCryptoPlugin remote call failed";
return result;
}
::aidl::android::hardware::drm::Uuid DrmHalTest::getAidlUUID() {
return toAidlUuid(getUUID());
}
std::vector<uint8_t> DrmHalTest::getUUID() {
auto paramUUID = GetParamUUID();
if (paramUUID == std::array<uint8_t, 16>()) {
return getVendorUUID();
}
return std::vector(paramUUID.begin(), paramUUID.end());
}
std::vector<uint8_t> DrmHalTest::getVendorUUID() {
if (vendorModule == nullptr) {
ALOGW("vendor module for %s not found", GetParamService().c_str());
return {};
}
return vendorModule->getUUID();
}
void DrmHalTest::provision() {
std::string certificateType;
std::string certificateAuthority;
vector<uint8_t> provisionRequest;
std::string defaultUrl;
ProvisionRequest result;
auto ret = drmPlugin->getProvisionRequest(certificateType, certificateAuthority, &result);
EXPECT_TXN(ret);
if (ret.isOk()) {
EXPECT_NE(result.request.size(), 0u);
provisionRequest = result.request;
defaultUrl = result.defaultUrl;
} else if (DrmErr(ret) == Status::ERROR_DRM_CANNOT_HANDLE) {
EXPECT_EQ(0u, result.request.size());
}
if (provisionRequest.size() > 0) {
vector<uint8_t> response =
vendorModule->handleProvisioningRequest(provisionRequest, defaultUrl);
ASSERT_NE(0u, response.size());
ProvideProvisionResponseResult result;
auto ret = drmPlugin->provideProvisionResponse(response, &result);
EXPECT_TXN(ret);
}
}
SessionId DrmHalTest::openSession(SecurityLevel level, Status* err) {
SessionId sessionId;
auto ret = drmPlugin->openSession(level, &sessionId);
EXPECT_TXN(ret);
*err = DrmErr(ret);
return sessionId;
}
/**
* Helper method to open a session and verify that a non-empty
* session ID is returned
*/
SessionId DrmHalTest::openSession() {
SessionId sessionId;
auto ret = drmPlugin->openSession(SecurityLevel::DEFAULT, &sessionId);
EXPECT_OK(ret);
EXPECT_NE(0u, sessionId.size());
return sessionId;
}
/**
* Helper method to close a session
*/
void DrmHalTest::closeSession(const SessionId& sessionId) {
auto ret = drmPlugin->closeSession(sessionId);
EXPECT_OK(ret);
}
vector<uint8_t> DrmHalTest::getKeyRequest(
const SessionId& sessionId,
const DrmHalVTSVendorModule_V1::ContentConfiguration& configuration,
const KeyType& type = KeyType::STREAMING) {
KeyRequest result;
auto ret = drmPlugin->getKeyRequest(sessionId, configuration.initData, configuration.mimeType,
type, toAidlKeyedVector(configuration.optionalParameters),
&result);
EXPECT_OK(ret) << "Failed to get key request for configuration "
<< configuration.name << " for key type "
<< static_cast<int>(type);
if (type == KeyType::RELEASE) {
EXPECT_EQ(KeyRequestType::RELEASE, result.requestType);
} else {
EXPECT_EQ(KeyRequestType::INITIAL, result.requestType);
}
EXPECT_NE(result.request.size(), 0u) << "Expected key request size"
" to have length > 0 bytes";
return result.request;
}
DrmHalVTSVendorModule_V1::ContentConfiguration DrmHalTest::getContent(const KeyType& type) const {
for (const auto& config : contentConfigurations) {
if (type != KeyType::OFFLINE || config.policy.allowOffline) {
return config;
}
}
ADD_FAILURE() << "no content configurations found";
return {};
}
vector<uint8_t> DrmHalTest::provideKeyResponse(const SessionId& sessionId,
const vector<uint8_t>& keyResponse) {
KeySetId result;
auto ret = drmPlugin->provideKeyResponse(sessionId, keyResponse, &result);
EXPECT_OK(ret) << "Failure providing key response for configuration ";
return result.keySetId;
}
/**
* Helper method to load keys for subsequent decrypt tests.
* These tests use predetermined key request/response to
* avoid requiring a round trip to a license server.
*/
vector<uint8_t> DrmHalTest::loadKeys(
const SessionId& sessionId,
const DrmHalVTSVendorModule_V1::ContentConfiguration& configuration, const KeyType& type) {
vector<uint8_t> keyRequest = getKeyRequest(sessionId, configuration, type);
/**
* Get key response from vendor module
*/
vector<uint8_t> keyResponse =
vendorModule->handleKeyRequest(keyRequest, configuration.serverUrl);
EXPECT_NE(keyResponse.size(), 0u) << "Expected key response size "
"to have length > 0 bytes";
return provideKeyResponse(sessionId, keyResponse);
}
vector<uint8_t> DrmHalTest::loadKeys(const SessionId& sessionId, const KeyType& type) {
return loadKeys(sessionId, getContent(type), type);
}
std::array<uint8_t, 16> DrmHalTest::toStdArray(const vector<uint8_t>& vec) {
EXPECT_EQ(16u, vec.size());
std::array<uint8_t, 16> arr;
std::copy_n(vec.begin(), vec.size(), arr.begin());
return arr;
}
KeyedVector DrmHalTest::toAidlKeyedVector(const map<string, string>& params) {
std::vector<KeyValue> stdKeyedVector;
for (auto it = params.begin(); it != params.end(); ++it) {
KeyValue keyValue;
keyValue.key = it->first;
keyValue.value = it->second;
stdKeyedVector.push_back(keyValue);
}
return KeyedVector(stdKeyedVector);
}
/**
* getDecryptMemory allocates memory for decryption, then sets it
* as a shared buffer base in the crypto hal. A parcelable Ashmem
* is returned.
*
* @param size the size of the memory segment to allocate
* @param the index of the memory segment which will be used
* to refer to it for decryption.
*/
Ashmem DrmHalTest::getDecryptMemory(size_t size, size_t index) {
int fd = ASharedMemory_create("drmVtsSharedMemory", size);
EXPECT_GE(fd, 0);
EXPECT_EQ(size, ASharedMemory_getSize(fd));
Ashmem ashmem;
ashmem.fd = ::ndk::ScopedFileDescriptor(fd);
ashmem.size = size;
EXPECT_OK(cryptoPlugin->setSharedBufferBase(ashmem, index));
return ashmem;
}
void DrmHalTest::fillRandom(const Ashmem& ashmem) {
std::random_device rd;
std::mt19937 rand(rd());
::ndk::ScopedFileDescriptor fd = ashmem.fd.dup();
size_t size = ashmem.size;
uint8_t* base = static_cast<uint8_t*>(
mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0));
EXPECT_NE(MAP_FAILED, base);
for (size_t i = 0; i < size / sizeof(uint32_t); i++) {
auto p = static_cast<uint32_t*>(static_cast<void*>(base));
p[i] = rand();
}
}
uint32_t DrmHalTest::decrypt(Mode mode, bool isSecure, const std::array<uint8_t, 16>& keyId,
uint8_t* iv, const vector<SubSample>& subSamples,
const Pattern& pattern, const vector<uint8_t>& key,
Status expectedStatus) {
const size_t kSegmentIndex = 0;
uint8_t localIv[AES_BLOCK_SIZE];
memcpy(localIv, iv, AES_BLOCK_SIZE);
vector<uint8_t> ivVec(localIv, localIv + AES_BLOCK_SIZE);
int64_t totalSize = 0;
for (size_t i = 0; i < subSamples.size(); i++) {
totalSize += subSamples[i].numBytesOfClearData;
totalSize += subSamples[i].numBytesOfEncryptedData;
}
// The first totalSize bytes of shared memory is the encrypted
// input, the second totalSize bytes (if exists) is the decrypted output.
size_t factor = expectedStatus == Status::ERROR_DRM_FRAME_TOO_LARGE ? 1 : 2;
Ashmem sharedMemory = getDecryptMemory(totalSize * factor, kSegmentIndex);
const SharedBuffer sourceBuffer = {.bufferId = kSegmentIndex, .offset = 0, .size = totalSize};
fillRandom(sharedMemory);
const DestinationBuffer destBuffer = {
.type = BufferType::SHARED_MEMORY,
.nonsecureMemory = {.bufferId = kSegmentIndex, .offset = totalSize, .size = totalSize},
.secureMemory = {.fds = {}, .ints = {}}};
const uint64_t offset = 0;
uint32_t bytesWritten = 0;
vector<uint8_t> keyIdVec(keyId.begin(), keyId.end());
DecryptResult result;
auto ret = cryptoPlugin->decrypt(isSecure, keyIdVec, ivVec, mode, pattern, subSamples,
sourceBuffer, offset, destBuffer, &result);
EXPECT_TXN(ret);
EXPECT_EQ(expectedStatus, DrmErr(ret)) << "Unexpected decrypt status " << result.detailedError;
bytesWritten = result.bytesWritten;
if (bytesWritten != totalSize) {
return bytesWritten;
}
::ndk::ScopedFileDescriptor fd = sharedMemory.fd.dup();
uint8_t* base = static_cast<uint8_t*>(
mmap(nullptr, totalSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0));
EXPECT_NE(MAP_FAILED, base);
// generate reference vector
vector<uint8_t> reference(totalSize);
memcpy(localIv, iv, AES_BLOCK_SIZE);
switch (mode) {
case Mode::UNENCRYPTED:
memcpy(&reference[0], base, totalSize);
break;
case Mode::AES_CTR:
aes_ctr_decrypt(&reference[0], base, localIv, subSamples, key);
break;
case Mode::AES_CBC:
aes_cbc_decrypt(&reference[0], base, localIv, subSamples, key);
break;
case Mode::AES_CBC_CTS:
ADD_FAILURE() << "AES_CBC_CTS mode not supported";
break;
}
// compare reference to decrypted data which is at base + total size
EXPECT_EQ(0, memcmp(static_cast<void*>(&reference[0]), static_cast<void*>(base + totalSize),
totalSize))
<< "decrypt data mismatch";
return totalSize;
}
/**
* Decrypt a list of clear+encrypted subsamples using the specified key
* in AES-CTR mode
*/
void DrmHalTest::aes_ctr_decrypt(uint8_t* dest, uint8_t* src, uint8_t* iv,
const vector<SubSample>& subSamples, const vector<uint8_t>& key) {
AES_KEY decryptionKey;
AES_set_encrypt_key(&key[0], 128, &decryptionKey);
size_t offset = 0;
unsigned int blockOffset = 0;
uint8_t previousEncryptedCounter[AES_BLOCK_SIZE];
memset(previousEncryptedCounter, 0, AES_BLOCK_SIZE);
for (size_t i = 0; i < subSamples.size(); i++) {
const SubSample& subSample = subSamples[i];
if (subSample.numBytesOfClearData > 0) {
memcpy(dest + offset, src + offset, subSample.numBytesOfClearData);
offset += subSample.numBytesOfClearData;
}
if (subSample.numBytesOfEncryptedData > 0) {
AES_ctr128_encrypt(src + offset, dest + offset, subSample.numBytesOfEncryptedData,
&decryptionKey, iv, previousEncryptedCounter, &blockOffset);
offset += subSample.numBytesOfEncryptedData;
}
}
}
/**
* Decrypt a list of clear+encrypted subsamples using the specified key
* in AES-CBC mode
*/
void DrmHalTest::aes_cbc_decrypt(uint8_t* dest, uint8_t* src, uint8_t* iv,
const vector<SubSample>& subSamples, const vector<uint8_t>& key) {
AES_KEY decryptionKey;
AES_set_encrypt_key(&key[0], 128, &decryptionKey);
size_t offset = 0;
for (size_t i = 0; i < subSamples.size(); i++) {
memcpy(dest + offset, src + offset, subSamples[i].numBytesOfClearData);
offset += subSamples[i].numBytesOfClearData;
AES_cbc_encrypt(src + offset, dest + offset, subSamples[i].numBytesOfEncryptedData,
&decryptionKey, iv, 0 /* decrypt */);
offset += subSamples[i].numBytesOfEncryptedData;
}
}
/**
* Helper method to test decryption with invalid keys is returned
*/
void DrmHalClearkeyTest::decryptWithInvalidKeys(vector<uint8_t>& invalidResponse,
vector<uint8_t>& iv, const Pattern& noPattern,
const vector<SubSample>& subSamples) {
DrmHalVTSVendorModule_V1::ContentConfiguration content = getContent();
if (content.keys.empty()) {
FAIL() << "no keys";
}
const auto& key = content.keys[0];
auto sessionId = openSession();
KeySetId result;
auto ret = drmPlugin->provideKeyResponse(sessionId, invalidResponse, &result);
EXPECT_OK(ret);
EXPECT_EQ(0u, result.keySetId.size());
EXPECT_OK(cryptoPlugin->setMediaDrmSession(sessionId));
uint32_t byteCount =
decrypt(Mode::AES_CTR, key.isSecure, toStdArray(key.keyId), &iv[0], subSamples,
noPattern, key.clearContentKey, Status::ERROR_DRM_NO_LICENSE);
EXPECT_EQ(0u, byteCount);
closeSession(sessionId);
}
} // namespace vts
} // namespace drm
} // namespace hardware
} // namespace android
} // namespace aidl

View file

@ -0,0 +1,551 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "drm_hal_test"
#include <gtest/gtest.h>
#include <log/log.h>
#include <openssl/aes.h>
#include <memory>
#include <vector>
#include "drm_hal_common.h"
using ::aidl::android::hardware::drm::EventType;
using ::aidl::android::hardware::drm::HdcpLevels;
using ::aidl::android::hardware::drm::KeyRequest;
using ::aidl::android::hardware::drm::HdcpLevel;
using ::aidl::android::hardware::drm::IDrmPluginListener;
using ::aidl::android::hardware::drm::KeyRequestType;
using ::aidl::android::hardware::drm::KeySetId;
using ::aidl::android::hardware::drm::KeyStatus;
using ::aidl::android::hardware::drm::KeyStatusType;
using ::aidl::android::hardware::drm::KeyType;
using ::aidl::android::hardware::drm::Mode;
using ::aidl::android::hardware::drm::OfflineLicenseState;
using ::aidl::android::hardware::drm::Pattern;
using ::aidl::android::hardware::drm::SecurityLevel;
using ::aidl::android::hardware::drm::Status;
using ::aidl::android::hardware::drm::SubSample;
using ::aidl::android::hardware::drm::Uuid;
using ::aidl::android::hardware::drm::vts::DrmErr;
using ::aidl::android::hardware::drm::vts::DrmHalClearkeyTest;
using ::aidl::android::hardware::drm::vts::DrmHalPluginListener;
using ::aidl::android::hardware::drm::vts::DrmHalTest;
using ::aidl::android::hardware::drm::vts::ListenerArgs;
using ::aidl::android::hardware::drm::vts::kCallbackKeysChange;
using ::aidl::android::hardware::drm::vts::kCallbackLostState;
using std::string;
using std::vector;
static const char* const kVideoMp4 = "video/mp4";
static const char* const kBadMime = "video/unknown";
static const char* const kDrmErrorTestKey = "drmErrorTest";
static const char* const kDrmErrorInvalidState = "invalidState";
static const char* const kDrmErrorResourceContention = "resourceContention";
static constexpr SecurityLevel kSwSecureCrypto = SecurityLevel::SW_SECURE_CRYPTO;
static constexpr SecurityLevel kHwSecureAll = SecurityLevel::HW_SECURE_ALL;
/**
* Ensure drm factory supports module UUID Scheme
*/
TEST_P(DrmHalTest, VendorUuidSupported) {
bool result = false;
auto ret =
drmFactory->isCryptoSchemeSupported(getAidlUUID(), kVideoMp4, kSwSecureCrypto, &result);
ALOGI("kVideoMp4 = %s res %d", kVideoMp4, static_cast<bool>(result));
EXPECT_OK(ret);
EXPECT_TRUE(result);
}
/**
* Ensure drm factory doesn't support an invalid scheme UUID
*/
TEST_P(DrmHalTest, InvalidPluginNotSupported) {
const vector<uint8_t> kInvalidUUID = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80};
bool result = false;
auto ret = drmFactory->isCryptoSchemeSupported(toAidlUuid(kInvalidUUID), kVideoMp4,
kSwSecureCrypto, &result);
EXPECT_OK(ret);
EXPECT_FALSE(result);
}
/**
* Ensure drm factory doesn't support an empty UUID
*/
TEST_P(DrmHalTest, EmptyPluginUUIDNotSupported) {
vector<uint8_t> emptyUUID(16);
memset(emptyUUID.data(), 0, 16);
bool result = false;
auto ret = drmFactory->isCryptoSchemeSupported(toAidlUuid(emptyUUID), kVideoMp4,
kSwSecureCrypto, &result);
EXPECT_OK(ret);
EXPECT_FALSE(result);
}
/**
* Ensure drm factory doesn't support an invalid mime type
*/
TEST_P(DrmHalTest, BadMimeNotSupported) {
bool result = false;
auto ret =
drmFactory->isCryptoSchemeSupported(getAidlUUID(), kBadMime, kSwSecureCrypto, &result);
EXPECT_OK(ret);
EXPECT_FALSE(result);
}
/**
* DrmPlugin tests
*/
/**
* Test that a DRM plugin can handle provisioning. While
* it is not required that a DRM scheme require provisioning,
* it should at least return appropriate status values. If
* a provisioning request is returned, it is passed to the
* vendor module which should provide a provisioning response
* that is delivered back to the HAL.
*/
TEST_P(DrmHalTest, DoProvisioning) {
for (auto level : {kHwSecureAll, kSwSecureCrypto}) {
Status err = Status::OK;
auto sid = openSession(level, &err);
if (err == Status::OK) {
closeSession(sid);
} else if (err == Status::ERROR_DRM_CANNOT_HANDLE) {
continue;
} else {
EXPECT_EQ(Status::ERROR_DRM_NOT_PROVISIONED, err);
provision();
}
}
}
/**
* A get key request should fail if no sessionId is provided
*/
TEST_P(DrmHalTest, GetKeyRequestNoSession) {
SessionId invalidSessionId;
vector<uint8_t> initData;
KeyedVector optionalParameters;
KeyRequest result;
auto ret = drmPlugin->getKeyRequest(invalidSessionId, initData, kVideoMp4, KeyType::STREAMING,
optionalParameters, &result);
EXPECT_TXN(ret);
EXPECT_EQ(Status::BAD_VALUE, DrmErr(ret));
}
/**
* Test that the plugin returns the documented error for the
* case of attempting to generate a key request using an
* invalid mime type
*/
TEST_P(DrmHalTest, GetKeyRequestBadMime) {
auto sessionId = openSession();
vector<uint8_t> initData;
KeyedVector optionalParameters;
KeyRequest result;
auto ret = drmPlugin->getKeyRequest(sessionId, initData, kBadMime, KeyType::STREAMING,
optionalParameters, &result);
EXPECT_EQ(EX_SERVICE_SPECIFIC, ret.getExceptionCode());
closeSession(sessionId);
}
/**
* Test drm plugin offline key support
*/
TEST_P(DrmHalTest, OfflineLicenseTest) {
auto sessionId = openSession();
vector<uint8_t> keySetId = loadKeys(sessionId, KeyType::OFFLINE);
closeSession(sessionId);
vector<KeySetId> result;
auto ret = drmPlugin->getOfflineLicenseKeySetIds(&result);
EXPECT_OK(ret);
bool found = false;
for (KeySetId keySetId2 : result) {
if (keySetId == keySetId2.keySetId) {
found = true;
break;
}
}
EXPECT_TRUE(found) << "keySetId not found";
ret = drmPlugin->removeOfflineLicense({keySetId});
EXPECT_OK(ret);
ret = drmPlugin->getOfflineLicenseKeySetIds(&result);
EXPECT_OK(ret);
for (KeySetId keySetId2 : result) {
EXPECT_NE(keySetId, keySetId2.keySetId);
}
ret = drmPlugin->removeOfflineLicense({keySetId});
EXPECT_TXN(ret);
EXPECT_EQ(Status::BAD_VALUE, DrmErr(ret));
}
/**
* Test drm plugin offline key state
*/
TEST_P(DrmHalTest, OfflineLicenseStateTest) {
auto sessionId = openSession();
DrmHalVTSVendorModule_V1::ContentConfiguration content = getContent(KeyType::OFFLINE);
vector<uint8_t> keySetId = loadKeys(sessionId, content, KeyType::OFFLINE);
closeSession(sessionId);
OfflineLicenseState result{};
auto ret = drmPlugin->getOfflineLicenseState({keySetId}, &result);
EXPECT_OK(ret);
EXPECT_EQ(OfflineLicenseState::USABLE, result);
vector<uint8_t> keyRequest = getKeyRequest(keySetId, content, KeyType::RELEASE);
ret = drmPlugin->getOfflineLicenseState({keySetId}, &result);
EXPECT_OK(ret);
EXPECT_EQ(OfflineLicenseState::INACTIVE, result);
/**
* Get key response from vendor module
*/
vector<uint8_t> keyResponse = vendorModule->handleKeyRequest(keyRequest, content.serverUrl);
EXPECT_GT(keyResponse.size(), 0u);
result = OfflineLicenseState::UNKNOWN;
provideKeyResponse(keySetId, keyResponse);
ret = drmPlugin->getOfflineLicenseState({keySetId}, &result);
EXPECT_TXN(ret);
EXPECT_EQ(Status::BAD_VALUE, DrmErr(ret));
EXPECT_EQ(OfflineLicenseState::UNKNOWN, result);
}
/**
* Negative offline license test. Remove empty keySetId
*/
TEST_P(DrmHalTest, RemoveEmptyKeySetId) {
KeySetId emptyKeySetId;
auto ret = drmPlugin->removeOfflineLicense(emptyKeySetId);
EXPECT_TXN(ret);
EXPECT_EQ(Status::BAD_VALUE, DrmErr(ret));
}
/**
* Negative offline license test. Get empty keySetId state
*/
TEST_P(DrmHalTest, GetEmptyKeySetIdState) {
KeySetId emptyKeySetId;
OfflineLicenseState result;
auto ret = drmPlugin->getOfflineLicenseState(emptyKeySetId, &result);
EXPECT_TXN(ret);
EXPECT_EQ(Status::BAD_VALUE, DrmErr(ret));
EXPECT_EQ(OfflineLicenseState::UNKNOWN, result);
}
/**
* Test that the plugin returns valid connected and max HDCP levels
*/
TEST_P(DrmHalTest, GetHdcpLevels) {
HdcpLevels result;
auto ret = drmPlugin->getHdcpLevels(&result);
EXPECT_OK(ret);
EXPECT_GE(result.connectedLevel, HdcpLevel::HDCP_NONE);
EXPECT_LE(result.maxLevel, HdcpLevel::HDCP_V2_3);
}
/**
* CryptoPlugin Decrypt tests
*/
/**
* Positive decrypt test. "Decrypt" a single clear segment
*/
TEST_P(DrmHalTest, ClearSegmentTest) {
for (const auto& config : contentConfigurations) {
for (const auto& key : config.keys) {
const size_t kSegmentSize = 1024;
vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
const Pattern noPattern = {0, 0};
const vector<SubSample> subSamples = {
{.numBytesOfClearData = kSegmentSize, .numBytesOfEncryptedData = 0}};
auto sessionId = openSession();
loadKeys(sessionId, config);
auto ret = cryptoPlugin->setMediaDrmSession(sessionId);
EXPECT_OK(ret);
uint32_t byteCount =
decrypt(Mode::UNENCRYPTED, key.isSecure, toStdArray(key.keyId), &iv[0],
subSamples, noPattern, key.clearContentKey, Status::OK);
EXPECT_EQ(kSegmentSize, byteCount);
closeSession(sessionId);
}
}
}
/**
* Positive decrypt test. Decrypt a single segment using aes_ctr.
* Verify data matches.
*/
TEST_P(DrmHalTest, EncryptedAesCtrSegmentTest) {
for (const auto& config : contentConfigurations) {
for (const auto& key : config.keys) {
const size_t kSegmentSize = 1024;
vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
const Pattern noPattern = {0, 0};
const vector<SubSample> subSamples = {
{.numBytesOfClearData = kSegmentSize, .numBytesOfEncryptedData = 0}};
auto sessionId = openSession();
loadKeys(sessionId, config);
auto ret = cryptoPlugin->setMediaDrmSession(sessionId);
EXPECT_OK(ret);
uint32_t byteCount = decrypt(Mode::AES_CTR, key.isSecure, toStdArray(key.keyId), &iv[0],
subSamples, noPattern, key.clearContentKey, Status::OK);
EXPECT_EQ(kSegmentSize, byteCount);
closeSession(sessionId);
}
}
}
/**
* Negative decrypt test. Decrypted frame too large to fit in output buffer
*/
TEST_P(DrmHalTest, ErrorFrameTooLarge) {
for (const auto& config : contentConfigurations) {
for (const auto& key : config.keys) {
const size_t kSegmentSize = 1024;
vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
const Pattern noPattern = {0, 0};
const vector<SubSample> subSamples = {
{.numBytesOfClearData = kSegmentSize, .numBytesOfEncryptedData = 0}};
auto sessionId = openSession();
loadKeys(sessionId, config);
auto ret = cryptoPlugin->setMediaDrmSession(sessionId);
EXPECT_OK(ret);
decrypt(Mode::UNENCRYPTED, key.isSecure, toStdArray(key.keyId), &iv[0], subSamples,
noPattern, key.clearContentKey, Status::ERROR_DRM_FRAME_TOO_LARGE);
closeSession(sessionId);
}
}
}
/**
* Negative decrypt test. Decrypt without loading keys.
*/
TEST_P(DrmHalTest, EncryptedAesCtrSegmentTestNoKeys) {
for (const auto& config : contentConfigurations) {
for (const auto& key : config.keys) {
vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
const Pattern noPattern = {0, 0};
const vector<SubSample> subSamples = {
{.numBytesOfClearData = 256, .numBytesOfEncryptedData = 256}};
auto sessionId = openSession();
auto ret = cryptoPlugin->setMediaDrmSession(sessionId);
EXPECT_OK(ret);
uint32_t byteCount =
decrypt(Mode::AES_CTR, key.isSecure, toStdArray(key.keyId), &iv[0], subSamples,
noPattern, key.clearContentKey, Status::ERROR_DRM_NO_LICENSE);
EXPECT_EQ(0u, byteCount);
closeSession(sessionId);
}
}
}
/**
* Ensure clearkey drm factory doesn't support security level higher than supported
*/
TEST_P(DrmHalClearkeyTest, BadLevelNotSupported) {
bool result = false;
auto ret = drmFactory->isCryptoSchemeSupported(getAidlUUID(), kVideoMp4, kHwSecureAll, &result);
EXPECT_OK(ret);
EXPECT_FALSE(result);
}
/**
* Test resource contention during attempt to generate key request
*/
TEST_P(DrmHalClearkeyTest, GetKeyRequestResourceContention) {
auto ret = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorResourceContention);
EXPECT_OK(ret);
auto sessionId = openSession();
vector<uint8_t> initData;
KeyedVector optionalParameters;
KeyRequest result;
ret = drmPlugin->getKeyRequest(sessionId, initData, kVideoMp4, KeyType::STREAMING,
optionalParameters, &result);
EXPECT_TXN(ret);
EXPECT_EQ(Status::ERROR_DRM_RESOURCE_CONTENTION, DrmErr(ret));
ret = drmPlugin->closeSession(sessionId);
EXPECT_TXN(ret);
EXPECT_NE(Status::OK, DrmErr(ret));
}
/**
* Test clearkey plugin offline key with mock error
*/
TEST_P(DrmHalClearkeyTest, OfflineLicenseInvalidState) {
auto sessionId = openSession();
vector<uint8_t> keySetId = loadKeys(sessionId, KeyType::OFFLINE);
auto ret = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorInvalidState);
EXPECT_OK(ret);
// everything should start failing
const Status kInvalidState = Status::ERROR_DRM_INVALID_STATE;
vector<KeySetId> result;
ret = drmPlugin->getOfflineLicenseKeySetIds(&result);
EXPECT_TXN(ret);
EXPECT_EQ(kInvalidState, DrmErr(ret));
EXPECT_EQ(0u, result.size());
OfflineLicenseState state = OfflineLicenseState::UNKNOWN;
ret = drmPlugin->getOfflineLicenseState({keySetId}, &state);
EXPECT_TXN(ret);
EXPECT_EQ(kInvalidState, DrmErr(ret));
EXPECT_EQ(OfflineLicenseState::UNKNOWN, state);
ret = drmPlugin->removeOfflineLicense({keySetId});
EXPECT_TXN(ret);
EXPECT_EQ(kInvalidState, DrmErr(ret));
closeSession(sessionId);
}
/**
* Test listener is triggered on key response
*/
TEST_P(DrmHalClearkeyTest, ListenerCallbacks) {
auto listener = ndk::SharedRefBase::make<DrmHalPluginListener>();
auto res = drmPlugin->setListener(listener);
EXPECT_OK(res);
auto sessionId = openSession();
loadKeys(sessionId, KeyType::STREAMING);
closeSession(sessionId);
auto args = listener->getEventArgs();
EXPECT_EQ(EventType::VENDOR_DEFINED, args.eventType);
EXPECT_EQ(sessionId, args.data);
EXPECT_EQ(sessionId, args.sessionId);
args = listener->getExpirationUpdateArgs();
EXPECT_EQ(sessionId, args.sessionId);
EXPECT_EQ(100, args.expiryTimeInMS);
args = listener->getKeysChangeArgs();
const vector<KeyStatus> keyStatusList = {
{{0xa, 0xb, 0xc}, KeyStatusType::USABLE},
{{0xd, 0xe, 0xf}, KeyStatusType::EXPIRED},
{{0x0, 0x1, 0x2}, KeyStatusType::USABLEINFUTURE},
};
EXPECT_EQ(sessionId, args.sessionId);
EXPECT_EQ(keyStatusList, args.keyStatusList);
EXPECT_TRUE(args.hasNewUsableKey);
}
/**
* Test SessionLostState is triggered on error
*/
TEST_P(DrmHalClearkeyTest, SessionLostState) {
auto listener = ndk::SharedRefBase::make<DrmHalPluginListener>();
auto res = drmPlugin->setListener(listener);
EXPECT_OK(res);
res = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorInvalidState);
EXPECT_OK(res);
auto sessionId = openSession();
auto ret = drmPlugin->closeSession(sessionId);
auto args = listener->getSessionLostStateArgs();
EXPECT_EQ(sessionId, args.sessionId);
}
/**
* Negative decrypt test. Decrypt with invalid key.
*/
TEST_P(DrmHalClearkeyTest, DecryptWithEmptyKey) {
vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
const Pattern noPattern = {0, 0};
const uint32_t kClearBytes = 512;
const uint32_t kEncryptedBytes = 512;
const vector<SubSample> subSamples = {
{.numBytesOfClearData = kClearBytes, .numBytesOfEncryptedData = kEncryptedBytes}};
// base 64 encoded JSON response string, must not contain padding character '='
const string emptyKeyResponse =
"{\"keys\":["
"{"
"\"kty\":\"oct\""
"\"alg\":\"A128KW2\""
"\"k\":\"SGVsbG8gRnJpZW5kIQ\""
"\"kid\":\"Y2xlYXJrZXlrZXlpZDAyAy\""
"}"
"{"
"\"kty\":\"oct\","
"\"alg\":\"A128KW2\""
"\"kid\":\"Y2xlYXJrZXlrZXlpZDAzAy\"," // empty key follows
"\"k\":\"R\""
"}]"
"}";
const size_t kEmptyKeyResponseSize = emptyKeyResponse.size();
vector<uint8_t> invalidResponse;
invalidResponse.resize(kEmptyKeyResponseSize);
memcpy(invalidResponse.data(), emptyKeyResponse.c_str(), kEmptyKeyResponseSize);
decryptWithInvalidKeys(invalidResponse, iv, noPattern, subSamples);
}
/**
* Negative decrypt test. Decrypt with a key exceeds AES_BLOCK_SIZE.
*/
TEST_P(DrmHalClearkeyTest, DecryptWithKeyTooLong) {
vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
const Pattern noPattern = {0, 0};
const uint32_t kClearBytes = 512;
const uint32_t kEncryptedBytes = 512;
const vector<SubSample> subSamples = {
{.numBytesOfClearData = kClearBytes, .numBytesOfEncryptedData = kEncryptedBytes}};
// base 64 encoded JSON response string, must not contain padding character '='
const string keyTooLongResponse =
"{\"keys\":["
"{"
"\"kty\":\"oct\","
"\"alg\":\"A128KW2\""
"\"kid\":\"Y2xlYXJrZXlrZXlpZDAzAy\"," // key too long
"\"k\":\"V2lubmllIHRoZSBwb29oIVdpbm5pZSB0aGUgcG9vaCE=\""
"}]"
"}";
const size_t kKeyTooLongResponseSize = keyTooLongResponse.size();
vector<uint8_t> invalidResponse;
invalidResponse.resize(kKeyTooLongResponseSize);
memcpy(invalidResponse.data(), keyTooLongResponse.c_str(), kKeyTooLongResponseSize);
decryptWithInvalidKeys(invalidResponse, iv, noPattern, subSamples);
}

View file

@ -0,0 +1,92 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Instantiate the set of test cases for each vendor module
*/
#define LOG_TAG "drm_hal_test_main"
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <android/binder_process.h>
#include <log/log.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
#include "drm_hal_common.h"
using ::aidl::android::hardware::drm::vts::DrmHalClearkeyTest;
using ::aidl::android::hardware::drm::vts::DrmHalTest;
using ::aidl::android::hardware::drm::vts::HalBaseName;
using drm_vts::DrmHalTestParam;
using drm_vts::PrintParamInstanceToString;
static const std::vector<DrmHalTestParam> getAllInstances() {
using ::aidl::android::hardware::drm::ICryptoFactory;
using ::aidl::android::hardware::drm::IDrmFactory;
std::vector<std::string> drmInstances =
android::getAidlHalInstanceNames(IDrmFactory::descriptor);
std::vector<std::string> cryptoInstances =
android::getAidlHalInstanceNames(ICryptoFactory::descriptor);
std::set<std::string> allInstances;
for (auto svc : drmInstances) {
allInstances.insert(HalBaseName(svc));
}
for (auto svc : cryptoInstances) {
allInstances.insert(HalBaseName(svc));
}
std::vector<DrmHalTestParam> allInstanceUuidCombos;
auto noUUID = [](std::string s) { return DrmHalTestParam(s); };
std::transform(allInstances.begin(), allInstances.end(),
std::back_inserter(allInstanceUuidCombos), noUUID);
return allInstanceUuidCombos;
};
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DrmHalTest);
INSTANTIATE_TEST_SUITE_P(PerInstance, DrmHalTest, testing::ValuesIn(getAllInstances()),
PrintParamInstanceToString);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DrmHalClearkeyTest);
INSTANTIATE_TEST_SUITE_P(PerInstance, DrmHalClearkeyTest, testing::ValuesIn(getAllInstances()),
PrintParamInstanceToString);
int main(int argc, char** argv) {
#if defined(__LP64__)
const char* kModulePath = "/data/local/tmp/64/lib";
#else
const char* kModulePath = "/data/local/tmp/32/lib";
#endif
DrmHalTest::gVendorModules = new drm_vts::VendorModules(kModulePath);
if (DrmHalTest::gVendorModules->getPathList().size() == 0) {
std::cerr << "WARNING: No vendor modules found in " << kModulePath
<< ", all vendor tests will be skipped" << std::endl;
}
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
::testing::InitGoogleTest(&argc, argv);
int status = RUN_ALL_TESTS();
ALOGI("Test result = %d", status);
return status;
}

View file

@ -0,0 +1,214 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <aidl/android/hardware/common/Ashmem.h>
#include <aidl/android/hardware/drm/BnDrmPluginListener.h>
#include <aidl/android/hardware/drm/ICryptoFactory.h>
#include <aidl/android/hardware/drm/ICryptoPlugin.h>
#include <aidl/android/hardware/drm/IDrmFactory.h>
#include <aidl/android/hardware/drm/IDrmPlugin.h>
#include <aidl/android/hardware/drm/IDrmPluginListener.h>
#include <aidl/android/hardware/drm/Status.h>
#include <array>
#include <chrono>
#include <future>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <android/binder_auto_utils.h>
#include "VtsHalHidlTargetCallbackBase.h"
#include "drm_hal_vendor_module_api.h"
#include "drm_vts_helper.h"
#include "vendor_modules.h"
using drm_vts::DrmHalTestParam;
namespace {
typedef vector<::aidl::android::hardware::drm::KeyValue> KeyedVector;
typedef std::vector<uint8_t> SessionId;
} // namespace
#define EXPECT_OK(ret) EXPECT_TRUE(::aidl::android::hardware::drm::vts::IsOk(ret))
#define EXPECT_TXN(ret) EXPECT_TRUE(ret.isOk() || ret.getExceptionCode() == EX_SERVICE_SPECIFIC)
namespace aidl {
namespace android {
namespace hardware {
namespace drm {
namespace vts {
::aidl::android::hardware::drm::Status DrmErr(const ::ndk::ScopedAStatus& ret);
std::string HalBaseName(const std::string& fullname);
std::string HalFullName(const std::string& iface, const std::string& basename);
testing::AssertionResult IsOk(const ::ndk::ScopedAStatus& ret);
extern const char* kDrmIface;
extern const char* kCryptoIface;
class DrmHalTest : public ::testing::TestWithParam<DrmHalTestParam> {
public:
static drm_vts::VendorModules* gVendorModules;
DrmHalTest();
virtual void SetUp() override;
virtual void TearDown() override {}
protected:
::aidl::android::hardware::drm::Uuid getAidlUUID();
std::vector<uint8_t> getUUID();
std::vector<uint8_t> getVendorUUID();
std::array<uint8_t, 16> GetParamUUID() { return GetParam().scheme_; }
std::string GetParamService() { return GetParam().instance_; }
::aidl::android::hardware::drm::Uuid toAidlUuid(const std::vector<uint8_t>& in_uuid) {
::aidl::android::hardware::drm::Uuid uuid;
uuid.uuid = in_uuid;
return uuid;
}
void provision();
SessionId openSession(::aidl::android::hardware::drm::SecurityLevel level,
::aidl::android::hardware::drm::Status* err);
SessionId openSession();
void closeSession(const SessionId& sessionId);
std::vector<uint8_t> loadKeys(
const SessionId& sessionId,
const ::aidl::android::hardware::drm::KeyType& type = KeyType::STREAMING);
std::vector<uint8_t> loadKeys(
const SessionId& sessionId, const DrmHalVTSVendorModule_V1::ContentConfiguration&,
const ::aidl::android::hardware::drm::KeyType& type = KeyType::STREAMING);
std::vector<uint8_t> getKeyRequest(const SessionId& sessionId,
const DrmHalVTSVendorModule_V1::ContentConfiguration&,
const ::aidl::android::hardware::drm::KeyType& type);
std::vector<uint8_t> provideKeyResponse(const SessionId& sessionId,
const std::vector<uint8_t>& keyResponse);
DrmHalVTSVendorModule_V1::ContentConfiguration getContent(
const ::aidl::android::hardware::drm::KeyType& type = KeyType::STREAMING) const;
KeyedVector toAidlKeyedVector(const std::map<std::string, std::string>& params);
std::array<uint8_t, 16> toStdArray(const std::vector<uint8_t>& vec);
void fillRandom(const ::aidl::android::hardware::common::Ashmem& ashmem);
::aidl::android::hardware::common::Ashmem getDecryptMemory(size_t size, size_t index);
uint32_t decrypt(::aidl::android::hardware::drm::Mode mode, bool isSecure,
const std::array<uint8_t, 16>& keyId, uint8_t* iv,
const std::vector<::aidl::android::hardware::drm::SubSample>& subSamples,
const ::aidl::android::hardware::drm::Pattern& pattern,
const std::vector<uint8_t>& key,
::aidl::android::hardware::drm::Status expectedStatus);
void aes_ctr_decrypt(uint8_t* dest, uint8_t* src, uint8_t* iv,
const std::vector<::aidl::android::hardware::drm::SubSample>& subSamples,
const std::vector<uint8_t>& key);
void aes_cbc_decrypt(uint8_t* dest, uint8_t* src, uint8_t* iv,
const std::vector<::aidl::android::hardware::drm::SubSample>& subSamples,
const std::vector<uint8_t>& key);
std::shared_ptr<::aidl::android::hardware::drm::IDrmFactory> drmFactory;
std::shared_ptr<::aidl::android::hardware::drm::ICryptoFactory> cryptoFactory;
std::shared_ptr<::aidl::android::hardware::drm::IDrmPlugin> drmPlugin;
std::shared_ptr<::aidl::android::hardware::drm::ICryptoPlugin> cryptoPlugin;
unique_ptr<DrmHalVTSVendorModule_V1> vendorModule;
std::vector<DrmHalVTSVendorModule_V1::ContentConfiguration> contentConfigurations;
private:
std::shared_ptr<::aidl::android::hardware::drm::IDrmPlugin> createDrmPlugin();
std::shared_ptr<::aidl::android::hardware::drm::ICryptoPlugin> createCryptoPlugin();
};
class DrmHalClearkeyTest : public DrmHalTest {
public:
virtual void SetUp() override {
DrmHalTest::SetUp();
const std::vector<uint8_t> kClearKeyUUID = {0xE2, 0x71, 0x9D, 0x58, 0xA9, 0x85, 0xB3, 0xC9,
0x78, 0x1A, 0xB0, 0x30, 0xAF, 0x78, 0xD3, 0x0E};
static const std::string kMimeType = "video/mp4";
static constexpr ::aidl::android::hardware::drm::SecurityLevel kSecurityLevel =
::aidl::android::hardware::drm::SecurityLevel::SW_SECURE_CRYPTO;
bool drmClearkey = false;
auto ret = drmFactory->isCryptoSchemeSupported(toAidlUuid(kClearKeyUUID), kMimeType,
kSecurityLevel, &drmClearkey);
if (!drmClearkey) {
GTEST_SKIP() << "ClearKey not supported by " << GetParamService();
}
}
virtual void TearDown() override {}
void decryptWithInvalidKeys(
std::vector<uint8_t>& invalidResponse, std::vector<uint8_t>& iv,
const ::aidl::android::hardware::drm::Pattern& noPattern,
const std::vector<::aidl::android::hardware::drm::SubSample>& subSamples);
};
/**
* Event Handling tests
*/
extern const char* kCallbackLostState;
extern const char* kCallbackKeysChange;
struct ListenerArgs {
EventType eventType;
SessionId sessionId;
int64_t expiryTimeInMS;
std::vector<uint8_t> data;
std::vector<KeyStatus> keyStatusList;
bool hasNewUsableKey;
};
class DrmHalPluginListener : public BnDrmPluginListener {
public:
DrmHalPluginListener() {}
virtual ~DrmHalPluginListener() {}
virtual ::ndk::ScopedAStatus onEvent(
::aidl::android::hardware::drm::EventType in_eventType,
const std::vector<uint8_t>& in_sessionId,
const std::vector<uint8_t>& in_data) override;
virtual ::ndk::ScopedAStatus onExpirationUpdate(
const std::vector<uint8_t>& in_sessionId,
int64_t in_expiryTimeInMS) override;
virtual ::ndk::ScopedAStatus onSessionLostState(
const std::vector<uint8_t>& in_sessionId) override;
virtual ::ndk::ScopedAStatus onKeysChange(
const std::vector<uint8_t>& in_sessionId,
const std::vector<::aidl::android::hardware::drm::KeyStatus>& in_keyStatusList,
bool in_hasNewUsableKey) override;
ListenerArgs getEventArgs();
ListenerArgs getExpirationUpdateArgs();
ListenerArgs getSessionLostStateArgs();
ListenerArgs getKeysChangeArgs();
private:
ListenerArgs getListenerArgs(std::promise<ListenerArgs>& promise);
std::promise<ListenerArgs> eventPromise, expirationUpdatePromise,
sessionLostStatePromise, keysChangePromise;
};
} // namespace vts
} // namespace drm
} // namespace hardware
} // namespace android
} // namespace aidl