// // 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 #include "WVCryptoPlugin.h" #include #include #include #include "HidlTypes.h" #include "log.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); } } } // 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& cdm) : mCDM(cdm), mUserId(wvcdm::UNKNOWN_UID) { if (data != NULL) { mSessionId.assign(static_cast(data), size); } if (!mCDM->IsOpenSession(mSessionId)) { mSessionId.clear(); } else { mCDM->GetSessionUserId(mSessionId, &mUserId); } } WVCryptoPlugin::~WVCryptoPlugin() { if (wvcdm::UNKNOWN_UID != mUserId) { wvcdm::SetLoggingUid(mUserId); } } Return 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 WVCryptoPlugin::notifyResolution( uint32_t width, uint32_t height) { mCDM->NotifyResolution(mSessionId, width, height); return Void(); } Return WVCryptoPlugin::setMediaDrmSession( const hidl_vec& sessionId) { if (sessionId.size() == 0) { return Status::BAD_VALUE; } const std::vector 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 WVCryptoPlugin::setSharedBufferBase( const hidl_memory& base, uint32_t bufferId) { sp hidlMemory = mapMemory(base); // allow mapMemory to return nullptr mSharedBufferMap[bufferId] = hidlMemory; return Void(); } Return WVCryptoPlugin::decrypt( bool secure, const hidl_array& keyId, const hidl_array& iv, Mode mode, const Pattern& pattern, const hidl_vec& 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 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 WVCryptoPlugin::decrypt_1_2( bool secure, const hidl_array& keyId, const hidl_array& iv, Mode mode, const Pattern& pattern, const hidl_vec& 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(keyId.data()), wvcdm::KEY_ID_SIZE); std::vector ivVector(iv.data(), iv.data() + wvcdm::KEY_IV_SIZE); std::string errorDetailMsg; sp 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 (static_cast(sourceBase->getPointer())); uint8_t* srcPtr = static_cast(base + source.offset + offset); void* destPtr = NULL; if (destination.type == BufferType::SHARED_MEMORY) { const SharedBuffer& destBuffer = destination.nonsecureMemory; sp 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(base + destination.nonsecureMemory.offset); } else if (destination.type == BufferType::NATIVE_HANDLE) { native_handle_t *handle = const_cast( destination.secureMemory.getNativeHandle()); destPtr = static_cast(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); 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 mapCdmResponseType(res); } } Return WVCryptoPlugin::getLogMessages(getLogMessages_cb _hidl_cb) { const std::vector &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