Files
hardware_interfaces/drm/aidl/vts/drm_hal_common.cpp
Robert Shih 5904a72fc3 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
2022-01-25 00:58:50 -08:00

603 lines
21 KiB
C++

/*
* 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