Files
android/libwvdrmengine/mediacrypto/aidl_src/WVCryptoPlugin.cpp
Edwin 9654d29be6 Widevine drm aidl: address API review
Interface update in change 16810770

Bug: 214410088
Test: atest VtsAidlHalDrmTargetTest
Change-Id: I19da51ef75952f5ff6c7c02e0393f574e69ee30b
2022-02-09 22:19:36 -08:00

354 lines
12 KiB
C++

//
// Copyright 2021 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 "WVCryptoPlugin.h"
#include <aidlcommonsupport/NativeHandle.h>
#include <sys/mman.h>
#include <utils/Log.h>
#include <algorithm>
#include <iterator>
#include "OEMCryptoCENC.h"
#include "Utils.h"
#include "WVErrors.h"
#include "log.h"
#include "mapErrors-inl.h"
#include "openssl/sha.h"
#include "wv_cdm_constants.h"
namespace wvdrm {
namespace hardware {
namespace drm {
namespace widevine {
using ::aidl::android::hardware::drm::DestinationBuffer;
using ::aidl::android::hardware::drm::Mode;
using ::aidl::android::hardware::drm::SharedBuffer;
using ::aidl::android::hardware::drm::Status;
using ::aidl::android::hardware::drm::SubSample;
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 ::android::sp<WvContentDecryptionModule>& cdm)
: mCDM(cdm), mUserId(wvutil::UNKNOWN_UID) {
if (data != nullptr) {
mSessionId.assign(static_cast<const char*>(data), size);
}
if (!mCDM->IsOpenSession(mSessionId)) {
mSessionId.clear();
} else {
mCDM->GetSessionUserId(mSessionId, &mUserId);
}
}
WVCryptoPlugin::~WVCryptoPlugin() {
if (wvutil::UNKNOWN_UID != mUserId) {
wvutil::SetLoggingUid(mUserId);
}
}
SharedBufferBase::SharedBufferBase(
const ::aidl::android::hardware::drm::SharedBuffer& mem)
: mBase(nullptr), mSize(mem.size) {
if (mem.handle.fds.empty()) {
return;
}
auto fd = mem.handle.fds[0].get();
auto addr =
mmap(nullptr, mem.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
ALOGE("mmap err: fd %d; errno %s", fd, strerror(errno));
} else {
mBase = static_cast<uint8_t*>(addr);
}
}
SharedBufferBase::~SharedBufferBase() {
if (munmap(mBase, mSize)) {
ALOGE("munmap err: base %p; errno %s", mBase, strerror(errno));
}
}
::ndk::ScopedAStatus WVCryptoPlugin::requiresSecureDecoderComponent(
const std::string& in_mime, bool* _aidl_return) {
if (!strncasecmp(in_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 (!::wvdrm::isCdmResponseTypeSuccess(res)) {
ALOGE("Error querying CDM status: %u", res);
*_aidl_return = false;
return ::ndk::ScopedAStatus::ok();
}
*_aidl_return = status[wvcdm::QUERY_KEY_SECURITY_LEVEL] ==
wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1;
return ::ndk::ScopedAStatus::ok();
} else {
// Type is not video, so never require a secure decoder.
*_aidl_return = false;
return ::ndk::ScopedAStatus::ok();
}
}
::ndk::ScopedAStatus WVCryptoPlugin::notifyResolution(int32_t in_width,
int32_t in_height) {
mCDM->NotifyResolution(mSessionId, in_width, in_height);
return ::ndk::ScopedAStatus::ok();
}
::ndk::ScopedAStatus WVCryptoPlugin::setMediaDrmSession(
const std::vector<uint8_t>& in_sessionId) {
if (in_sessionId.size() == 0) {
return toNdkScopedAStatus(Status::BAD_VALUE);
}
CdmSessionId cdmSessionId(in_sessionId.begin(), in_sessionId.end());
if (!mCDM->IsOpenSession(cdmSessionId)) {
return toNdkScopedAStatus(Status::ERROR_DRM_SESSION_NOT_OPENED);
} else {
mSessionId = cdmSessionId;
return toNdkScopedAStatus(Status::OK);
}
}
::ndk::ScopedAStatus WVCryptoPlugin::setSharedBufferBase(
const ::aidl::android::hardware::drm::SharedBuffer& in_base) {
std::lock_guard<std::mutex> shared_buffer_lock(mSharedBufferLock);
mSharedBufferMap[in_base.bufferId] =
std::make_shared<SharedBufferBase>(in_base);
return ::ndk::ScopedAStatus::ok();
}
::ndk::ScopedAStatus WVCryptoPlugin::decrypt(
const ::aidl::android::hardware::drm::DecryptArgs& in_args,
int32_t* _aidl_return) {
const char* detailedError = "";
*_aidl_return = 0; // bytes decrypted
uint8_t* srcPtr = nullptr;
void* destPtr = nullptr;
// Convert parameters to the form the CDM wishes to consume them in.
const KeyId cryptoKey(reinterpret_cast<const char*>(in_args.keyId.data()),
wvcdm::KEY_ID_SIZE);
// start scope for lock_guard
{
std::lock_guard<std::mutex> lock(mSharedBufferLock);
if (mSharedBufferMap.find(in_args.source.bufferId) ==
mSharedBufferMap.end()) {
detailedError = "source decrypt buffer base not set";
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE, detailedError);
}
const auto NON_SECURE = DestinationBuffer::Tag::nonsecureMemory;
if (in_args.destination.getTag() == NON_SECURE) {
const SharedBuffer& dest = in_args.destination.get<NON_SECURE>();
if (mSharedBufferMap.find(dest.bufferId) == mSharedBufferMap.end()) {
detailedError = "destination decrypt buffer base not set";
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE,
detailedError);
}
}
if (in_args.mode != Mode::UNENCRYPTED && in_args.mode != Mode::AES_CTR &&
in_args.mode != Mode::AES_CBC) {
detailedError =
"The requested encryption mode is not supported by Widevine CDM.";
return toNdkScopedAStatus(Status::BAD_VALUE, detailedError);
} else if (in_args.mode == Mode::AES_CTR &&
(in_args.pattern.encryptBlocks != 0 ||
in_args.pattern.skipBlocks != 0)) {
detailedError = "The 'cens' schema is not supported by Widevine CDM.";
return toNdkScopedAStatus(Status::BAD_VALUE, detailedError);
}
auto src = mSharedBufferMap[in_args.source.bufferId];
if (src->mBase == nullptr) {
detailedError = "source is a nullptr";
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE, detailedError);
}
size_t totalSrcSize = 0;
if (__builtin_add_overflow(in_args.source.offset, in_args.offset,
&totalSrcSize) ||
__builtin_add_overflow(totalSrcSize, in_args.source.size,
&totalSrcSize) ||
totalSrcSize > src->mSize) {
android_errorWriteLog(0x534e4554, "176496160");
detailedError = "invalid source buffer size";
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE, detailedError);
}
srcPtr = src->mBase + in_args.source.offset + in_args.offset;
if (in_args.destination.getTag() == NON_SECURE) {
const SharedBuffer& destBuffer = in_args.destination.get<NON_SECURE>();
auto dest = mSharedBufferMap[destBuffer.bufferId];
if (dest->mBase == nullptr) {
detailedError = "destination is a nullptr";
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE,
detailedError);
}
size_t totalDstSize = 0;
if (__builtin_add_overflow(destBuffer.offset, destBuffer.size,
&totalDstSize) ||
totalDstSize > dest->mSize) {
android_errorWriteLog(0x534e4554, "176444622");
detailedError = "invalid buffer size";
return toNdkScopedAStatus(Status::ERROR_DRM_FRAME_TOO_LARGE,
detailedError);
}
destPtr = static_cast<void*>(
dest->mBase + in_args.destination.get<NON_SECURE>().offset);
} else if (in_args.destination.getTag() ==
DestinationBuffer::Tag::secureMemory) {
native_handle_t* handle = android::makeFromAidl(
in_args.destination.get<DestinationBuffer::Tag::secureMemory>());
destPtr = static_cast<void*>(handle);
}
} // lock_guard scope
// Set up the decrypt params
CdmDecryptionParametersV16 params;
params.key_id = cryptoKey;
params.is_secure = in_args.secure;
if (in_args.mode == Mode::AES_CTR) {
params.cipher_mode = wvcdm::kCipherModeCtr;
} else if (in_args.mode == Mode::AES_CBC) {
params.cipher_mode = wvcdm::kCipherModeCbc;
}
params.pattern.encrypt_blocks = in_args.pattern.encryptBlocks;
params.pattern.skip_blocks = in_args.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 = in_args.iv;
// 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(in_args.subSamples.size());
std::transform(
in_args.subSamples.data(),
in_args.subSamples.data() + in_args.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 (in_args.mode == Mode::UNENCRYPTED && hasProtectedData) {
detailedError = "Protected ranges found in allegedly clear data.";
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE, detailedError);
}
// Decrypt
std::string errorDetailMsg;
Status res = attemptDecrypt(params, hasProtectedData, &errorDetailMsg);
if (res != Status::OK) {
detailedError = errorDetailMsg.data();
return toNdkScopedAStatus(res, detailedError);
}
*_aidl_return = totalSize; // return bytes decrypted
detailedError = errorDetailMsg.data();
return toNdkScopedAStatus(Status::OK, detailedError);
}
Status WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParametersV16& params,
bool hasProtectedData,
std::string* errorDetailMsg) {
CdmResponseType res = mCDM->DecryptV16(mSessionId, hasProtectedData, params);
if (::wvdrm::isCdmResponseTypeSuccess(res)) {
return Status::OK;
} else {
ALOGE("Decrypt error in session %s during a sample %s protected data: %d",
mSessionId.c_str(), hasProtectedData ? "with" : "without", res);
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:
break;
}
return ::wvdrm::mapCdmResponseType<Status>(res);
}
}
::ndk::ScopedAStatus WVCryptoPlugin::getLogMessages(
std::vector<::aidl::android::hardware::drm::LogMessage>* _aidl_return) {
const std::vector<wvutil::LogMessage>& logs(wvutil::g_logbuf.getLogs());
std::vector<::aidl::android::hardware::drm::LogMessage> msgs;
for (auto log : logs) {
msgs.push_back({log.time_ms_, ::wvdrm::toAidlLogPriority(log.priority_),
log.message_});
}
*_aidl_return = msgs;
return ::wvdrm::toNdkScopedAStatus(Status::OK);
}
} // namespace widevine
} // namespace drm
} // namespace hardware
} // namespace wvdrm