The interface is defined in hardware/interfaces/drm/aidl(http://go/ag/15329852). Test: build m android.hardware.drm-service.widevine -j128 Test: build_and_run_all_unit_tests.sh for hidl tests Test: atest VtsAidlHalDrmTargetTest Test: atest vts_treble_vintf_vendor_test:vts_treble_vintf_vendor_test.DeviceManifest/SingleManifestTest#ManifestAidlHalsServed/0 -- --abi x86_64 Bug: 200055138 Bug: 170964303 Change-Id: I5654d90d8a4b0bae4b4a78e79b27c1cafec36be7
351 lines
12 KiB
C++
351 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::BufferType;
|
|
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::common::Ashmem& mem)
|
|
: mBase(nullptr), mSize(mem.size) {
|
|
if (mem.fd.get() < 0) {
|
|
return;
|
|
}
|
|
auto addr = mmap(nullptr, mem.size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
mem.fd.get(), 0);
|
|
if (addr == MAP_FAILED) {
|
|
ALOGE("mmap err: fd %d; errno %s", mem.fd.get(), 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::common::Ashmem& in_base,
|
|
int32_t in_bufferId) {
|
|
std::lock_guard<std::mutex> shared_buffer_lock(mSharedBufferLock);
|
|
|
|
mSharedBufferMap[in_bufferId] = std::make_shared<SharedBufferBase>(in_base);
|
|
return ::ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
::ndk::ScopedAStatus WVCryptoPlugin::decrypt(
|
|
bool in_secure, const std::vector<uint8_t>& in_keyId,
|
|
const std::vector<uint8_t>& in_iv,
|
|
::aidl::android::hardware::drm::Mode in_mode,
|
|
const ::aidl::android::hardware::drm::Pattern& in_pattern,
|
|
const std::vector<SubSample>& in_subSamples,
|
|
const ::aidl::android::hardware::drm::SharedBuffer& in_source,
|
|
int64_t in_offset,
|
|
const ::aidl::android::hardware::drm::DestinationBuffer& in_destination,
|
|
::aidl::android::hardware::drm::DecryptResult* _aidl_return) {
|
|
_aidl_return->bytesWritten = 0;
|
|
|
|
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_keyId.data()),
|
|
wvcdm::KEY_ID_SIZE);
|
|
|
|
// start scope for lock_guard
|
|
{
|
|
std::lock_guard<std::mutex> lock(mSharedBufferLock);
|
|
if (mSharedBufferMap.find(in_source.bufferId) == mSharedBufferMap.end()) {
|
|
_aidl_return->detailedError = "source decrypt buffer base not set";
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
if (in_destination.type == BufferType::SHARED_MEMORY) {
|
|
const SharedBuffer& dest = in_destination.nonsecureMemory;
|
|
if (mSharedBufferMap.find(dest.bufferId) == mSharedBufferMap.end()) {
|
|
_aidl_return->detailedError = "destination decrypt buffer base not set";
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
}
|
|
|
|
if (in_mode != Mode::UNENCRYPTED && in_mode != Mode::AES_CTR &&
|
|
in_mode != Mode::AES_CBC) {
|
|
_aidl_return->detailedError =
|
|
"The requested encryption mode is not supported by Widevine CDM.";
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
} else if (in_mode == Mode::AES_CTR &&
|
|
(in_pattern.encryptBlocks != 0 || in_pattern.skipBlocks != 0)) {
|
|
_aidl_return->detailedError =
|
|
"The 'cens' schema is not supported by Widevine CDM.";
|
|
return toNdkScopedAStatus(Status::BAD_VALUE);
|
|
}
|
|
|
|
auto src = mSharedBufferMap[in_source.bufferId];
|
|
if (src->mBase == nullptr) {
|
|
_aidl_return->detailedError = "source is a nullptr";
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
size_t totalSrcSize = 0;
|
|
if (__builtin_add_overflow(in_source.offset, in_offset, &totalSrcSize) ||
|
|
__builtin_add_overflow(totalSrcSize, in_source.size, &totalSrcSize) ||
|
|
totalSrcSize > src->mSize) {
|
|
android_errorWriteLog(0x534e4554, "176496160");
|
|
_aidl_return->detailedError = "invalid source buffer size";
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
srcPtr = src->mBase + in_source.offset + in_offset;
|
|
|
|
if (in_destination.type == BufferType::SHARED_MEMORY) {
|
|
const SharedBuffer& destBuffer = in_destination.nonsecureMemory;
|
|
auto dest = mSharedBufferMap[destBuffer.bufferId];
|
|
if (dest->mBase == nullptr) {
|
|
_aidl_return->detailedError = "destination is a nullptr";
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
size_t totalDstSize = 0;
|
|
if (__builtin_add_overflow(destBuffer.offset, destBuffer.size,
|
|
&totalDstSize) ||
|
|
totalDstSize > dest->mSize) {
|
|
android_errorWriteLog(0x534e4554, "176444622");
|
|
_aidl_return->detailedError = "invalid buffer size";
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_FRAME_TOO_LARGE);
|
|
}
|
|
destPtr = static_cast<void*>(dest->mBase +
|
|
in_destination.nonsecureMemory.offset);
|
|
} else if (in_destination.type == BufferType::NATIVE_HANDLE) {
|
|
native_handle_t* handle =
|
|
android::makeFromAidl(in_destination.secureMemory);
|
|
destPtr = static_cast<void*>(handle);
|
|
}
|
|
} // lock_guard scope
|
|
|
|
// Set up the decrypt params
|
|
CdmDecryptionParametersV16 params;
|
|
params.key_id = cryptoKey;
|
|
params.is_secure = in_secure;
|
|
if (in_mode == Mode::AES_CTR) {
|
|
params.cipher_mode = wvcdm::kCipherModeCtr;
|
|
} else if (in_mode == Mode::AES_CBC) {
|
|
params.cipher_mode = wvcdm::kCipherModeCbc;
|
|
}
|
|
params.pattern.encrypt_blocks = in_pattern.encryptBlocks;
|
|
params.pattern.skip_blocks = in_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_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_subSamples.size());
|
|
std::transform(
|
|
in_subSamples.data(), in_subSamples.data() + in_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_mode == Mode::UNENCRYPTED && hasProtectedData) {
|
|
_aidl_return->detailedError =
|
|
"Protected ranges found in allegedly clear data.";
|
|
return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE);
|
|
}
|
|
|
|
// Decrypt
|
|
std::string errorDetailMsg;
|
|
Status res = attemptDecrypt(params, hasProtectedData, &errorDetailMsg);
|
|
if (res != Status::OK) {
|
|
_aidl_return->detailedError = errorDetailMsg;
|
|
return toNdkScopedAStatus(res);
|
|
}
|
|
|
|
_aidl_return->bytesWritten = totalSize;
|
|
_aidl_return->detailedError = errorDetailMsg;
|
|
return toNdkScopedAStatus(Status::OK);
|
|
}
|
|
|
|
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
|