diff options
Diffstat (limited to 'drm/1.2/vts/functional/drm_hal_common.cpp')
| -rw-r--r-- | drm/1.2/vts/functional/drm_hal_common.cpp | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/drm/1.2/vts/functional/drm_hal_common.cpp b/drm/1.2/vts/functional/drm_hal_common.cpp new file mode 100644 index 0000000000..bfffbe8d22 --- /dev/null +++ b/drm/1.2/vts/functional/drm_hal_common.cpp @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2019 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@1.2" + +#include <android/hidl/allocator/1.0/IAllocator.h> +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <hidlmemory/mapping.h> +#include <log/log.h> +#include <openssl/aes.h> +#include <random> + +#include "drm_hal_clearkey_module.h" +#include "drm_hal_common.h" + +using ::android::hardware::drm::V1_0::BufferType; +using ::android::hardware::drm::V1_0::DestinationBuffer; +using ICryptoPluginV1_0 = ::android::hardware::drm::V1_0::ICryptoPlugin; +using IDrmPluginV1_0 = ::android::hardware::drm::V1_0::IDrmPlugin; +using ::android::hardware::drm::V1_0::KeyValue; +using ::android::hardware::drm::V1_0::SharedBuffer; +using StatusV1_0 = ::android::hardware::drm::V1_0::Status; + +using ::android::hardware::drm::V1_1::KeyRequestType; + +using ::android::hardware::drm::V1_2::KeySetId; +using ::android::hardware::drm::V1_2::OfflineLicenseState; +using StatusV1_2 = ::android::hardware::drm::V1_2::Status; + +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_memory; + +using ::android::hidl::allocator::V1_0::IAllocator; + +using std::random_device; +using std::mt19937; + +namespace android { +namespace hardware { +namespace drm { +namespace V1_2 { +namespace vts { + +const char *kCallbackLostState = "LostState"; +const char *kCallbackKeysChange = "KeysChange"; + +drm_vts::VendorModules *DrmHalTest::gVendorModules = nullptr; + +/** + * DrmHalPluginListener + */ + +Return<void> DrmHalPluginListener::sendSessionLostState(const hidl_vec<uint8_t>& sessionId) { + ListenerEventArgs args; + args.sessionId = sessionId; + NotifyFromCallback(kCallbackLostState, args); + return Void(); +} + +Return<void> DrmHalPluginListener::sendKeysChange_1_2(const hidl_vec<uint8_t>& sessionId, + const hidl_vec<KeyStatus>& keyStatusList, bool hasNewUsableKey) { + ListenerEventArgs args; + args.sessionId = sessionId; + args.keyStatusList = keyStatusList; + args.hasNewUsableKey = hasNewUsableKey; + NotifyFromCallback(kCallbackKeysChange, args); + return Void(); +} + +/** + * DrmHalTest + */ + +DrmHalTest::DrmHalTest() + : vendorModule(GetParam() == "clearkey" + ? new DrmHalVTSClearkeyModule() + : static_cast<DrmHalVTSVendorModule_V1*>(gVendorModules->getModule(GetParam()))), + contentConfigurations(vendorModule->getContentConfigurations()) { +} + +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(), + GetParam().c_str()); + + string name = vendorModule->getServiceName(); + drmFactory = VtsHalHidlTargetTestBase::getService<IDrmFactory>(name); + if (drmFactory == nullptr) { + drmFactory = VtsHalHidlTargetTestBase::getService<IDrmFactory>(); + } + if (drmFactory != nullptr) { + drmPlugin = createDrmPlugin(); + } + + cryptoFactory = VtsHalHidlTargetTestBase::getService<ICryptoFactory>(name); + if (cryptoFactory == nullptr) { + cryptoFactory = VtsHalHidlTargetTestBase::getService<ICryptoFactory>(); + } + if (cryptoFactory != nullptr) { + cryptoPlugin = createCryptoPlugin(); + } + + // If drm scheme not installed skip subsequent tests + if (!drmFactory->isCryptoSchemeSupported(getVendorUUID())) { + vendorModule->setInstalled(false); + return; + } + + ASSERT_NE(nullptr, drmPlugin.get()) << "Can't find " << vendorModule->getServiceName() << " drm@1.2 plugin"; + ASSERT_NE(nullptr, cryptoPlugin.get()) << "Can't find " << vendorModule->getServiceName() << " crypto@1.2 plugin"; + +} + +sp<IDrmPlugin> DrmHalTest::createDrmPlugin() { + if (drmFactory == nullptr) { + return nullptr; + } + sp<IDrmPlugin> plugin = nullptr; + hidl_string packageName("android.hardware.drm.test"); + auto res = drmFactory->createPlugin( + getVendorUUID(), packageName, + [&](StatusV1_0 status, const sp<IDrmPluginV1_0>& pluginV1_0) { + EXPECT_EQ(StatusV1_0::OK, status); + plugin = IDrmPlugin::castFrom(pluginV1_0); + }); + + if (!res.isOk()) { + ALOGE("createDrmPlugin remote call failed"); + } + return plugin; +} + +sp<ICryptoPlugin> DrmHalTest::createCryptoPlugin() { + if (cryptoFactory == nullptr) { + return nullptr; + } + sp<ICryptoPlugin> plugin = nullptr; + hidl_vec<uint8_t> initVec; + auto res = cryptoFactory->createPlugin( + getVendorUUID(), initVec, + [&](StatusV1_0 status, const sp<ICryptoPluginV1_0>& pluginV1_0) { + EXPECT_EQ(StatusV1_0::OK, status); + plugin = ICryptoPlugin::castFrom(pluginV1_0); + }); + if (!res.isOk()) { + ALOGE("createCryptoPlugin remote call failed"); + } + return plugin; +} + +hidl_array<uint8_t, 16> DrmHalTest::getVendorUUID() { + vector<uint8_t> uuid = vendorModule->getUUID(); + return hidl_array<uint8_t, 16>(&uuid[0]); +} + +/** + * Helper method to open a session and verify that a non-empty + * session ID is returned + */ +SessionId DrmHalTest::openSession() { + SessionId sessionId; + + auto res = drmPlugin->openSession([&](StatusV1_0 status, const hidl_vec<unsigned char> &id) { + EXPECT_EQ(StatusV1_0::OK, status); + EXPECT_NE(id.size(), 0u); + sessionId = id; + }); + EXPECT_OK(res); + return sessionId; +} + +/** + * Helper method to close a session + */ +void DrmHalTest::closeSession(const SessionId& sessionId) { + StatusV1_0 status = drmPlugin->closeSession(sessionId); + EXPECT_EQ(StatusV1_0::OK, status); +} + +hidl_vec<uint8_t> DrmHalTest::getKeyRequest( + const SessionId& sessionId, + const DrmHalVTSVendorModule_V1::ContentConfiguration& configuration, + const KeyType& type = KeyType::STREAMING) { + hidl_vec<uint8_t> keyRequest; + auto res = drmPlugin->getKeyRequest_1_2( + sessionId, configuration.initData, configuration.mimeType, type, + toHidlKeyedVector(configuration.optionalParameters), + [&](Status status, const hidl_vec<uint8_t>& request, + KeyRequestType requestType, const hidl_string&) { + EXPECT_EQ(Status::OK, status) << "Failed to get " + "key request for configuration " + << configuration.name; + if (type == KeyType::RELEASE) { + EXPECT_EQ(KeyRequestType::RELEASE, requestType); + } else { + EXPECT_EQ(KeyRequestType::INITIAL, requestType); + } + EXPECT_NE(request.size(), 0u) << "Expected key request size" + " to have length > 0 bytes"; + keyRequest = request; + }); + EXPECT_OK(res); + return keyRequest; +} + +DrmHalVTSVendorModule_V1::ContentConfiguration DrmHalTest::getContent(const KeyType& type) const { + for (const auto& config : contentConfigurations) { + if (type != KeyType::OFFLINE || config.policy.allowOffline) { + return config; + } + } + EXPECT_TRUE(false) << "no content configurations found"; + return {}; +} + +hidl_vec<uint8_t> DrmHalTest::provideKeyResponse( + const SessionId& sessionId, + const hidl_vec<uint8_t>& keyResponse) { + hidl_vec<uint8_t> keySetId; + auto res = drmPlugin->provideKeyResponse( + sessionId, keyResponse, + [&](StatusV1_0 status, const hidl_vec<uint8_t>& myKeySetId) { + EXPECT_EQ(StatusV1_0::OK, status) << "Failure providing " + "key response for configuration "; + keySetId = myKeySetId; + }); + EXPECT_OK(res); + return 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. + */ +hidl_vec<uint8_t> DrmHalTest::loadKeys( + const SessionId& sessionId, + const DrmHalVTSVendorModule_V1::ContentConfiguration& configuration, + const KeyType& type) { + hidl_vec<uint8_t> keyRequest = getKeyRequest(sessionId, configuration, type); + + /** + * Get key response from vendor module + */ + hidl_vec<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); +} + +hidl_vec<uint8_t> DrmHalTest::loadKeys( + const SessionId& sessionId, + const KeyType& type) { + return loadKeys(sessionId, getContent(type), type); +} + +KeyedVector DrmHalTest::toHidlKeyedVector( + 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); +} + +hidl_array<uint8_t, 16> DrmHalTest::toHidlArray(const vector<uint8_t>& vec) { + EXPECT_EQ(16u, vec.size()); + return hidl_array<uint8_t, 16>(&vec[0]); +} + +/** + * getDecryptMemory allocates memory for decryption, then sets it + * as a shared buffer base in the crypto hal. The allocated and + * mapped IMemory 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. + */ +sp<IMemory> DrmHalTest::getDecryptMemory(size_t size, size_t index) { + sp<IAllocator> ashmemAllocator = IAllocator::getService("ashmem"); + EXPECT_NE(nullptr, ashmemAllocator.get()); + + hidl_memory hidlMemory; + auto res = ashmemAllocator->allocate( + size, [&](bool success, const hidl_memory& memory) { + EXPECT_EQ(success, true); + EXPECT_EQ(memory.size(), size); + hidlMemory = memory; + }); + + EXPECT_OK(res); + + sp<IMemory> mappedMemory = mapMemory(hidlMemory); + EXPECT_NE(nullptr, mappedMemory.get()); + res = cryptoPlugin->setSharedBufferBase(hidlMemory, index); + EXPECT_OK(res); + return mappedMemory; +} + +void DrmHalTest::fillRandom(const sp<IMemory>& memory) { + random_device rd; + mt19937 rand(rd()); + for (size_t i = 0; i < memory->getSize() / sizeof(uint32_t); i++) { + auto p = static_cast<uint32_t*>( + static_cast<void*>(memory->getPointer())); + p[i] = rand(); + } +} + +uint32_t DrmHalTest::decrypt(Mode mode, bool isSecure, + const hidl_array<uint8_t, 16>& keyId, uint8_t* iv, + const hidl_vec<SubSample>& subSamples, const Pattern& pattern, + const vector<uint8_t>& key, StatusV1_2 expectedStatus) { + const size_t kSegmentIndex = 0; + + uint8_t localIv[AES_BLOCK_SIZE]; + memcpy(localIv, iv, AES_BLOCK_SIZE); + + size_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 == StatusV1_2::ERROR_DRM_FRAME_TOO_LARGE ? 1 : 2; + sp<IMemory> sharedMemory = + getDecryptMemory(totalSize * factor, kSegmentIndex); + + const SharedBuffer sourceBuffer = { + .bufferId = kSegmentIndex, .offset = 0, .size = totalSize}; + fillRandom(sharedMemory); + + const DestinationBuffer destBuffer = {.type = BufferType::SHARED_MEMORY, + {.bufferId = kSegmentIndex, + .offset = totalSize, + .size = totalSize}, + .secureMemory = nullptr}; + const uint64_t offset = 0; + uint32_t bytesWritten = 0; + auto res = cryptoPlugin->decrypt_1_2(isSecure, keyId, localIv, mode, pattern, + subSamples, sourceBuffer, offset, destBuffer, + [&](StatusV1_2 status, uint32_t count, string detailedError) { + EXPECT_EQ(expectedStatus, status) << "Unexpected decrypt status " << + detailedError; + bytesWritten = count; + }); + EXPECT_OK(res); + + if (bytesWritten != totalSize) { + return bytesWritten; + } + uint8_t* base = static_cast<uint8_t*>( + static_cast<void*>(sharedMemory->getPointer())); + + // 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: + EXPECT_TRUE(false) << "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 hidl_vec<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 hidl_vec<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( + hidl_vec<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(); + auto res = drmPlugin->provideKeyResponse( + sessionId, invalidResponse, + [&](StatusV1_0 status, const hidl_vec<uint8_t>& myKeySetId) { + EXPECT_EQ(StatusV1_0::OK, status); + EXPECT_EQ(0u, myKeySetId.size()); + }); + EXPECT_OK(res); + + EXPECT_TRUE(cryptoPlugin->setMediaDrmSession(sessionId).isOk()); + + uint32_t byteCount = decrypt(Mode::AES_CTR, key.isSecure, + toHidlArray(key.keyId), &iv[0], subSamples, noPattern, + key.clearContentKey, Status::ERROR_DRM_NO_LICENSE); + EXPECT_EQ(0u, byteCount); + + closeSession(sessionId); +} + +} // namespace vts +} // namespace V1_2 +} // namespace drm +} // namespace hardware +} // namespace android |
