/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "CryptoHal" #include #include #include #include #include #include #include #include #include #include #include using drm::V1_0::BufferType; using drm::V1_0::DestinationBuffer; using drm::V1_0::ICryptoFactory; using drm::V1_0::ICryptoPlugin; using drm::V1_0::Mode; using drm::V1_0::Pattern; using drm::V1_0::SharedBuffer; using drm::V1_0::Status; using drm::V1_0::SubSample; using ::android::hardware::hidl_array; using ::android::hardware::hidl_handle; using ::android::hardware::hidl_memory; using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; using ::android::hardware::HidlMemory; using ::android::hardware::Return; using ::android::hardware::Void; using ::android::sp; typedef drm::V1_2::Status Status_V1_2; namespace android { static status_t toStatusT(Status status) { switch (status) { case Status::OK: return OK; case Status::ERROR_DRM_NO_LICENSE: return ERROR_DRM_NO_LICENSE; case Status::ERROR_DRM_LICENSE_EXPIRED: return ERROR_DRM_LICENSE_EXPIRED; case Status::ERROR_DRM_RESOURCE_BUSY: return ERROR_DRM_RESOURCE_BUSY; case Status::ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION: return ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION; case Status::ERROR_DRM_SESSION_NOT_OPENED: return ERROR_DRM_SESSION_NOT_OPENED; case Status::ERROR_DRM_CANNOT_HANDLE: return ERROR_DRM_CANNOT_HANDLE; case Status::ERROR_DRM_DECRYPT: return ERROR_DRM_DECRYPT; default: return UNKNOWN_ERROR; } } static status_t toStatusT_1_2(Status_V1_2 status) { switch (status) { case Status_V1_2::ERROR_DRM_SESSION_LOST_STATE: return ERROR_DRM_SESSION_LOST_STATE;; case Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE: return ERROR_DRM_FRAME_TOO_LARGE; case Status_V1_2::ERROR_DRM_INSUFFICIENT_SECURITY: return ERROR_DRM_INSUFFICIENT_SECURITY; default: return toStatusT(static_cast(status)); } } static hidl_vec toHidlVec(const Vector &vector) { hidl_vec vec; vec.setToExternal(const_cast(vector.array()), vector.size()); return vec; } static hidl_vec toHidlVec(const void *ptr, size_t size) { hidl_vec vec; vec.resize(size); memcpy(vec.data(), ptr, size); return vec; } static hidl_array toHidlArray16(const uint8_t *ptr) { if (!ptr) { return hidl_array(); } return hidl_array(ptr); } static String8 toString8(hidl_string hString) { return String8(hString.c_str()); } CryptoHal::CryptoHal() : mFactories(makeCryptoFactories()), mInitCheck((mFactories.size() == 0) ? ERROR_UNSUPPORTED : NO_INIT), mHeapSeqNum(0) { } CryptoHal::~CryptoHal() { } Vector> CryptoHal::makeCryptoFactories() { Vector> factories; auto manager = hardware::defaultServiceManager1_2(); if (manager != NULL) { manager->listManifestByInterface(drm::V1_0::ICryptoFactory::descriptor, [&factories](const hidl_vec ®istered) { for (const auto &instance : registered) { auto factory = drm::V1_0::ICryptoFactory::getService(instance); if (factory != NULL) { ALOGD("found drm@1.0 ICryptoFactory %s", instance.c_str()); factories.push_back(factory); } } } ); manager->listManifestByInterface(drm::V1_1::ICryptoFactory::descriptor, [&factories](const hidl_vec ®istered) { for (const auto &instance : registered) { auto factory = drm::V1_1::ICryptoFactory::getService(instance); if (factory != NULL) { ALOGD("found drm@1.1 ICryptoFactory %s", instance.c_str()); factories.push_back(factory); } } } ); } if (factories.size() == 0) { // must be in passthrough mode, load the default passthrough service auto passthrough = ICryptoFactory::getService(); if (passthrough != NULL) { ALOGI("makeCryptoFactories: using default passthrough crypto instance"); factories.push_back(passthrough); } else { ALOGE("Failed to find any crypto factories"); } } return factories; } sp CryptoHal::makeCryptoPlugin(const sp& factory, const uint8_t uuid[16], const void *initData, size_t initDataSize) { sp plugin; Return hResult = factory->createPlugin(toHidlArray16(uuid), toHidlVec(initData, initDataSize), [&](Status status, const sp& hPlugin) { if (status != Status::OK) { ALOGE("Failed to make crypto plugin"); return; } plugin = hPlugin; } ); return plugin; } status_t CryptoHal::initCheck() const { return mInitCheck; } bool CryptoHal::isCryptoSchemeSupported(const uint8_t uuid[16]) { Mutex::Autolock autoLock(mLock); for (size_t i = 0; i < mFactories.size(); i++) { if (mFactories[i]->isCryptoSchemeSupported(uuid)) { return true; } } return false; } status_t CryptoHal::createPlugin(const uint8_t uuid[16], const void *data, size_t size) { Mutex::Autolock autoLock(mLock); for (size_t i = 0; i < mFactories.size(); i++) { if (mFactories[i]->isCryptoSchemeSupported(uuid)) { mPlugin = makeCryptoPlugin(mFactories[i], uuid, data, size); if (mPlugin != NULL) { mPluginV1_2 = drm::V1_2::ICryptoPlugin::castFrom(mPlugin); } } } if (mPlugin == NULL) { mInitCheck = ERROR_UNSUPPORTED; } else { mInitCheck = OK; } return mInitCheck; } status_t CryptoHal::destroyPlugin() { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } mPlugin.clear(); mPluginV1_2.clear(); return OK; } bool CryptoHal::requiresSecureDecoderComponent(const char *mime) const { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return false; } Return hResult = mPlugin->requiresSecureDecoderComponent(hidl_string(mime)); if (!hResult.isOk()) { return false; } return hResult; } /** * If the heap base isn't set, get the heap base from the HidlMemory * and send it to the HAL so it can map a remote heap of the same * size. Once the heap base is established, shared memory buffers * are sent by providing an offset into the heap and a buffer size. */ int32_t CryptoHal::setHeapBase(const sp& heap) { if (heap == NULL || mHeapSeqNum < 0) { ALOGE("setHeapBase(): heap %p mHeapSeqNum %d", heap.get(), mHeapSeqNum); return -1; } Mutex::Autolock autoLock(mLock); int32_t seqNum = mHeapSeqNum++; uint32_t bufferId = static_cast(seqNum); mHeapSizes.add(seqNum, heap->size()); Return hResult = mPlugin->setSharedBufferBase(*heap, bufferId); ALOGE_IF(!hResult.isOk(), "setSharedBufferBase(): remote call failed"); return seqNum; } void CryptoHal::clearHeapBase(int32_t seqNum) { Mutex::Autolock autoLock(mLock); /* * Clear the remote shared memory mapping by setting the shared * buffer base to a null hidl_memory. * * TODO: Add a releaseSharedBuffer method in a future DRM HAL * API version to make this explicit. */ ssize_t index = mHeapSizes.indexOfKey(seqNum); if (index >= 0) { if (mPlugin != NULL) { uint32_t bufferId = static_cast(seqNum); Return hResult = mPlugin->setSharedBufferBase(hidl_memory(), bufferId); ALOGE_IF(!hResult.isOk(), "setSharedBufferBase(): remote call failed"); } mHeapSizes.removeItem(seqNum); } } status_t CryptoHal::checkSharedBuffer(const ::SharedBuffer &buffer) { int32_t seqNum = static_cast(buffer.bufferId); // memory must be in one of the heaps that have been set if (mHeapSizes.indexOfKey(seqNum) < 0) { return UNKNOWN_ERROR; } // memory must be within the address space of the heap size_t heapSize = mHeapSizes.valueFor(seqNum); if (heapSize < buffer.offset + buffer.size || SIZE_MAX - buffer.offset < buffer.size) { android_errorWriteLog(0x534e4554, "76221123"); return UNKNOWN_ERROR; } return OK; } ssize_t CryptoHal::decrypt(const uint8_t keyId[16], const uint8_t iv[16], CryptoPlugin::Mode mode, const CryptoPlugin::Pattern &pattern, const ::SharedBuffer &hSource, size_t offset, const CryptoPlugin::SubSample *subSamples, size_t numSubSamples, const ::DestinationBuffer &hDestination, AString *errorDetailMsg) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } Mode hMode; switch(mode) { case CryptoPlugin::kMode_Unencrypted: hMode = Mode::UNENCRYPTED ; break; case CryptoPlugin::kMode_AES_CTR: hMode = Mode::AES_CTR; break; case CryptoPlugin::kMode_AES_WV: hMode = Mode::AES_CBC_CTS; break; case CryptoPlugin::kMode_AES_CBC: hMode = Mode::AES_CBC; break; default: return UNKNOWN_ERROR; } Pattern hPattern; hPattern.encryptBlocks = pattern.mEncryptBlocks; hPattern.skipBlocks = pattern.mSkipBlocks; std::vector stdSubSamples; for (size_t i = 0; i < numSubSamples; i++) { SubSample subSample; subSample.numBytesOfClearData = subSamples[i].mNumBytesOfClearData; subSample.numBytesOfEncryptedData = subSamples[i].mNumBytesOfEncryptedData; stdSubSamples.push_back(subSample); } auto hSubSamples = hidl_vec(stdSubSamples); bool secure; if (hDestination.type == BufferType::SHARED_MEMORY) { status_t status = checkSharedBuffer(hDestination.nonsecureMemory); if (status != OK) { return status; } secure = false; } else if (hDestination.type == BufferType::NATIVE_HANDLE) { secure = true; } else { android_errorWriteLog(0x534e4554, "70526702"); return UNKNOWN_ERROR; } status_t status = checkSharedBuffer(hSource); if (status != OK) { return status; } status_t err = UNKNOWN_ERROR; uint32_t bytesWritten = 0; Return hResult; if (mPluginV1_2 != NULL) { hResult = mPluginV1_2->decrypt_1_2(secure, toHidlArray16(keyId), toHidlArray16(iv), hMode, hPattern, hSubSamples, hSource, offset, hDestination, [&](Status_V1_2 status, uint32_t hBytesWritten, hidl_string hDetailedError) { if (status == Status_V1_2::OK) { bytesWritten = hBytesWritten; *errorDetailMsg = toString8(hDetailedError); } err = toStatusT_1_2(status); } ); } else { hResult = mPlugin->decrypt(secure, toHidlArray16(keyId), toHidlArray16(iv), hMode, hPattern, hSubSamples, hSource, offset, hDestination, [&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) { if (status == Status::OK) { bytesWritten = hBytesWritten; *errorDetailMsg = toString8(hDetailedError); } err = toStatusT(status); } ); } err = hResult.isOk() ? err : DEAD_OBJECT; if (err == OK) { return bytesWritten; } return err; } void CryptoHal::notifyResolution(uint32_t width, uint32_t height) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return; } mPlugin->notifyResolution(width, height); } status_t CryptoHal::setMediaDrmSession(const Vector &sessionId) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } return toStatusT(mPlugin->setMediaDrmSession(toHidlVec(sessionId))); } } // namespace android