[ Merge of http://go/wvgerrit/168397 ] When CdmResponseType (enum) was transformed to CdmResponseType (struct), the test printers where not updated to print the result of failed comparisons. In addition, several logs statements were updated haphazardly, leaving inconsistencies and potential compiler-specific behavior. This CL replaces CdmResponseType std::string operator with a ToString() method. This is to make it consistent with Google's C++ style guide on conversion operators vs methods. The string conversion function is now defined in wv_cdm_types.cpp instead of inline in the header file. The PrintTo function has been implemented along with the other CDM test printers in test_printers.cpp. Bug: 273989359 Test: run_x86_64_tests Test: MediaDrmParameterizedTests on redfin Test: Forrest drm_compliance Change-Id: Ibfaa17029046b75b1c8c278f7bd7e04a24379848
363 lines
13 KiB
C++
363 lines
13 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 <android/binder_ibinder_platform.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) {
|
|
set_base(nullptr);
|
|
set_size(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 {
|
|
set_base(static_cast<uint8_t*>(addr));
|
|
}
|
|
}
|
|
|
|
SharedBufferBase::~SharedBufferBase() {
|
|
if (munmap(base(), size())) {
|
|
ALOGE("munmap err: base %p; errno %s", base(), 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: %d", static_cast<int>(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
|
|
|
|
native_handle_t* handle = nullptr;
|
|
uint8_t* srcPtr = nullptr;
|
|
void* destPtr = nullptr;
|
|
// Convert parameters to the form the CDM wishes to consume them in.
|
|
const KeyId cryptoKey(in_args.keyId.begin(), in_args.keyId.end());
|
|
|
|
// 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;
|
|
const auto SECURE = DestinationBuffer::Tag::secureMemory;
|
|
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->base() == 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->size()) {
|
|
android_errorWriteLog(0x534e4554, "176496160");
|
|
detailedError = "invalid source buffer size";
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE, detailedError);
|
|
}
|
|
|
|
srcPtr = src->base() + 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->base() == 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->size()) {
|
|
android_errorWriteLog(0x534e4554, "176444622");
|
|
detailedError = "invalid buffer size";
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_FRAME_TOO_LARGE,
|
|
detailedError);
|
|
}
|
|
destPtr = static_cast<void*>(
|
|
dest->base() + in_args.destination.get<NON_SECURE>().offset);
|
|
} else if (in_args.destination.getTag() == SECURE) {
|
|
handle = android::makeFromAidl(in_args.destination.get<SECURE>());
|
|
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) {
|
|
native_handle_delete(handle);
|
|
detailedError = "Protected ranges found in allegedly clear data.";
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE, detailedError);
|
|
}
|
|
|
|
// Decrypt
|
|
std::string errorDetailMsg;
|
|
auto res = attemptDecrypt(params, hasProtectedData, &errorDetailMsg);
|
|
native_handle_delete(handle);
|
|
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);
|
|
}
|
|
|
|
::ndk::SpAIBinder WVCryptoPlugin::createBinder() {
|
|
auto binder = BnCryptoPlugin::createBinder();
|
|
AIBinder_setRequestingSid(binder.get(), true);
|
|
return binder;
|
|
}
|
|
|
|
::wvdrm::WvStatus WVCryptoPlugin::attemptDecrypt(
|
|
const CdmDecryptionParametersV16& params, bool hasProtectedData,
|
|
std::string* errorDetailMsg) {
|
|
CdmResponseType res = mCDM->DecryptV16(mSessionId, hasProtectedData, params);
|
|
|
|
if (::wvdrm::isCdmResponseTypeSuccess(res)) {
|
|
return ::wvdrm::WvStatus(Status::OK);
|
|
} else {
|
|
ALOGE("Decrypt error in session %s during a sample %s protected data: %d",
|
|
mSessionId.c_str(), hasProtectedData ? "with" : "without", static_cast<int>(res));
|
|
switch (res.code()) {
|
|
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(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
|