Files
android/libwvdrmengine/mediacrypto/src_hidl/WVCryptoPlugin.cpp
Robert Shih 12995287fa libwvhidl: implement API to get plugin logs
Bug: 162255728
Test: VtsHalDrmV1_4TargetTest
Change-Id: I333cb1ee2f25ae718e7f544f4a5f7ee50668041a
2021-02-04 06:43:54 -08:00

363 lines
12 KiB
C++

//
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
//
//#define LOG_NDEBUG 0
#define LOG_TAG "WVCdm"
#include <utils/Log.h>
#include "WVCryptoPlugin.h"
#include <algorithm>
#include <hidlmemory/mapping.h>
#include <iterator>
#include "HidlTypes.h"
#include "mapErrors-inl.h"
#include "OEMCryptoCENC.h"
#include "openssl/sha.h"
#include "TypeConvert.h"
#include "wv_cdm_constants.h"
#include "WVErrors.h"
namespace {
inline Status toStatus_1_0(Status_V1_2 status) {
switch (status) {
case Status_V1_2::ERROR_DRM_INSUFFICIENT_SECURITY:
case Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE:
case Status_V1_2::ERROR_DRM_SESSION_LOST_STATE:
return Status::ERROR_DRM_UNKNOWN;
default:
return static_cast<Status>(status);
}
}
} // namespace
namespace wvdrm {
namespace hardware {
namespace drm {
namespace V1_4 {
namespace widevine {
using android::hardware::drm::V1_2::widevine::toHidlVec;
using android::hardware::drm::V1_2::widevine::toVector;
using wvcdm::CdmDecryptionParametersV16;
using wvcdm::CdmDecryptionSample;
using wvcdm::CdmDecryptionSubsample;
using wvcdm::CdmQueryMap;
using wvcdm::CdmResponseType;
using wvcdm::CdmSessionId;
using wvcdm::KeyId;
using wvcdm::WvContentDecryptionModule;
WVCryptoPlugin::WVCryptoPlugin(const void* data, size_t size,
const sp<WvContentDecryptionModule>& cdm)
: mCDM(cdm){
if (data != NULL) {
mSessionId.assign(static_cast<const char *>(data), size);
}
if (!mCDM->IsOpenSession(mSessionId)) {
mSessionId.clear();
}
}
Return<bool> WVCryptoPlugin::requiresSecureDecoderComponent(
const hidl_string& mime) {
if (!strncasecmp(mime.c_str(), "video/", 6)) {
// Type is video, so query CDM to see if we require a secure decoder.
CdmQueryMap status;
CdmResponseType res = mCDM->QuerySessionStatus(mSessionId, &status);
if (!isCdmResponseTypeSuccess(res)) {
ALOGE("Error querying CDM status: %u", res);
return false;
}
return status[wvcdm::QUERY_KEY_SECURITY_LEVEL] ==
wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1;
} else {
// Type is not video, so never require a secure decoder.
return false;
}
}
Return<void> WVCryptoPlugin::notifyResolution(
uint32_t width, uint32_t height) {
mCDM->NotifyResolution(mSessionId, width, height);
return Void();
}
Return<Status> WVCryptoPlugin::setMediaDrmSession(
const hidl_vec<uint8_t>& sessionId) {
if (sessionId.size() == 0) {
return Status::BAD_VALUE;
}
const std::vector<uint8_t> sId = toVector(sessionId);
CdmSessionId cdmSessionId(sId.begin(), sId.end());
if (!mCDM->IsOpenSession(cdmSessionId)) {
return Status::ERROR_DRM_SESSION_NOT_OPENED;
} else {
mSessionId = cdmSessionId;
return Status::OK;
}
}
Return<void> WVCryptoPlugin::setSharedBufferBase(
const hidl_memory& base, uint32_t bufferId) {
sp<IMemory> hidlMemory = mapMemory(base);
// allow mapMemory to return nullptr
mSharedBufferMap[bufferId] = hidlMemory;
return Void();
}
Return<void> WVCryptoPlugin::decrypt(
bool secure,
const hidl_array<uint8_t, 16>& keyId,
const hidl_array<uint8_t, 16>& iv,
Mode mode,
const Pattern& pattern,
const hidl_vec<SubSample>& subSamples,
const SharedBuffer& source,
uint64_t offset,
const DestinationBuffer& destination,
decrypt_cb _hidl_cb) {
Status status = Status::ERROR_DRM_UNKNOWN;
hidl_string detailedError;
uint32_t bytesWritten = 0;
Return<void> hResult = decrypt_1_2(
secure, keyId, iv, mode, pattern, subSamples, source, offset, destination,
[&](Status_V1_2 hStatus, uint32_t hBytesWritten, hidl_string hDetailedError) {
status = toStatus_1_0(hStatus);
if (status == Status::OK) {
bytesWritten = hBytesWritten;
detailedError = hDetailedError;
}
}
);
status = hResult.isOk() ? status : Status::ERROR_DRM_CANNOT_HANDLE;
_hidl_cb(status, bytesWritten, detailedError);
return Void();
}
Return<void> WVCryptoPlugin::decrypt_1_2(
bool secure,
const hidl_array<uint8_t, 16>& keyId,
const hidl_array<uint8_t, 16>& iv,
Mode mode,
const Pattern& pattern,
const hidl_vec<SubSample>& subSamples,
const SharedBuffer& source,
uint64_t offset,
const DestinationBuffer& destination,
decrypt_1_2_cb _hidl_cb) {
if (mSharedBufferMap.find(source.bufferId) == mSharedBufferMap.end()) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"source decrypt buffer base not set");
return Void();
}
if (destination.type == BufferType::SHARED_MEMORY) {
const SharedBuffer& dest = destination.nonsecureMemory;
if (mSharedBufferMap.find(dest.bufferId) == mSharedBufferMap.end()) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"destination decrypt buffer base not set");
return Void();
}
}
if (mode != Mode::UNENCRYPTED &&
mode != Mode::AES_CTR &&
mode != Mode::AES_CBC) {
_hidl_cb(Status_V1_2::BAD_VALUE, 0,
"The requested encryption mode is not supported by Widevine CDM.");
return Void();
} else if (mode == Mode::AES_CTR &&
(pattern.encryptBlocks != 0 || pattern.skipBlocks != 0)) {
_hidl_cb(Status_V1_2::BAD_VALUE, 0,
"The 'cens' schema is not supported by Widevine CDM.");
return Void();
}
// Convert parameters to the form the CDM wishes to consume them in.
const KeyId cryptoKey(
reinterpret_cast<const char*>(keyId.data()), wvcdm::KEY_ID_SIZE);
std::vector<uint8_t> ivVector(iv.data(), iv.data() + wvcdm::KEY_IV_SIZE);
std::string errorDetailMsg;
sp<IMemory> sourceBase = mSharedBufferMap[source.bufferId];
if (sourceBase == nullptr) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "source is a nullptr");
return Void();
}
size_t totalSrcSize = 0;
if (__builtin_add_overflow(source.offset, offset, &totalSrcSize) ||
__builtin_add_overflow(totalSrcSize, source.size, &totalSrcSize) ||
totalSrcSize > sourceBase->getSize()) {
android_errorWriteLog(0x534e4554, "176496160");
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size");
return Void();
}
uint8_t *base = static_cast<uint8_t *>
(static_cast<void *>(sourceBase->getPointer()));
uint8_t* srcPtr = static_cast<uint8_t *>(base + source.offset + offset);
void* destPtr = NULL;
if (destination.type == BufferType::SHARED_MEMORY) {
const SharedBuffer& destBuffer = destination.nonsecureMemory;
sp<IMemory> destBase = mSharedBufferMap[destBuffer.bufferId];
if (destBase == nullptr) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "destination is a nullptr");
return Void();
}
if (destBuffer.offset + destBuffer.size > destBase->getSize()) {
_hidl_cb(Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE, 0, "invalid buffer size");
return Void();
}
destPtr = static_cast<void *>(base + destination.nonsecureMemory.offset);
} else if (destination.type == BufferType::NATIVE_HANDLE) {
native_handle_t *handle = const_cast<native_handle_t *>(
destination.secureMemory.getNativeHandle());
destPtr = static_cast<void *>(handle);
}
// Set up the decrypt params
CdmDecryptionParametersV16 params;
params.key_id = cryptoKey;
params.is_secure = secure;
if (mode == Mode::AES_CTR) {
params.cipher_mode = wvcdm::kCipherModeCtr;
} else if (mode == Mode::AES_CBC) {
params.cipher_mode = wvcdm::kCipherModeCbc;
}
params.pattern.encrypt_blocks = pattern.encryptBlocks;
params.pattern.skip_blocks = pattern.skipBlocks;
// Set up the sample
// Android's API only supports one at a time
params.samples.emplace_back();
CdmDecryptionSample& sample = params.samples.back();
sample.encrypt_buffer = srcPtr;
sample.decrypt_buffer = destPtr;
sample.decrypt_buffer_offset = 0;
sample.iv = ivVector;
// Set up the subsamples
// We abuse std::transform() here to also do some side-effects: Tallying the
// total size of the sample and checking if any of the data is protected.
size_t totalSize = 0;
bool hasProtectedData = false;
sample.subsamples.reserve(subSamples.size());
std::transform(subSamples.data(), subSamples.data() + subSamples.size(),
std::back_inserter(sample.subsamples),
[&](const SubSample& subSample) -> CdmDecryptionSubsample {
totalSize +=
subSample.numBytesOfClearData + subSample.numBytesOfEncryptedData;
hasProtectedData |= subSample.numBytesOfEncryptedData > 0;
return CdmDecryptionSubsample(subSample.numBytesOfClearData,
subSample.numBytesOfEncryptedData);
});
sample.encrypt_buffer_length = totalSize;
sample.decrypt_buffer_size = totalSize;
if (mode == Mode::UNENCRYPTED && hasProtectedData) {
_hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0,
"Protected ranges found in allegedly clear data.");
return Void();
}
// Decrypt
Status_V1_2 res = attemptDecrypt(params, hasProtectedData, &errorDetailMsg);
if (res != Status_V1_2::OK) {
_hidl_cb(res, 0, errorDetailMsg.c_str());
return Void();
}
_hidl_cb(Status_V1_2::OK, totalSize, errorDetailMsg.c_str());
return Void();
}
Status_V1_2 WVCryptoPlugin::attemptDecrypt(
const CdmDecryptionParametersV16& params, bool hasProtectedData,
std::string* errorDetailMsg) {
CdmResponseType res = mCDM->DecryptV16(mSessionId, hasProtectedData,
params);
if (isCdmResponseTypeSuccess(res)) {
return Status_V1_2::OK;
} else {
ALOGE("Decrypt error in session %s during a sample %s protected data: %d",
mSessionId.c_str(),
hasProtectedData ? "with" : "without",
res);
bool actionableError = true;
switch (res) {
case wvcdm::INSUFFICIENT_CRYPTO_RESOURCES:
errorDetailMsg->assign(
"Error decrypting data: insufficient crypto resources");
break;
case wvcdm::NEED_KEY:
case wvcdm::KEY_NOT_FOUND_IN_SESSION:
errorDetailMsg->assign(
"Error decrypting data: requested key has not been loaded");
break;
case wvcdm::DECRYPT_NOT_READY:
errorDetailMsg->assign(
"Error decrypting data: license validity period is in the future");
break;
case wvcdm::SESSION_NOT_FOUND_FOR_DECRYPT:
errorDetailMsg->assign(
"Error decrypting data: session not found, possibly reclaimed");
break;
case wvcdm::DECRYPT_ERROR:
errorDetailMsg->assign(
"Error decrypting data: unspecified error");
break;
case wvcdm::INSUFFICIENT_OUTPUT_PROTECTION:
case wvcdm::ANALOG_OUTPUT_ERROR:
errorDetailMsg->assign(
"Error decrypting data: insufficient output protection");
break;
case wvcdm::KEY_PROHIBITED_FOR_SECURITY_LEVEL:
errorDetailMsg->assign(
"Error decrypting data: key prohibited for security level");
break;
default:
actionableError = false;
break;
}
if (actionableError) {
// This error is actionable by the app and should be passed up.
return mapCdmResponseType_1_2(res);
} else {
// Swallow the specifics of other errors to obscure decrypt internals.
return Status_V1_2::ERROR_DRM_UNKNOWN;
}
}
}
Return<void> WVCryptoPlugin::getLogMessages(getLogMessages_cb _hidl_cb) {
const std::vector<wvcdm::LogMessage> &logs(wvcdm::g_logbuf.getLogs());
_hidl_cb(::drm::V1_4::Status::OK, toHidlVec<::drm::V1_4::LogMessage>(logs));
return Void();
}
} // namespace widevine
} // namespace V1_4
} // namespace drm
} // namespace hardware
} // namespace wvdrm