// // 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 #include #include #include #include #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& cdm) : mCDM(cdm), mUserId(wvutil::UNKNOWN_UID) { if (data != nullptr) { mSessionId.assign(static_cast(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(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& 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 shared_buffer_lock(mSharedBufferLock); mSharedBufferMap[in_base.bufferId] = std::make_shared(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(in_args.keyId.data()), wvcdm::KEY_ID_SIZE); // start scope for lock_guard { std::lock_guard 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(); 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(); 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( dest->mBase + in_args.destination.get().offset); } else if (in_args.destination.getTag() == DestinationBuffer::Tag::secureMemory) { native_handle_t* handle = android::makeFromAidl( in_args.destination.get()); destPtr = static_cast(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(res); } } ::ndk::ScopedAStatus WVCryptoPlugin::getLogMessages( std::vector<::aidl::android::hardware::drm::LogMessage>* _aidl_return) { const std::vector& 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