/* * Copyright (C) 2013 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 "Drm" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { static inline int getCallingPid() { return IPCThreadState::self()->getCallingPid(); } static bool checkPermission(const char* permissionString) { if (getpid() == IPCThreadState::self()->getCallingPid()) return true; bool ok = checkCallingPermission(String16(permissionString)); if (!ok) ALOGE("Request requires %s", permissionString); return ok; } KeyedVector, String8> Drm::mUUIDToLibraryPathMap; KeyedVector > Drm::mLibraryPathToOpenLibraryMap; Mutex Drm::mMapLock; Mutex Drm::mLock; static bool operator<(const Vector &lhs, const Vector &rhs) { if (lhs.size() < rhs.size()) { return true; } else if (lhs.size() > rhs.size()) { return false; } return memcmp((void *)lhs.array(), (void *)rhs.array(), rhs.size()) < 0; } struct DrmSessionClient : public DrmSessionClientInterface { DrmSessionClient(Drm* drm) : mDrm(drm) {} virtual bool reclaimSession(const Vector& sessionId) { sp drm = mDrm.promote(); if (drm == NULL) { return true; } status_t err = drm->closeSession(sessionId); if (err != OK) { return false; } drm->sendEvent(DrmPlugin::kDrmPluginEventSessionReclaimed, 0, &sessionId, NULL); return true; } protected: virtual ~DrmSessionClient() {} private: wp mDrm; DISALLOW_EVIL_CONSTRUCTORS(DrmSessionClient); }; Drm::Drm() : mInitCheck(NO_INIT), mDrmSessionClient(new DrmSessionClient(this)), mListener(NULL), mFactory(NULL), mPlugin(NULL) { } Drm::~Drm() { DrmSessionManager::Instance()->removeDrm(mDrmSessionClient); delete mPlugin; mPlugin = NULL; closeFactory(); } void Drm::closeFactory() { delete mFactory; mFactory = NULL; mLibrary.clear(); } status_t Drm::initCheck() const { return mInitCheck; } status_t Drm::setListener(const sp& listener) { Mutex::Autolock lock(mEventLock); if (mListener != NULL){ IInterface::asBinder(mListener)->unlinkToDeath(this); } if (listener != NULL) { IInterface::asBinder(listener)->linkToDeath(this); } mListener = listener; return NO_ERROR; } void Drm::sendEvent(DrmPlugin::EventType eventType, int extra, Vector const *sessionId, Vector const *data) { mEventLock.lock(); sp listener = mListener; mEventLock.unlock(); if (listener != NULL) { Parcel obj; writeByteArray(obj, sessionId); writeByteArray(obj, data); Mutex::Autolock lock(mNotifyLock); listener->notify(eventType, extra, &obj); } } void Drm::sendExpirationUpdate(Vector const *sessionId, int64_t expiryTimeInMS) { mEventLock.lock(); sp listener = mListener; mEventLock.unlock(); if (listener != NULL) { Parcel obj; writeByteArray(obj, sessionId); obj.writeInt64(expiryTimeInMS); Mutex::Autolock lock(mNotifyLock); listener->notify(DrmPlugin::kDrmPluginEventExpirationUpdate, 0, &obj); } } void Drm::sendKeysChange(Vector const *sessionId, Vector const *keyStatusList, bool hasNewUsableKey) { mEventLock.lock(); sp listener = mListener; mEventLock.unlock(); if (listener != NULL) { Parcel obj; writeByteArray(obj, sessionId); size_t nkeys = keyStatusList->size(); obj.writeInt32(keyStatusList->size()); for (size_t i = 0; i < nkeys; ++i) { const DrmPlugin::KeyStatus *keyStatus = &keyStatusList->itemAt(i); writeByteArray(obj, &keyStatus->mKeyId); obj.writeInt32(keyStatus->mType); } obj.writeInt32(hasNewUsableKey); Mutex::Autolock lock(mNotifyLock); listener->notify(DrmPlugin::kDrmPluginEventKeysChange, 0, &obj); } } /* * Search the plugins directory for a plugin that supports the scheme * specified by uuid * * If found: * mLibrary holds a strong pointer to the dlopen'd library * mFactory is set to the library's factory method * mInitCheck is set to OK * * If not found: * mLibrary is cleared and mFactory are set to NULL * mInitCheck is set to an error (!OK) */ void Drm::findFactoryForScheme(const uint8_t uuid[16]) { closeFactory(); // lock static maps Mutex::Autolock autoLock(mMapLock); // first check cache Vector uuidVector; uuidVector.appendArray(uuid, sizeof(uuid[0]) * 16); ssize_t index = mUUIDToLibraryPathMap.indexOfKey(uuidVector); if (index >= 0) { if (loadLibraryForScheme(mUUIDToLibraryPathMap[index], uuid)) { mInitCheck = OK; return; } else { ALOGE("Failed to load from cached library path!"); mInitCheck = ERROR_UNSUPPORTED; return; } } // no luck, have to search String8 dirPath("/vendor/lib/mediadrm"); DIR* pDir = opendir(dirPath.string()); if (pDir == NULL) { mInitCheck = ERROR_UNSUPPORTED; ALOGE("Failed to open plugin directory %s", dirPath.string()); return; } struct dirent* pEntry; while ((pEntry = readdir(pDir))) { String8 pluginPath = dirPath + "/" + pEntry->d_name; if (pluginPath.getPathExtension() == ".so") { if (loadLibraryForScheme(pluginPath, uuid)) { mUUIDToLibraryPathMap.add(uuidVector, pluginPath); mInitCheck = OK; closedir(pDir); return; } } } closedir(pDir); ALOGE("Failed to find drm plugin"); mInitCheck = ERROR_UNSUPPORTED; } bool Drm::loadLibraryForScheme(const String8 &path, const uint8_t uuid[16]) { // get strong pointer to open shared library ssize_t index = mLibraryPathToOpenLibraryMap.indexOfKey(path); if (index >= 0) { mLibrary = mLibraryPathToOpenLibraryMap[index].promote(); } else { index = mLibraryPathToOpenLibraryMap.add(path, NULL); } if (!mLibrary.get()) { mLibrary = new SharedLibrary(path); if (!*mLibrary) { return false; } mLibraryPathToOpenLibraryMap.replaceValueAt(index, mLibrary); } typedef DrmFactory *(*CreateDrmFactoryFunc)(); CreateDrmFactoryFunc createDrmFactory = (CreateDrmFactoryFunc)mLibrary->lookup("createDrmFactory"); if (createDrmFactory == NULL || (mFactory = createDrmFactory()) == NULL || !mFactory->isCryptoSchemeSupported(uuid)) { closeFactory(); return false; } return true; } bool Drm::isCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) { Mutex::Autolock autoLock(mLock); if (!mFactory || !mFactory->isCryptoSchemeSupported(uuid)) { findFactoryForScheme(uuid); if (mInitCheck != OK) { return false; } } if (mimeType != "") { return mFactory->isContentTypeSupported(mimeType); } return true; } status_t Drm::createPlugin(const uint8_t uuid[16]) { Mutex::Autolock autoLock(mLock); if (mPlugin != NULL) { return -EINVAL; } if (!mFactory || !mFactory->isCryptoSchemeSupported(uuid)) { findFactoryForScheme(uuid); } if (mInitCheck != OK) { return mInitCheck; } status_t result = mFactory->createDrmPlugin(uuid, &mPlugin); mPlugin->setListener(this); return result; } status_t Drm::destroyPlugin() { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } delete mPlugin; mPlugin = NULL; return OK; } status_t Drm::openSession(Vector &sessionId) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } status_t err = mPlugin->openSession(sessionId); if (err == ERROR_DRM_RESOURCE_BUSY) { bool retry = false; mLock.unlock(); // reclaimSession may call back to closeSession, since mLock is shared between Drm // instances, we should unlock here to avoid deadlock. retry = DrmSessionManager::Instance()->reclaimSession(getCallingPid()); mLock.lock(); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } if (retry) { err = mPlugin->openSession(sessionId); } } if (err == OK) { DrmSessionManager::Instance()->addSession(getCallingPid(), mDrmSessionClient, sessionId); } return err; } status_t Drm::closeSession(Vector const &sessionId) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } status_t err = mPlugin->closeSession(sessionId); if (err == OK) { DrmSessionManager::Instance()->removeSession(sessionId); } return err; } status_t Drm::getKeyRequest(Vector const &sessionId, Vector const &initData, String8 const &mimeType, DrmPlugin::KeyType keyType, KeyedVector const &optionalParameters, Vector &request, String8 &defaultUrl, DrmPlugin::KeyRequestType *keyRequestType) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->getKeyRequest(sessionId, initData, mimeType, keyType, optionalParameters, request, defaultUrl, keyRequestType); } status_t Drm::provideKeyResponse(Vector const &sessionId, Vector const &response, Vector &keySetId) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->provideKeyResponse(sessionId, response, keySetId); } status_t Drm::removeKeys(Vector const &keySetId) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->removeKeys(keySetId); } status_t Drm::restoreKeys(Vector const &sessionId, Vector const &keySetId) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->restoreKeys(sessionId, keySetId); } status_t Drm::queryKeyStatus(Vector const &sessionId, KeyedVector &infoMap) const { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->queryKeyStatus(sessionId, infoMap); } status_t Drm::getProvisionRequest(String8 const &certType, String8 const &certAuthority, Vector &request, String8 &defaultUrl) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->getProvisionRequest(certType, certAuthority, request, defaultUrl); } status_t Drm::provideProvisionResponse(Vector const &response, Vector &certificate, Vector &wrappedKey) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->provideProvisionResponse(response, certificate, wrappedKey); } status_t Drm::getSecureStops(List > &secureStops) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->getSecureStops(secureStops); } status_t Drm::getSecureStop(Vector const &ssid, Vector &secureStop) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->getSecureStop(ssid, secureStop); } status_t Drm::releaseSecureStops(Vector const &ssRelease) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->releaseSecureStops(ssRelease); } status_t Drm::releaseAllSecureStops() { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->releaseAllSecureStops(); } status_t Drm::getPropertyString(String8 const &name, String8 &value ) const { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->getPropertyString(name, value); } status_t Drm::getPropertyByteArray(String8 const &name, Vector &value ) const { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->getPropertyByteArray(name, value); } status_t Drm::setPropertyString(String8 const &name, String8 const &value ) const { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->setPropertyString(name, value); } status_t Drm::setPropertyByteArray(String8 const &name, Vector const &value ) const { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } return mPlugin->setPropertyByteArray(name, value); } status_t Drm::setCipherAlgorithm(Vector const &sessionId, String8 const &algorithm) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->setCipherAlgorithm(sessionId, algorithm); } status_t Drm::setMacAlgorithm(Vector const &sessionId, String8 const &algorithm) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->setMacAlgorithm(sessionId, algorithm); } status_t Drm::encrypt(Vector const &sessionId, Vector const &keyId, Vector const &input, Vector const &iv, Vector &output) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->encrypt(sessionId, keyId, input, iv, output); } status_t Drm::decrypt(Vector const &sessionId, Vector const &keyId, Vector const &input, Vector const &iv, Vector &output) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->decrypt(sessionId, keyId, input, iv, output); } status_t Drm::sign(Vector const &sessionId, Vector const &keyId, Vector const &message, Vector &signature) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->sign(sessionId, keyId, message, signature); } status_t Drm::verify(Vector const &sessionId, Vector const &keyId, Vector const &message, Vector const &signature, bool &match) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->verify(sessionId, keyId, message, signature, match); } status_t Drm::signRSA(Vector const &sessionId, String8 const &algorithm, Vector const &message, Vector const &wrappedKey, Vector &signature) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { return mInitCheck; } if (mPlugin == NULL) { return -EINVAL; } if (!checkPermission("android.permission.ACCESS_DRM_CERTIFICATES")) { return -EPERM; } DrmSessionManager::Instance()->useSession(sessionId); return mPlugin->signRSA(sessionId, algorithm, message, wrappedKey, signature); } void Drm::binderDied(const wp &the_late_who __unused) { mEventLock.lock(); mListener.clear(); mEventLock.unlock(); Mutex::Autolock autoLock(mLock); delete mPlugin; mPlugin = NULL; closeFactory(); } void Drm::writeByteArray(Parcel &obj, Vector const *array) { if (array && array->size()) { obj.writeInt32(array->size()); obj.write(array->array(), array->size()); } else { obj.writeInt32(0); } } } // namespace android