Files
hardware_interfaces/drm/aidl/vts/drm_hal_test.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

552 lines
19 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_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);
}