diff options
Diffstat (limited to 'drm')
39 files changed, 3213 insertions, 45 deletions
diff --git a/drm/libdrmframework/Android.mk b/drm/libdrmframework/Android.mk index 49c4f9b..33f9d3b 100644 --- a/drm/libdrmframework/Android.mk +++ b/drm/libdrmframework/Android.mk @@ -19,12 +19,14 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ DrmManagerClientImpl.cpp \ - DrmManagerClient.cpp + DrmManagerClient.cpp \ + NoOpDrmManagerClientImpl.cpp LOCAL_MODULE:= libdrmframework LOCAL_SHARED_LIBRARIES := \ libutils \ + libcutils \ liblog \ libbinder \ libdl diff --git a/drm/libdrmframework/DrmManagerClient.cpp b/drm/libdrmframework/DrmManagerClient.cpp index ea30d01..440dd91 100644 --- a/drm/libdrmframework/DrmManagerClient.cpp +++ b/drm/libdrmframework/DrmManagerClient.cpp @@ -29,7 +29,7 @@ DrmManagerClient::DrmManagerClient(): } DrmManagerClient::~DrmManagerClient() { - DrmManagerClientImpl::remove(mUniqueId); + mDrmManagerClientImpl->remove(mUniqueId); mDrmManagerClientImpl->removeClient(mUniqueId); mDrmManagerClientImpl->setOnInfoListener(mUniqueId, NULL); } diff --git a/drm/libdrmframework/DrmManagerClientImpl.cpp b/drm/libdrmframework/DrmManagerClientImpl.cpp index ffefd74..2d2c90e 100644 --- a/drm/libdrmframework/DrmManagerClientImpl.cpp +++ b/drm/libdrmframework/DrmManagerClientImpl.cpp @@ -21,8 +21,10 @@ #include <utils/String8.h> #include <utils/Vector.h> #include <binder/IServiceManager.h> +#include <cutils/properties.h> #include "DrmManagerClientImpl.h" +#include "NoOpDrmManagerClientImpl.h" using namespace android; @@ -35,9 +37,12 @@ const String8 DrmManagerClientImpl::EMPTY_STRING(""); DrmManagerClientImpl* DrmManagerClientImpl::create( int* pUniqueId, bool isNative) { - *pUniqueId = getDrmManagerService()->addUniqueId(isNative); - - return new DrmManagerClientImpl(); + sp<IDrmManagerService> service = getDrmManagerService(); + if (service != NULL) { + *pUniqueId = getDrmManagerService()->addUniqueId(isNative); + return new DrmManagerClientImpl(); + } + return new NoOpDrmManagerClientImpl(); } void DrmManagerClientImpl::remove(int uniqueId) { @@ -47,6 +52,12 @@ void DrmManagerClientImpl::remove(int uniqueId) { const sp<IDrmManagerService>& DrmManagerClientImpl::getDrmManagerService() { Mutex::Autolock lock(sMutex); if (NULL == sDrmManagerService.get()) { + char value[PROPERTY_VALUE_MAX]; + if (property_get("drm.service.enabled", value, NULL) == 0) { + // Drm is undefined for this device + return sDrmManagerService; + } + sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> binder; do { diff --git a/drm/libdrmframework/NoOpDrmManagerClientImpl.cpp b/drm/libdrmframework/NoOpDrmManagerClientImpl.cpp new file mode 100644 index 0000000..dab583d --- /dev/null +++ b/drm/libdrmframework/NoOpDrmManagerClientImpl.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2014 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. + */ + +#include "NoOpDrmManagerClientImpl.h" + +namespace android { + +void NoOpDrmManagerClientImpl::remove(int uniqueId) { +} + +void NoOpDrmManagerClientImpl::addClient(int uniqueId) { +} + +void NoOpDrmManagerClientImpl::removeClient(int uniqueId) { +} + +status_t NoOpDrmManagerClientImpl::setOnInfoListener( + int uniqueId, const sp<DrmManagerClient::OnInfoListener>& infoListener) { + return UNKNOWN_ERROR; +} + +DrmConstraints* NoOpDrmManagerClientImpl::getConstraints(int uniqueId, const String8* path, const int action) { + return NULL; +} + +DrmMetadata* NoOpDrmManagerClientImpl::getMetadata(int uniqueId, const String8* path) { + return NULL; +} + +bool NoOpDrmManagerClientImpl::canHandle(int uniqueId, const String8& path, const String8& mimeType) { + return false; +} + +DrmInfoStatus* NoOpDrmManagerClientImpl::processDrmInfo(int uniqueId, const DrmInfo* drmInfo) { + return NULL; +} + +DrmInfo* NoOpDrmManagerClientImpl::acquireDrmInfo(int uniqueId, const DrmInfoRequest* drmInfoRequest) { + return NULL; +} + +status_t NoOpDrmManagerClientImpl::saveRights(int uniqueId, const DrmRights& drmRights, + const String8& rightsPath, const String8& contentPath) { + return UNKNOWN_ERROR; +} + +String8 NoOpDrmManagerClientImpl::getOriginalMimeType(int uniqueId, const String8& path, int fd) { + return String8(); +} + +int NoOpDrmManagerClientImpl::getDrmObjectType(int uniqueId, const String8& path, const String8& mimeType) { + return -1; +} + +int NoOpDrmManagerClientImpl::checkRightsStatus(int uniqueId, const String8& path, int action) { + return -1; +} + +status_t NoOpDrmManagerClientImpl::consumeRights(int uniqueId, sp<DecryptHandle> &decryptHandle, int action, bool reserve) { + return UNKNOWN_ERROR; +} + +status_t NoOpDrmManagerClientImpl::setPlaybackStatus( + int uniqueId, sp<DecryptHandle> &decryptHandle, int playbackStatus, int64_t position) { + return UNKNOWN_ERROR; +} + +bool NoOpDrmManagerClientImpl::validateAction( + int uniqueId, const String8& path, int action, const ActionDescription& description) { + return false; +} + +status_t NoOpDrmManagerClientImpl::removeRights(int uniqueId, const String8& path) { + return UNKNOWN_ERROR; +} + +status_t NoOpDrmManagerClientImpl::removeAllRights(int uniqueId) { + return UNKNOWN_ERROR; +} + +int NoOpDrmManagerClientImpl::openConvertSession(int uniqueId, const String8& mimeType) { + return -1; +} + +DrmConvertedStatus* NoOpDrmManagerClientImpl::convertData(int uniqueId, int convertId, const DrmBuffer* inputData) { + return NULL; +} + +DrmConvertedStatus* NoOpDrmManagerClientImpl::closeConvertSession(int uniqueId, int convertId) { + return NULL; +} + +status_t NoOpDrmManagerClientImpl::getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray) { + return UNKNOWN_ERROR; +} + +sp<DecryptHandle> NoOpDrmManagerClientImpl::openDecryptSession( + int uniqueId, int fd, off64_t offset, off64_t length, const char* mime) { + return NULL; +} + +sp<DecryptHandle> NoOpDrmManagerClientImpl::openDecryptSession( + int uniqueId, const char* uri, const char* mime) { + return NULL; +} + +sp<DecryptHandle> NoOpDrmManagerClientImpl::openDecryptSession(int uniqueId, const DrmBuffer& buf, + const String8& mimeType) { + return NULL; +} + +status_t NoOpDrmManagerClientImpl::closeDecryptSession(int uniqueId, sp<DecryptHandle> &decryptHandle) { + return UNKNOWN_ERROR; +} + +status_t NoOpDrmManagerClientImpl::initializeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, + int decryptUnitId, const DrmBuffer* headerInfo) { + return UNKNOWN_ERROR; +} + +status_t NoOpDrmManagerClientImpl::decrypt(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId, + const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) { + return UNKNOWN_ERROR; +} + +status_t NoOpDrmManagerClientImpl::finalizeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId) { + return UNKNOWN_ERROR; +} + +ssize_t NoOpDrmManagerClientImpl::pread(int uniqueId, sp<DecryptHandle> &decryptHandle, + void* buffer, ssize_t numBytes, off64_t offset) { + return -1; +} + +status_t NoOpDrmManagerClientImpl::notify(const DrmInfoEvent& event) { + return UNKNOWN_ERROR; +} + +} diff --git a/drm/libdrmframework/include/DrmManagerClientImpl.h b/drm/libdrmframework/include/DrmManagerClientImpl.h index 3400cb1..3858675 100644 --- a/drm/libdrmframework/include/DrmManagerClientImpl.h +++ b/drm/libdrmframework/include/DrmManagerClientImpl.h @@ -34,30 +34,30 @@ class DrmInfoEvent; * */ class DrmManagerClientImpl : public BnDrmServiceListener { -private: +protected: DrmManagerClientImpl() { } public: static DrmManagerClientImpl* create(int* pUniqueId, bool isNative); - static void remove(int uniqueId); - virtual ~DrmManagerClientImpl() { } public: + virtual void remove(int uniqueId); + /** * Adds the client respective to given unique id. * * @param[in] uniqueId Unique identifier for a session */ - void addClient(int uniqueId); + virtual void addClient(int uniqueId); /** * Removes the client respective to given unique id. * * @param[in] uniqueId Unique identifier for a session */ - void removeClient(int uniqueId); + virtual void removeClient(int uniqueId); /** * Register a callback to be invoked when the caller required to @@ -68,7 +68,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t setOnInfoListener( + virtual status_t setOnInfoListener( int uniqueId, const sp<DrmManagerClient::OnInfoListener>& infoListener); /** @@ -83,7 +83,7 @@ public: * @note * In case of error, return NULL */ - DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); + virtual DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); /** * Get metadata information associated with input content. @@ -95,7 +95,7 @@ public: * @note * In case of error, return NULL */ - DrmMetadata* getMetadata(int uniqueId, const String8* path); + virtual DrmMetadata* getMetadata(int uniqueId, const String8* path); /** * Check whether the given mimetype or path can be handled @@ -106,7 +106,7 @@ public: * @return * True if DrmManager can handle given path or mime type. */ - bool canHandle(int uniqueId, const String8& path, const String8& mimeType); + virtual bool canHandle(int uniqueId, const String8& path, const String8& mimeType); /** * Executes given drm information based on its type @@ -116,7 +116,7 @@ public: * @return DrmInfoStatus * instance as a result of processing given input */ - DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); + virtual DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); /** * Retrieves necessary information for registration, unregistration or rights @@ -127,7 +127,7 @@ public: * @return DrmInfo * instance as a result of processing given input */ - DrmInfo* acquireDrmInfo(int uniqueId, const DrmInfoRequest* drmInfoRequest); + virtual DrmInfo* acquireDrmInfo(int uniqueId, const DrmInfoRequest* drmInfoRequest); /** * Save DRM rights to specified rights path @@ -140,7 +140,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t saveRights(int uniqueId, const DrmRights& drmRights, + virtual status_t saveRights(int uniqueId, const DrmRights& drmRights, const String8& rightsPath, const String8& contentPath); /** @@ -152,7 +152,7 @@ public: * @return String8 * Returns mime-type of the original content, such as "video/mpeg" */ - String8 getOriginalMimeType(int uniqueId, const String8& path, int fd); + virtual String8 getOriginalMimeType(int uniqueId, const String8& path, int fd); /** * Retrieves the type of the protected object (content, rights, etc..) @@ -165,7 +165,7 @@ public: * @return type of the DRM content, * such as DrmObjectType::CONTENT, DrmObjectType::RIGHTS_OBJECT */ - int getDrmObjectType(int uniqueId, const String8& path, const String8& mimeType); + virtual int getDrmObjectType(int uniqueId, const String8& path, const String8& mimeType); /** * Check whether the given content has valid rights or not @@ -176,7 +176,7 @@ public: * @return the status of the rights for the protected content, * such as RightsStatus::RIGHTS_VALID, RightsStatus::RIGHTS_EXPIRED, etc. */ - int checkRightsStatus(int uniqueId, const String8& path, int action); + virtual int checkRightsStatus(int uniqueId, const String8& path, int action); /** * Consumes the rights for a content. @@ -190,7 +190,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t consumeRights(int uniqueId, sp<DecryptHandle> &decryptHandle, int action, bool reserve); + virtual status_t consumeRights(int uniqueId, sp<DecryptHandle> &decryptHandle, int action, bool reserve); /** * Informs the DRM engine about the playback actions performed on the DRM files. @@ -203,7 +203,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t setPlaybackStatus( + virtual status_t setPlaybackStatus( int uniqueId, sp<DecryptHandle> &decryptHandle, int playbackStatus, int64_t position); /** @@ -215,7 +215,7 @@ public: * @param[in] description Detailed description of the action * @return true if the action is allowed. */ - bool validateAction( + virtual bool validateAction( int uniqueId, const String8& path, int action, const ActionDescription& description); /** @@ -226,7 +226,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t removeRights(int uniqueId, const String8& path); + virtual status_t removeRights(int uniqueId, const String8& path); /** * Removes all the rights information of each plug-in associated with @@ -236,7 +236,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t removeAllRights(int uniqueId); + virtual status_t removeAllRights(int uniqueId); /** * This API is for Forward Lock based DRM scheme. @@ -248,7 +248,7 @@ public: * @param[in] mimeType Description/MIME type of the input data packet * @return Return handle for the convert session */ - int openConvertSession(int uniqueId, const String8& mimeType); + virtual int openConvertSession(int uniqueId, const String8& mimeType); /** * Accepts and converts the input data which is part of DRM file. @@ -263,7 +263,7 @@ public: * the output converted data and offset. In this case the * application will ignore the offset information. */ - DrmConvertedStatus* convertData(int uniqueId, int convertId, const DrmBuffer* inputData); + virtual DrmConvertedStatus* convertData(int uniqueId, int convertId, const DrmBuffer* inputData); /** * Informs the Drm Agent when there is no more data which need to be converted @@ -279,7 +279,7 @@ public: * the application on which offset these signature data * should be appended. */ - DrmConvertedStatus* closeConvertSession(int uniqueId, int convertId); + virtual DrmConvertedStatus* closeConvertSession(int uniqueId, int convertId); /** * Retrieves all DrmSupportInfo instance that native DRM framework can handle. @@ -292,7 +292,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); + virtual status_t getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); /** * Open the decrypt session to decrypt the given protected content @@ -305,7 +305,7 @@ public: * @return * Handle for the decryption session */ - sp<DecryptHandle> openDecryptSession( + virtual sp<DecryptHandle> openDecryptSession( int uniqueId, int fd, off64_t offset, off64_t length, const char* mime); /** @@ -317,7 +317,7 @@ public: * @return * Handle for the decryption session */ - sp<DecryptHandle> openDecryptSession( + virtual sp<DecryptHandle> openDecryptSession( int uniqueId, const char* uri, const char* mime); /** @@ -329,7 +329,7 @@ public: * @return * Handle for the decryption session */ - sp<DecryptHandle> openDecryptSession(int uniqueId, const DrmBuffer& buf, + virtual sp<DecryptHandle> openDecryptSession(int uniqueId, const DrmBuffer& buf, const String8& mimeType); /** @@ -340,7 +340,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t closeDecryptSession(int uniqueId, sp<DecryptHandle> &decryptHandle); + virtual status_t closeDecryptSession(int uniqueId, sp<DecryptHandle> &decryptHandle); /** * Initialize decryption for the given unit of the protected content @@ -352,7 +352,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, + virtual status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo); /** @@ -372,7 +372,7 @@ public: * DRM_ERROR_SESSION_NOT_OPENED, DRM_ERROR_DECRYPT_UNIT_NOT_INITIALIZED, * DRM_ERROR_DECRYPT for failure. */ - status_t decrypt(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId, + virtual status_t decrypt(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV); /** @@ -384,7 +384,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t finalizeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId); + virtual status_t finalizeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId); /** * Reads the specified number of bytes from an open DRM file. @@ -397,7 +397,7 @@ public: * * @return Number of bytes read. Returns -1 for Failure. */ - ssize_t pread(int uniqueId, sp<DecryptHandle> &decryptHandle, + virtual ssize_t pread(int uniqueId, sp<DecryptHandle> &decryptHandle, void* buffer, ssize_t numBytes, off64_t offset); /** @@ -407,7 +407,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t notify(const DrmInfoEvent& event); + virtual status_t notify(const DrmInfoEvent& event); private: Mutex mLock; diff --git a/drm/libdrmframework/include/NoOpDrmManagerClientImpl.h b/drm/libdrmframework/include/NoOpDrmManagerClientImpl.h new file mode 100644 index 0000000..e8e8f42 --- /dev/null +++ b/drm/libdrmframework/include/NoOpDrmManagerClientImpl.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef __NO_OP_DRM_MANAGER_CLIENT_IMPL_H__ +#define __NO_OP_DRM_MANAGER_CLIENT_IMPL_H__ + +#include "DrmManagerClientImpl.h" + +namespace android { + +class NoOpDrmManagerClientImpl : public DrmManagerClientImpl { +public: + NoOpDrmManagerClientImpl() { } + + void remove(int uniqueId); + void addClient(int uniqueId); + void removeClient(int uniqueId); + status_t setOnInfoListener( + int uniqueId, const sp<DrmManagerClient::OnInfoListener>& infoListener); + DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); + + DrmMetadata* getMetadata(int uniqueId, const String8* path); + bool canHandle(int uniqueId, const String8& path, const String8& mimeType); + DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); + DrmInfo* acquireDrmInfo(int uniqueId, const DrmInfoRequest* drmInfoRequest); + status_t saveRights(int uniqueId, const DrmRights& drmRights, + const String8& rightsPath, const String8& contentPath); + String8 getOriginalMimeType(int uniqueId, const String8& path, int fd); + int getDrmObjectType(int uniqueId, const String8& path, const String8& mimeType); + int checkRightsStatus(int uniqueId, const String8& path, int action); + status_t consumeRights(int uniqueId, sp<DecryptHandle> &decryptHandle, int action, bool reserve); + status_t setPlaybackStatus( + int uniqueId, sp<DecryptHandle> &decryptHandle, int playbackStatus, int64_t position); + bool validateAction( + int uniqueId, const String8& path, int action, const ActionDescription& description); + status_t removeRights(int uniqueId, const String8& path); + status_t removeAllRights(int uniqueId); + int openConvertSession(int uniqueId, const String8& mimeType); + DrmConvertedStatus* convertData(int uniqueId, int convertId, const DrmBuffer* inputData); + DrmConvertedStatus* closeConvertSession(int uniqueId, int convertId); + status_t getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); + sp<DecryptHandle> openDecryptSession( + int uniqueId, int fd, off64_t offset, off64_t length, const char* mime); + sp<DecryptHandle> openDecryptSession( + int uniqueId, const char* uri, const char* mime); + sp<DecryptHandle> openDecryptSession(int uniqueId, const DrmBuffer& buf, + const String8& mimeType); + status_t closeDecryptSession(int uniqueId, sp<DecryptHandle> &decryptHandle); + status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, + int decryptUnitId, const DrmBuffer* headerInfo); + status_t decrypt(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId, + const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV); + status_t finalizeDecryptUnit(int uniqueId, sp<DecryptHandle> &decryptHandle, int decryptUnitId); + ssize_t pread(int uniqueId, sp<DecryptHandle> &decryptHandle, + void* buffer, ssize_t numBytes, off64_t offset); + status_t notify(const DrmInfoEvent& event); +}; + +} + +#endif // __NO_OP_DRM_MANAGER_CLIENT_IMPL_H diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp index 234aef2..f400732 100644 --- a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp @@ -316,6 +316,7 @@ String8 FwdLockEngine::onGetOriginalMimeType(int uniqueId, const String8& path, if (-1 < fileDesc) { if (FwdLockFile_attach(fileDesc) < 0) { + close(fileDesc); return mimeString; } const char* pMimeType = FwdLockFile_GetContentType(fileDesc); diff --git a/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.cpp b/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.cpp new file mode 100644 index 0000000..01f8d65 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 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 "ClearKeyCryptoPlugin" +#include <utils/Log.h> + +#include <openssl/aes.h> + +#include "AesCtrDecryptor.h" + +namespace clearkeydrm { + +static const size_t kBlockBitCount = kBlockSize * 8; + +android::status_t AesCtrDecryptor::decrypt(const android::Vector<uint8_t>& key, + const Iv iv, const uint8_t* source, + uint8_t* destination, + const SubSample* subSamples, + size_t numSubSamples, + size_t* bytesDecryptedOut) { + uint32_t blockOffset = 0; + uint8_t previousEncryptedCounter[kBlockSize]; + memset(previousEncryptedCounter, 0, kBlockSize); + + size_t offset = 0; + AES_KEY opensslKey; + AES_set_encrypt_key(key.array(), kBlockBitCount, &opensslKey); + Iv opensslIv; + memcpy(opensslIv, iv, sizeof(opensslIv)); + + for (size_t i = 0; i < numSubSamples; ++i) { + const SubSample& subSample = subSamples[i]; + + if (subSample.mNumBytesOfClearData > 0) { + memcpy(destination + offset, source + offset, + subSample.mNumBytesOfClearData); + offset += subSample.mNumBytesOfClearData; + } + + if (subSample.mNumBytesOfEncryptedData > 0) { + AES_ctr128_encrypt(source + offset, destination + offset, + subSample.mNumBytesOfEncryptedData, &opensslKey, + opensslIv, previousEncryptedCounter, + &blockOffset); + offset += subSample.mNumBytesOfEncryptedData; + } + } + + *bytesDecryptedOut = offset; + return android::OK; +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.h b/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.h new file mode 100644 index 0000000..b416266 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_AES_CTR_DECRYPTOR_H_ +#define CLEARKEY_AES_CTR_DECRYPTOR_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <Utils.h> +#include <utils/Errors.h> +#include <utils/Vector.h> + +#include "ClearKeyTypes.h" + +namespace clearkeydrm { + +class AesCtrDecryptor { +public: + AesCtrDecryptor() {} + + android::status_t decrypt(const android::Vector<uint8_t>& key, const Iv iv, + const uint8_t* source, uint8_t* destination, + const SubSample* subSamples, size_t numSubSamples, + size_t* bytesDecryptedOut); + +private: + DISALLOW_EVIL_CONSTRUCTORS(AesCtrDecryptor); +}; + +} // namespace clearkeydrm + +#endif // CLEARKEY_AES_CTR_DECRYPTOR_H_ diff --git a/drm/mediadrm/plugins/clearkey/Android.mk b/drm/mediadrm/plugins/clearkey/Android.mk new file mode 100644 index 0000000..22a85b4 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/Android.mk @@ -0,0 +1,62 @@ +# +# Copyright (C) 2014 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. +# +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + AesCtrDecryptor.cpp \ + ClearKeyUUID.cpp \ + CreatePluginFactories.cpp \ + CryptoFactory.cpp \ + CryptoPlugin.cpp \ + DrmFactory.cpp \ + DrmPlugin.cpp \ + InitDataParser.cpp \ + JsonWebKey.cpp \ + Session.cpp \ + SessionLibrary.cpp \ + Utils.cpp \ + +LOCAL_C_INCLUDES := \ + bionic \ + external/jsmn \ + external/openssl/include \ + frameworks/av/drm/mediadrm/plugins/clearkey \ + frameworks/av/include \ + frameworks/native/include \ + +LOCAL_MODULE := libdrmclearkeyplugin + +LOCAL_PROPRIETARY_MODULE := true +LOCAL_MODULE_RELATIVE_PATH := mediadrm + +LOCAL_SHARED_LIBRARIES := \ + libcrypto \ + liblog \ + libstagefright_foundation \ + libutils \ + +LOCAL_STATIC_LIBRARIES := \ + libjsmn \ + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_SHARED_LIBRARY) + +######################################################################### +# Build unit tests + +include $(LOCAL_PATH)/tests/Android.mk diff --git a/drm/mediadrm/plugins/clearkey/ClearKeyTypes.h b/drm/mediadrm/plugins/clearkey/ClearKeyTypes.h new file mode 100644 index 0000000..a28959a --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/ClearKeyTypes.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_TYPES_H_ +#define CLEARKEY_TYPES_H_ + +#include <media/hardware/CryptoAPI.h> +#include <openssl/aes.h> +#include <utils/KeyedVector.h> +#include <utils/Vector.h> + +namespace clearkeydrm { + +const uint8_t kBlockSize = AES_BLOCK_SIZE; +typedef uint8_t KeyId[kBlockSize]; +typedef uint8_t Iv[kBlockSize]; + +typedef android::CryptoPlugin::SubSample SubSample; + +typedef android::KeyedVector<android::Vector<uint8_t>, + android::Vector<uint8_t> > KeyMap; + +} // namespace clearkeydrm + +#endif // CLEARKEY_TYPES_H_ diff --git a/drm/mediadrm/plugins/clearkey/ClearKeyUUID.cpp b/drm/mediadrm/plugins/clearkey/ClearKeyUUID.cpp new file mode 100644 index 0000000..ed050f7 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/ClearKeyUUID.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 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. + */ + +#include <string.h> + +#include "ClearKeyUUID.h" + +namespace clearkeydrm { + +bool isClearKeyUUID(const uint8_t uuid[16]) { + static const uint8_t kClearKeyUUID[16] = { + 0x10,0x77,0xEF,0xEC,0xC0,0xB2,0x4D,0x02, + 0xAC,0xE3,0x3C,0x1E,0x52,0xE2,0xFB,0x4B + }; + + return !memcmp(uuid, kClearKeyUUID, sizeof(kClearKeyUUID)); +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/ClearKeyUUID.h b/drm/mediadrm/plugins/clearkey/ClearKeyUUID.h new file mode 100644 index 0000000..ac99418 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/ClearKeyUUID.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_UUID_H_ +#define CLEARKEY_UUID_H_ + +#include <stdint.h> + +namespace clearkeydrm { + +bool isClearKeyUUID(const uint8_t uuid[16]); + +} // namespace clearkeydrm + +#endif // CLEARKEY_UUID_H_ diff --git a/drm/mediadrm/plugins/clearkey/CreatePluginFactories.cpp b/drm/mediadrm/plugins/clearkey/CreatePluginFactories.cpp new file mode 100644 index 0000000..ec1420e --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/CreatePluginFactories.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 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. + */ + +#include "CreatePluginFactories.h" + +#include "CryptoFactory.h" +#include "DrmFactory.h" + +extern "C" { + +android::DrmFactory* createDrmFactory() { + return new clearkeydrm::DrmFactory(); +} + +android::CryptoFactory* createCryptoFactory() { + return new clearkeydrm::CryptoFactory(); +} + +} // extern "C" diff --git a/drm/mediadrm/plugins/clearkey/CreatePluginFactories.h b/drm/mediadrm/plugins/clearkey/CreatePluginFactories.h new file mode 100644 index 0000000..d9acec1 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/CreatePluginFactories.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_CREATE_PLUGIN_FACTORIES_H_ +#define CLEARKEY_CREATE_PLUGIN_FACTORIES_H_ + +#include <media/drm/DrmAPI.h> +#include <media/hardware/CryptoAPI.h> + +extern "C" { + android::DrmFactory* createDrmFactory(); + android::CryptoFactory* createCryptoFactory(); +} + +#endif // CLEARKEY_CREATE_PLUGIN_FACTORIES_H_ diff --git a/drm/mediadrm/plugins/clearkey/CryptoFactory.cpp b/drm/mediadrm/plugins/clearkey/CryptoFactory.cpp new file mode 100644 index 0000000..ee3189b --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/CryptoFactory.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 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 "ClearKeyCryptoPlugin" +#include <utils/Log.h> + +#include <utils/Errors.h> +#include <utils/StrongPointer.h> + +#include "CryptoFactory.h" + +#include "ClearKeyUUID.h" +#include "CryptoPlugin.h" +#include "Session.h" +#include "SessionLibrary.h" + +namespace clearkeydrm { + +bool CryptoFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) const { + return isClearKeyUUID(uuid); +} + +android::status_t CryptoFactory::createPlugin( + const uint8_t uuid[16], + const void* data, size_t size, + android::CryptoPlugin** plugin) { + if (!isCryptoSchemeSupported(uuid)) { + *plugin = NULL; + return android::BAD_VALUE; + } + + android::sp<Session> session = SessionLibrary::get()->findSession( + data, size); + *plugin = new CryptoPlugin(session); + return android::OK; +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/CryptoFactory.h b/drm/mediadrm/plugins/clearkey/CryptoFactory.h new file mode 100644 index 0000000..568bc4b --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/CryptoFactory.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_CRYPTO_FACTORY_H_ +#define CLEARKEY_CRYPTO_FACTORY_H_ + +#include <media/hardware/CryptoAPI.h> +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> + +namespace clearkeydrm { + +class CryptoFactory : public android::CryptoFactory { +public: + CryptoFactory() {} + virtual ~CryptoFactory() {} + + virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]) const; + + virtual android::status_t createPlugin( + const uint8_t uuid[16], + const void* data, size_t size, + android::CryptoPlugin** plugin); + +private: + DISALLOW_EVIL_CONSTRUCTORS(CryptoFactory); +}; + +} // namespace clearkeydrm + +#endif // CLEARKEY_CRYPTO_FACTORY_H_ diff --git a/drm/mediadrm/plugins/clearkey/CryptoPlugin.cpp b/drm/mediadrm/plugins/clearkey/CryptoPlugin.cpp new file mode 100644 index 0000000..adad136 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/CryptoPlugin.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2014 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 "ClearKeyCryptoPlugin" +#include <utils/Log.h> + +#include <media/stagefright/MediaErrors.h> +#include <utils/Errors.h> + +#include "CryptoPlugin.h" + +namespace clearkeydrm { + +using android::Vector; +using android::AString; +using android::status_t; + +// Returns negative values for error code and positive values for the size of +// decrypted data. In theory, the output size can be larger than the input +// size, but in practice this will never happen for AES-CTR. +ssize_t CryptoPlugin::decrypt(bool secure, const KeyId keyId, const Iv iv, + Mode mode, const void* srcPtr, + const SubSample* subSamples, size_t numSubSamples, + void* dstPtr, AString* errorDetailMsg) { + if (secure) { + errorDetailMsg->setTo("Secure decryption is not supported with " + "ClearKey."); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + if (mode == kMode_Unencrypted) { + size_t offset = 0; + for (size_t i = 0; i < numSubSamples; ++i) { + const SubSample& subSample = subSamples[i]; + + if (subSample.mNumBytesOfEncryptedData != 0) { + errorDetailMsg->setTo( + "Encrypted subsamples found in allegedly unencrypted " + "data."); + return android::ERROR_DRM_DECRYPT; + } + + if (subSample.mNumBytesOfClearData != 0) { + memcpy(reinterpret_cast<uint8_t*>(dstPtr) + offset, + reinterpret_cast<const uint8_t*>(srcPtr) + offset, + subSample.mNumBytesOfClearData); + offset += subSample.mNumBytesOfClearData; + } + } + return static_cast<ssize_t>(offset); + } else if (mode == kMode_AES_CTR) { + size_t bytesDecrypted; + status_t res = mSession->decrypt(keyId, iv, srcPtr, dstPtr, subSamples, + numSubSamples, &bytesDecrypted); + if (res == android::OK) { + return static_cast<ssize_t>(bytesDecrypted); + } else { + errorDetailMsg->setTo("Decryption Error"); + return static_cast<ssize_t>(res); + } + } else { + errorDetailMsg->setTo( + "Selected encryption mode is not supported by the ClearKey DRM " + "Plugin."); + return android::ERROR_DRM_CANNOT_HANDLE; + } +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/CryptoPlugin.h b/drm/mediadrm/plugins/clearkey/CryptoPlugin.h new file mode 100644 index 0000000..002d9e0 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/CryptoPlugin.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_CRYPTO_PLUGIN_H_ +#define CLEARKEY_CRYPTO_PLUGIN_H_ + +#include <media/hardware/CryptoAPI.h> +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/foundation/AString.h> +#include <utils/Errors.h> +#include <utils/StrongPointer.h> + +#include "ClearKeyTypes.h" +#include "Session.h" +#include "Utils.h" + +namespace clearkeydrm { + +class CryptoPlugin : public android::CryptoPlugin { +public: + CryptoPlugin(const android::sp<Session>& session) : mSession(session) {} + virtual ~CryptoPlugin() {} + + virtual bool requiresSecureDecoderComponent(const char* mime) const { + UNUSED(mime); + return false; + } + + virtual ssize_t decrypt( + bool secure, const KeyId keyId, const Iv iv, + Mode mode, const void* srcPtr, + const SubSample* subSamples, size_t numSubSamples, + void* dstPtr, android::AString* errorDetailMsg); + +private: + DISALLOW_EVIL_CONSTRUCTORS(CryptoPlugin); + + android::sp<Session> mSession; +}; + +} // namespace clearkeydrm + +#endif // CLEARKEY_CRYPTO_PLUGIN_H_ diff --git a/drm/mediadrm/plugins/clearkey/DrmFactory.cpp b/drm/mediadrm/plugins/clearkey/DrmFactory.cpp new file mode 100644 index 0000000..40275cf --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/DrmFactory.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 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 "ClearKeyCryptoPlugin" +#include <utils/Log.h> + +#include <utils/Errors.h> + +#include "DrmFactory.h" + +#include "DrmPlugin.h" +#include "ClearKeyUUID.h" +#include "SessionLibrary.h" + +namespace clearkeydrm { + +bool DrmFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) { + return isClearKeyUUID(uuid); +} + +bool DrmFactory::isContentTypeSupported(const android::String8 &initDataType) { + // This should match the types handed by InitDataParser. + return initDataType == "cenc" || + initDataType == "webm"; +} + +android::status_t DrmFactory::createDrmPlugin( + const uint8_t uuid[16], android::DrmPlugin** plugin) { + if (!isCryptoSchemeSupported(uuid)) { + *plugin = NULL; + return android::BAD_VALUE; + } + + *plugin = new DrmPlugin(SessionLibrary::get()); + return android::OK; +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/DrmFactory.h b/drm/mediadrm/plugins/clearkey/DrmFactory.h new file mode 100644 index 0000000..164d3d0 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/DrmFactory.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_DRM_FACTORY_H_ +#define CLEARKEY_DRM_FACTORY_H_ + +#include <media/drm/DrmAPI.h> +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> + +#include "Utils.h" + +namespace clearkeydrm { + +class DrmFactory : public android::DrmFactory { +public: + DrmFactory() {} + virtual ~DrmFactory() {} + + virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]); + + virtual bool isContentTypeSupported(const android::String8 &initDataType); + + virtual android::status_t createDrmPlugin( + const uint8_t uuid[16], android::DrmPlugin** plugin); + +private: + DISALLOW_EVIL_CONSTRUCTORS(DrmFactory); +}; + +} // namespace clearkeydrm + +#endif // CLEARKEY_DRM_FACTORY_H_ diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp new file mode 100644 index 0000000..96fca94 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014 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 "ClearKeyCryptoPlugin" +#include <utils/Log.h> + +#include <media/stagefright/MediaErrors.h> +#include <utils/StrongPointer.h> + +#include "DrmPlugin.h" + +#include "Session.h" + +namespace clearkeydrm { + +using android::sp; + +status_t DrmPlugin::openSession(Vector<uint8_t>& sessionId) { + sp<Session> session = mSessionLibrary->createSession(); + sessionId = session->sessionId(); + return android::OK; +} + +status_t DrmPlugin::closeSession(const Vector<uint8_t>& sessionId) { + sp<Session> session = mSessionLibrary->findSession(sessionId); + mSessionLibrary->destroySession(session); + return android::OK; +} + +status_t DrmPlugin::getKeyRequest( + const Vector<uint8_t>& scope, + const Vector<uint8_t>& initData, + const String8& initDataType, + KeyType keyType, + const KeyedVector<String8, String8>& optionalParameters, + Vector<uint8_t>& request, + String8& defaultUrl) { + UNUSED(optionalParameters); + if (keyType != kKeyType_Streaming) { + return android::ERROR_DRM_CANNOT_HANDLE; + } + + sp<Session> session = mSessionLibrary->findSession(scope); + defaultUrl.clear(); + return session->getKeyRequest(initData, initDataType, &request); +} + +status_t DrmPlugin::provideKeyResponse( + const Vector<uint8_t>& scope, + const Vector<uint8_t>& response, + Vector<uint8_t>& keySetId) { + sp<Session> session = mSessionLibrary->findSession(scope); + status_t res = session->provideKeyResponse(response); + if (res == android::OK) { + keySetId.clear(); + } + return res; +} + +status_t DrmPlugin::getPropertyString( + const String8& name, String8& value) const { + if (name == "vendor") { + value = "Google"; + } else if (name == "version") { + value = "1.0"; + } else if (name == "description") { + value = "ClearKey CDM"; + } else if (name == "algorithms") { + value = ""; + } else { + ALOGE("App requested unknown string property %s", name.string()); + return android::ERROR_DRM_CANNOT_HANDLE; + } + return android::OK; +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/DrmPlugin.h new file mode 100644 index 0000000..bfbc6bf --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/DrmPlugin.h @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_DRM_PLUGIN_H_ +#define CLEARKEY_DRM_PLUGIN_H_ + +#include <media/drm/DrmAPI.h> +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/MediaErrors.h> +#include <utils/Errors.h> +#include <utils/KeyedVector.h> +#include <utils/List.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +#include "SessionLibrary.h" +#include "Utils.h" + +namespace clearkeydrm { + +using android::KeyedVector; +using android::List; +using android::status_t; +using android::String8; +using android::Vector; + +class DrmPlugin : public android::DrmPlugin { +public: + DrmPlugin(SessionLibrary* sessionLibrary) + : mSessionLibrary(sessionLibrary) {} + virtual ~DrmPlugin() {} + + virtual status_t openSession(Vector<uint8_t>& sessionId); + + virtual status_t closeSession(const Vector<uint8_t>& sessionId); + + virtual status_t getKeyRequest( + const Vector<uint8_t>& scope, + const Vector<uint8_t>& initData, + const String8& initDataType, + KeyType keyType, + const KeyedVector<String8, String8>& optionalParameters, + Vector<uint8_t>& request, + String8& defaultUrl); + + virtual status_t provideKeyResponse( + const Vector<uint8_t>& scope, + const Vector<uint8_t>& response, + Vector<uint8_t>& keySetId); + + virtual status_t removeKeys(const Vector<uint8_t>& sessionId) { + UNUSED(sessionId); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t restoreKeys( + const Vector<uint8_t>& sessionId, + const Vector<uint8_t>& keySetId) { + UNUSED(sessionId); + UNUSED(keySetId); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t queryKeyStatus( + const Vector<uint8_t>& sessionId, + KeyedVector<String8, String8>& infoMap) const { + UNUSED(sessionId); + UNUSED(infoMap); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t getProvisionRequest( + const String8& cert_type, + const String8& cert_authority, + Vector<uint8_t>& request, + String8& defaultUrl) { + UNUSED(cert_type); + UNUSED(cert_authority); + UNUSED(request); + UNUSED(defaultUrl); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t provideProvisionResponse( + const Vector<uint8_t>& response, + Vector<uint8_t>& certificate, + Vector<uint8_t>& wrappedKey) { + UNUSED(response); + UNUSED(certificate); + UNUSED(wrappedKey); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t getSecureStops(List<Vector<uint8_t> >& secureStops) { + UNUSED(secureStops); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t releaseSecureStops(const Vector<uint8_t>& ssRelease) { + UNUSED(ssRelease); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t getPropertyString( + const String8& name, String8& value) const; + + virtual status_t getPropertyByteArray( + const String8& name, Vector<uint8_t>& value) const { + UNUSED(name); + UNUSED(value); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t setPropertyString( + const String8& name, const String8& value) { + UNUSED(name); + UNUSED(value); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t setPropertyByteArray( + const String8& name, const Vector<uint8_t>& value) { + UNUSED(name); + UNUSED(value); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t setCipherAlgorithm( + const Vector<uint8_t>& sessionId, const String8& algorithm) { + UNUSED(sessionId); + UNUSED(algorithm); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t setMacAlgorithm( + const Vector<uint8_t>& sessionId, const String8& algorithm) { + UNUSED(sessionId); + UNUSED(algorithm); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t encrypt( + const Vector<uint8_t>& sessionId, + const Vector<uint8_t>& keyId, + const Vector<uint8_t>& input, + const Vector<uint8_t>& iv, + Vector<uint8_t>& output) { + UNUSED(sessionId); + UNUSED(keyId); + UNUSED(input); + UNUSED(iv); + UNUSED(output); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t decrypt( + const Vector<uint8_t>& sessionId, + const Vector<uint8_t>& keyId, + const Vector<uint8_t>& input, + const Vector<uint8_t>& iv, + Vector<uint8_t>& output) { + UNUSED(sessionId); + UNUSED(keyId); + UNUSED(input); + UNUSED(iv); + UNUSED(output); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t sign( + const Vector<uint8_t>& sessionId, + const Vector<uint8_t>& keyId, + const Vector<uint8_t>& message, + Vector<uint8_t>& signature) { + UNUSED(sessionId); + UNUSED(keyId); + UNUSED(message); + UNUSED(signature); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t verify( + const Vector<uint8_t>& sessionId, + const Vector<uint8_t>& keyId, + const Vector<uint8_t>& message, + const Vector<uint8_t>& signature, bool& match) { + UNUSED(sessionId); + UNUSED(keyId); + UNUSED(message); + UNUSED(signature); + UNUSED(match); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + virtual status_t signRSA( + const Vector<uint8_t>& sessionId, + const String8& algorithm, + const Vector<uint8_t>& message, + const Vector<uint8_t>& wrappedKey, + Vector<uint8_t>& signature) { + UNUSED(sessionId); + UNUSED(algorithm); + UNUSED(message); + UNUSED(wrappedKey); + UNUSED(signature); + return android::ERROR_DRM_CANNOT_HANDLE; + } + +private: + DISALLOW_EVIL_CONSTRUCTORS(DrmPlugin); + + SessionLibrary* mSessionLibrary; +}; + +} // namespace clearkeydrm + +#endif // CLEARKEY_DRM_PLUGIN_H_ diff --git a/drm/mediadrm/plugins/clearkey/InitDataParser.cpp b/drm/mediadrm/plugins/clearkey/InitDataParser.cpp new file mode 100644 index 0000000..c22d73a --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/InitDataParser.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2014 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 "ClearKeyCryptoPlugin" +#include <utils/Log.h> + +#include <endian.h> +#include <media/stagefright/foundation/AString.h> +#include <media/stagefright/foundation/base64.h> +#include <media/stagefright/MediaErrors.h> +#include <string.h> + +#include "InitDataParser.h" + +#include "ClearKeyUUID.h" +#include "Utils.h" + +namespace clearkeydrm { + +using android::AString; +using android::String8; +using android::Vector; + +namespace { + const size_t kKeyIdSize = 16; + const size_t kSystemIdSize = 16; +} + +android::status_t InitDataParser::parse(const Vector<uint8_t>& initData, + const String8& initDataType, + Vector<uint8_t>* licenseRequest) { + // Build a list of the key IDs + Vector<const uint8_t*> keyIds; + if (initDataType == "cenc") { + android::status_t res = parsePssh(initData, &keyIds); + if (res != android::OK) { + return res; + } + } else if (initDataType == "webm") { + // WebM "init data" is just a single key ID + if (initData.size() != kKeyIdSize) { + return android::ERROR_DRM_CANNOT_HANDLE; + } + keyIds.push(initData.array()); + } else { + return android::ERROR_DRM_CANNOT_HANDLE; + } + + // Build the request + String8 requestJson = generateRequest(keyIds); + licenseRequest->clear(); + licenseRequest->appendArray( + reinterpret_cast<const uint8_t*>(requestJson.string()), + requestJson.size()); + return android::OK; +} + +android::status_t InitDataParser::parsePssh(const Vector<uint8_t>& initData, + Vector<const uint8_t*>* keyIds) { + size_t readPosition = 0; + + // Validate size field + uint32_t expectedSize = initData.size(); + expectedSize = htonl(expectedSize); + if (memcmp(&initData[readPosition], &expectedSize, + sizeof(expectedSize)) != 0) { + return android::ERROR_DRM_CANNOT_HANDLE; + } + readPosition += sizeof(expectedSize); + + // Validate PSSH box identifier + const char psshIdentifier[4] = {'p', 's', 's', 'h'}; + if (memcmp(&initData[readPosition], psshIdentifier, + sizeof(psshIdentifier)) != 0) { + return android::ERROR_DRM_CANNOT_HANDLE; + } + readPosition += sizeof(psshIdentifier); + + // Validate EME version number + const uint8_t psshVersion1[4] = {1, 0, 0, 0}; + if (memcmp(&initData[readPosition], psshVersion1, + sizeof(psshVersion1)) != 0) { + return android::ERROR_DRM_CANNOT_HANDLE; + } + readPosition += sizeof(psshVersion1); + + // Validate system ID + if (!isClearKeyUUID(&initData[readPosition])) { + return android::ERROR_DRM_CANNOT_HANDLE; + } + readPosition += kSystemIdSize; + + // Read key ID count + uint32_t keyIdCount; + memcpy(&keyIdCount, &initData[readPosition], sizeof(keyIdCount)); + keyIdCount = ntohl(keyIdCount); + readPosition += sizeof(keyIdCount); + if (readPosition + (keyIdCount * kKeyIdSize) != + initData.size() - sizeof(uint32_t)) { + return android::ERROR_DRM_CANNOT_HANDLE; + } + + // Calculate the key ID offsets + for (uint32_t i = 0; i < keyIdCount; ++i) { + size_t keyIdPosition = readPosition + (i * kKeyIdSize); + keyIds->push(&initData[keyIdPosition]); + } + return android::OK; +} + +String8 InitDataParser::generateRequest(const Vector<const uint8_t*>& keyIds) { + const String8 kRequestPrefix("{\"kids\":["); + const String8 kRequestSuffix("],\"type\":\"temporary\"}"); + const String8 kBase64Padding("="); + + String8 request(kRequestPrefix); + AString encodedId; + for (size_t i = 0; i < keyIds.size(); ++i) { + encodedId.clear(); + android::encodeBase64(keyIds[i], kKeyIdSize, &encodedId); + if (i != 0) { + request.append(","); + } + request.appendFormat("\"%s\"", encodedId.c_str()); + } + request.append(kRequestSuffix); + + // Android's Base64 encoder produces padding. EME forbids padding. + request.removeAll(kBase64Padding); + return request; +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/InitDataParser.h b/drm/mediadrm/plugins/clearkey/InitDataParser.h new file mode 100644 index 0000000..9505d2a --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/InitDataParser.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_INIT_DATA_PARSER_H_ +#define CLEARKEY_INIT_DATA_PARSER_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +namespace clearkeydrm { + +class InitDataParser { +public: + InitDataParser() {} + + android::status_t parse(const android::Vector<uint8_t>& initData, + const android::String8& initDataType, + android::Vector<uint8_t>* licenseRequest); + +private: + DISALLOW_EVIL_CONSTRUCTORS(InitDataParser); + + android::status_t parsePssh(const android::Vector<uint8_t>& initData, + android::Vector<const uint8_t*>* keyIds); + + android::String8 generateRequest( + const android::Vector<const uint8_t*>& keyIds); +}; + +} // namespace clearkeydrm + +#endif // CLEARKEY_INIT_DATA_PARSER_H_ diff --git a/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp b/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp new file mode 100644 index 0000000..53ffae4 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2014 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_TAG "JsonWebKey" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/AString.h> +#include <media/stagefright/foundation/base64.h> +#include <utils/Log.h> + +#include "JsonWebKey.h" + +namespace { +const android::String8 kKeysTag("keys"); +const android::String8 kKeyTypeTag("kty"); +const android::String8 kSymmetricKeyValue("oct"); +const android::String8 kKeyTag("k"); +const android::String8 kKeyIdTag("kid"); +const android::String8 kBase64Padding("="); +} + +namespace clearkeydrm { + +using android::ABuffer; +using android::AString; + +JsonWebKey::JsonWebKey() { +} + +JsonWebKey::~JsonWebKey() { +} + +/* + * Parses a JSON Web Key Set string, initializes a KeyMap with key id:key + * pairs from the JSON Web Key Set. Both key ids and keys are base64url + * encoded. The KeyMap contains base64url decoded key id:key pairs. + * + * @return Returns false for errors, true for success. + */ +bool JsonWebKey::extractKeysFromJsonWebKeySet(const String8& jsonWebKeySet, + KeyMap* keys) { + + keys->clear(); + if (!parseJsonWebKeySet(jsonWebKeySet, &mJsonObjects)) { + return false; + } + + // mJsonObjects[0] contains the entire JSON Web Key Set, including + // all the base64 encoded keys. Each key is also stored separately as + // a JSON object in mJsonObjects[1..n] where n is the total + // number of keys in the set. + if (!isJsonWebKeySet(mJsonObjects[0])) { + return false; + } + + String8 encodedKey, encodedKeyId; + Vector<uint8_t> decodedKey, decodedKeyId; + + // mJsonObjects[1] contains the first JSON Web Key in the set + for (size_t i = 1; i < mJsonObjects.size(); ++i) { + encodedKeyId.clear(); + encodedKey.clear(); + + if (!parseJsonObject(mJsonObjects[i], &mTokens)) + return false; + + if (findKey(mJsonObjects[i], &encodedKeyId, &encodedKey)) { + if (encodedKeyId.isEmpty() || encodedKey.isEmpty()) { + ALOGE("Must have both key id and key in the JsonWebKey set."); + continue; + } + + if (!decodeBase64String(encodedKeyId, &decodedKeyId)) { + ALOGE("Failed to decode key id(%s)", encodedKeyId.string()); + continue; + } + + if (!decodeBase64String(encodedKey, &decodedKey)) { + ALOGE("Failed to decode key(%s)", encodedKey.string()); + continue; + } + + keys->add(decodedKeyId, decodedKey); + } + } + return true; +} + +bool JsonWebKey::decodeBase64String(const String8& encodedText, + Vector<uint8_t>* decodedText) { + + decodedText->clear(); + + // encodedText should not contain padding characters as per EME spec. + if (encodedText.find(kBase64Padding) != -1) { + return false; + } + + // Since android::decodeBase64() requires padding characters, + // add them so length of encodedText is exactly a multiple of 4. + int remainder = encodedText.length() % 4; + String8 paddedText(encodedText); + if (remainder > 0) { + for (int i = 0; i < 4 - remainder; ++i) { + paddedText.append(kBase64Padding); + } + } + + android::sp<ABuffer> buffer = + android::decodeBase64(AString(paddedText.string())); + if (buffer == NULL) { + ALOGE("Malformed base64 encoded content found."); + return false; + } + + decodedText->appendArray(buffer->base(), buffer->size()); + return true; +} + +bool JsonWebKey::findKey(const String8& jsonObject, String8* keyId, + String8* encodedKey) { + + String8 key, value; + + // Only allow symmetric key, i.e. "kty":"oct" pair. + if (jsonObject.find(kKeyTypeTag) >= 0) { + findValue(kKeyTypeTag, &value); + if (0 != value.compare(kSymmetricKeyValue)) + return false; + } + + if (jsonObject.find(kKeyIdTag) >= 0) { + findValue(kKeyIdTag, keyId); + } + + if (jsonObject.find(kKeyTag) >= 0) { + findValue(kKeyTag, encodedKey); + } + return true; +} + +void JsonWebKey::findValue(const String8 &key, String8* value) { + value->clear(); + const char* valueToken; + for (Vector<String8>::const_iterator nextToken = mTokens.begin(); + nextToken != mTokens.end(); ++nextToken) { + if (0 == (*nextToken).compare(key)) { + if (nextToken + 1 == mTokens.end()) + break; + valueToken = (*(nextToken + 1)).string(); + value->setTo(valueToken); + nextToken++; + break; + } + } +} + +bool JsonWebKey::isJsonWebKeySet(const String8& jsonObject) const { + if (jsonObject.find(kKeysTag) == -1) { + ALOGE("JSON Web Key does not contain keys."); + return false; + } + return true; +} + +/* + * Parses a JSON objects string and initializes a vector of tokens. + * + * @return Returns false for errors, true for success. + */ +bool JsonWebKey::parseJsonObject(const String8& jsonObject, + Vector<String8>* tokens) { + jsmn_parser parser; + + jsmn_init(&parser); + int numTokens = jsmn_parse(&parser, + jsonObject.string(), jsonObject.size(), NULL, 0); + if (numTokens < 0) { + ALOGE("Parser returns error code=%d", numTokens); + return false; + } + + unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t); + mJsmnTokens.clear(); + mJsmnTokens.setCapacity(jsmnTokensSize); + + jsmn_init(&parser); + int status = jsmn_parse(&parser, jsonObject.string(), + jsonObject.size(), mJsmnTokens.editArray(), numTokens); + if (status < 0) { + ALOGE("Parser returns error code=%d", status); + return false; + } + + tokens->clear(); + String8 token; + const char *pjs; + for (int j = 0; j < numTokens; ++j) { + pjs = jsonObject.string() + mJsmnTokens[j].start; + if (mJsmnTokens[j].type == JSMN_STRING || + mJsmnTokens[j].type == JSMN_PRIMITIVE) { + token.setTo(pjs, mJsmnTokens[j].end - mJsmnTokens[j].start); + tokens->add(token); + } + } + return true; +} + +/* + * Parses JSON Web Key Set string and initializes a vector of JSON objects. + * + * @return Returns false for errors, true for success. + */ +bool JsonWebKey::parseJsonWebKeySet(const String8& jsonWebKeySet, + Vector<String8>* jsonObjects) { + if (jsonWebKeySet.isEmpty()) { + ALOGE("Empty JSON Web Key"); + return false; + } + + // The jsmn parser only supports unicode encoding. + jsmn_parser parser; + + // Computes number of tokens. A token marks the type, offset in + // the original string. + jsmn_init(&parser); + int numTokens = jsmn_parse(&parser, + jsonWebKeySet.string(), jsonWebKeySet.size(), NULL, 0); + if (numTokens < 0) { + ALOGE("Parser returns error code=%d", numTokens); + return false; + } + + unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t); + mJsmnTokens.setCapacity(jsmnTokensSize); + + jsmn_init(&parser); + int status = jsmn_parse(&parser, jsonWebKeySet.string(), + jsonWebKeySet.size(), mJsmnTokens.editArray(), numTokens); + if (status < 0) { + ALOGE("Parser returns error code=%d", status); + return false; + } + + String8 token; + const char *pjs; + for (int i = 0; i < numTokens; ++i) { + pjs = jsonWebKeySet.string() + mJsmnTokens[i].start; + if (mJsmnTokens[i].type == JSMN_OBJECT) { + token.setTo(pjs, mJsmnTokens[i].end - mJsmnTokens[i].start); + jsonObjects->add(token); + } + } + return true; +} + +} // clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/JsonWebKey.h b/drm/mediadrm/plugins/clearkey/JsonWebKey.h new file mode 100644 index 0000000..6ae50ee --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/JsonWebKey.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 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. + */ +#ifndef CLEARKEY_JSON_WEB_KEY_H_ +#define CLEARKEY_JSON_WEB_KEY_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/KeyedVector.h> +#include <utils/String8.h> +#include <utils/StrongPointer.h> + +#include "jsmn.h" +#include "Utils.h" +#include "ClearKeyTypes.h" + +namespace clearkeydrm { + +using android::KeyedVector; +using android::sp; +using android::String8; +using android::Vector; + +class JsonWebKey { + public: + JsonWebKey(); + virtual ~JsonWebKey(); + + bool extractKeysFromJsonWebKeySet(const String8& jsonWebKeySet, + KeyMap* keys); + + private: + Vector<jsmntok_t> mJsmnTokens; + Vector<String8> mJsonObjects; + Vector<String8> mTokens; + + bool decodeBase64String(const String8& encodedText, + Vector<uint8_t>* decodedText); + bool findKey(const String8& jsonObject, String8* keyId, + String8* encodedKey); + void findValue(const String8 &key, String8* value); + bool isJsonWebKeySet(const String8& jsonObject) const; + bool parseJsonObject(const String8& jsonObject, Vector<String8>* tokens); + bool parseJsonWebKeySet(const String8& jsonWebKeySet, Vector<String8>* jsonObjects); + + DISALLOW_EVIL_CONSTRUCTORS(JsonWebKey); +}; + +} // namespace clearkeydrm + +#endif // CLEARKEY_JSON_WEB_KEY_H_ diff --git a/drm/mediadrm/plugins/clearkey/Session.cpp b/drm/mediadrm/plugins/clearkey/Session.cpp new file mode 100644 index 0000000..95016f5 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/Session.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 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 "ClearKeyCryptoPlugin" +#include <utils/Log.h> + +#include <media/stagefright/MediaErrors.h> +#include <utils/String8.h> + +#include "Session.h" + +#include "AesCtrDecryptor.h" +#include "InitDataParser.h" +#include "JsonWebKey.h" + +namespace clearkeydrm { + +using android::Mutex; +using android::String8; +using android::Vector; +using android::status_t; + +status_t Session::getKeyRequest( + const Vector<uint8_t>& initData, + const String8& initDataType, + Vector<uint8_t>* keyRequest) const { + InitDataParser parser; + return parser.parse(initData, initDataType, keyRequest); +} + +status_t Session::provideKeyResponse(const Vector<uint8_t>& response) { + String8 responseString( + reinterpret_cast<const char*>(response.array()), response.size()); + KeyMap keys; + + Mutex::Autolock lock(mMapLock); + JsonWebKey parser; + if (parser.extractKeysFromJsonWebKeySet(responseString, &keys)) { + for (size_t i = 0; i < keys.size(); ++i) { + const KeyMap::key_type& keyId = keys.keyAt(i); + const KeyMap::value_type& key = keys.valueAt(i); + mKeyMap.add(keyId, key); + } + return android::OK; + } else { + return android::ERROR_DRM_UNKNOWN; + } +} + +status_t Session::decrypt( + const KeyId keyId, const Iv iv, const void* source, + void* destination, const SubSample* subSamples, + size_t numSubSamples, size_t* bytesDecryptedOut) { + Mutex::Autolock lock(mMapLock); + + Vector<uint8_t> keyIdVector; + keyIdVector.appendArray(keyId, kBlockSize); + if (mKeyMap.indexOfKey(keyIdVector) < 0) { + return android::ERROR_DRM_NO_LICENSE; + } + + const Vector<uint8_t>& key = mKeyMap.valueFor(keyIdVector); + AesCtrDecryptor decryptor; + return decryptor.decrypt( + key, iv, + reinterpret_cast<const uint8_t*>(source), + reinterpret_cast<uint8_t*>(destination), subSamples, + numSubSamples, bytesDecryptedOut); +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/Session.h b/drm/mediadrm/plugins/clearkey/Session.h new file mode 100644 index 0000000..cab0dc3 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/Session.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_SESSION_H_ +#define CLEARKEY_SESSION_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> +#include <utils/Mutex.h> +#include <utils/RefBase.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +#include "ClearKeyTypes.h" +#include "Utils.h" + +namespace clearkeydrm { + +class Session : public android::RefBase { +public: + explicit Session(const android::Vector<uint8_t>& sessionId) + : mSessionId(sessionId) {} + virtual ~Session() {} + + const android::Vector<uint8_t>& sessionId() const { return mSessionId; } + + android::status_t getKeyRequest( + const android::Vector<uint8_t>& initData, + const android::String8& initDataType, + android::Vector<uint8_t>* keyRequest) const; + + android::status_t provideKeyResponse( + const android::Vector<uint8_t>& response); + + android::status_t decrypt( + const KeyId keyId, const Iv iv, const void* source, + void* destination, const SubSample* subSamples, + size_t numSubSamples, size_t* bytesDecryptedOut); + +private: + DISALLOW_EVIL_CONSTRUCTORS(Session); + + const android::Vector<uint8_t> mSessionId; + + android::Mutex mMapLock; + KeyMap mKeyMap; +}; + +} // namespace clearkeydrm + +#endif // CLEARKEY_SESSION_H_ diff --git a/drm/mediadrm/plugins/clearkey/SessionLibrary.cpp b/drm/mediadrm/plugins/clearkey/SessionLibrary.cpp new file mode 100644 index 0000000..d047c53 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/SessionLibrary.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 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 "ClearKeyCryptoPlugin" +#include <utils/Log.h> + +#include <utils/String8.h> + +#include "SessionLibrary.h" + +namespace clearkeydrm { + +using android::Mutex; +using android::sp; +using android::String8; +using android::Vector; + +Mutex SessionLibrary::sSingletonLock; +SessionLibrary* SessionLibrary::sSingleton = NULL; + +SessionLibrary* SessionLibrary::get() { + Mutex::Autolock lock(sSingletonLock); + + if (sSingleton == NULL) { + ALOGD("Instantiating Session Library Singleton."); + sSingleton = new SessionLibrary(); + } + + return sSingleton; +} + +const sp<Session>& SessionLibrary::createSession() { + Mutex::Autolock lock(mSessionsLock); + + String8 sessionIdString = String8::format("%u", mNextSessionId); + mNextSessionId += 1; + Vector<uint8_t> sessionId; + sessionId.appendArray( + reinterpret_cast<const uint8_t*>(sessionIdString.string()), + sessionIdString.size()); + + mSessions.add(sessionId, new Session(sessionId)); + return mSessions.valueFor(sessionId); +} + +const sp<Session>& SessionLibrary::findSession( + const Vector<uint8_t>& sessionId) { + Mutex::Autolock lock(mSessionsLock); + return mSessions.valueFor(sessionId); +} + +const sp<Session>& SessionLibrary::findSession( + const void* data, size_t size) { + Vector<uint8_t> sessionId; + sessionId.appendArray(reinterpret_cast<const uint8_t*>(data), size); + return findSession(sessionId); +} + +void SessionLibrary::destroySession(const sp<Session>& session) { + Mutex::Autolock lock(mSessionsLock);\ + mSessions.removeItem(session->sessionId()); +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/SessionLibrary.h b/drm/mediadrm/plugins/clearkey/SessionLibrary.h new file mode 100644 index 0000000..56c8828 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/SessionLibrary.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_SESSION_LIBRARY_H_ +#define CLEARKEY_SESSION_LIBRARY_H_ + +#include <utils/KeyedVector.h> +#include <utils/Mutex.h> +#include <utils/StrongPointer.h> +#include <utils/Vector.h> + +#include "Session.h" +#include "Utils.h" + +namespace clearkeydrm { + +class SessionLibrary { +public: + static SessionLibrary* get(); + + const android::sp<Session>& createSession(); + + const android::sp<Session>& findSession( + const android::Vector<uint8_t>& sessionId); + + const android::sp<Session>& findSession(const void* data, size_t size); + + void destroySession(const android::sp<Session>& session); + +private: + DISALLOW_EVIL_CONSTRUCTORS(SessionLibrary); + + SessionLibrary() : mNextSessionId(1) {} + + static android::Mutex sSingletonLock; + static SessionLibrary* sSingleton; + + android::Mutex mSessionsLock; + uint32_t mNextSessionId; + android::KeyedVector<android::Vector<uint8_t>, android::sp<Session> > + mSessions; +}; + +} // namespace clearkeydrm + +#endif // CLEARKEY_SESSION_LIBRARY_H_ diff --git a/drm/mediadrm/plugins/clearkey/Utils.cpp b/drm/mediadrm/plugins/clearkey/Utils.cpp new file mode 100644 index 0000000..93c643b --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/Utils.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 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. + */ + +#include "Utils.h" + +namespace android { + +bool operator<(const Vector<uint8_t> &lhs, const Vector<uint8_t> &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; +} + +} // namespace android diff --git a/drm/mediadrm/plugins/clearkey/Utils.h b/drm/mediadrm/plugins/clearkey/Utils.h new file mode 100644 index 0000000..2543124 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/Utils.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef CLEARKEY_UTILS_H_ +#define CLEARKEY_UTILS_H_ + +#include <utils/Vector.h> + +// Add a comparison operator for this Vector specialization so that it can be +// used as a key in a KeyedVector. +namespace android { + +bool operator<(const Vector<uint8_t> &lhs, const Vector<uint8_t> &rhs); + +} // namespace android + +#define UNUSED(x) (void)(x); + +#endif // CLEARKEY_UTILS_H_ diff --git a/drm/mediadrm/plugins/clearkey/tests/AesCtrDecryptorUnittest.cpp b/drm/mediadrm/plugins/clearkey/tests/AesCtrDecryptorUnittest.cpp new file mode 100644 index 0000000..039e402 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/tests/AesCtrDecryptorUnittest.cpp @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2014 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. + */ + +#include <gtest/gtest.h> +#include <string.h> + +#include <utils/String8.h> +#include <utils/Vector.h> + +#include "AesCtrDecryptor.h" + +namespace clearkeydrm { + +using namespace android; + +class AesCtrDecryptorTest : public ::testing::Test { + protected: + typedef uint8_t Key[kBlockSize]; + + status_t attemptDecrypt(const Key& key, const Iv& iv, const uint8_t* source, + uint8_t* destination, const SubSample* subSamples, + size_t numSubSamples, size_t* bytesDecryptedOut) { + Vector<uint8_t> keyVector; + keyVector.appendArray(key, kBlockSize); + + AesCtrDecryptor decryptor; + return decryptor.decrypt(keyVector, iv, source, destination, subSamples, + numSubSamples, bytesDecryptedOut); + } + + template <size_t totalSize> + void attemptDecryptExpectingSuccess(const Key& key, const Iv& iv, + const uint8_t* encrypted, + const uint8_t* decrypted, + const SubSample* subSamples, + size_t numSubSamples) { + uint8_t outputBuffer[totalSize] = {}; + size_t bytesDecrypted = 0; + ASSERT_EQ(android::OK, attemptDecrypt(key, iv, encrypted, outputBuffer, + subSamples, numSubSamples, + &bytesDecrypted)); + EXPECT_EQ(totalSize, bytesDecrypted); + EXPECT_EQ(0, memcmp(outputBuffer, decrypted, totalSize)); + } +}; + +TEST_F(AesCtrDecryptorTest, DecryptsContiguousEncryptedBlock) { + const size_t kTotalSize = 64; + const size_t kNumSubsamples = 1; + + // Test vectors from NIST-800-38A + Key key = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + + Iv iv = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + + uint8_t encrypted[kTotalSize] = { + 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, + 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce, + 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, + 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff, + 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e, + 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab, + 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1, + 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee + }; + + uint8_t decrypted[kTotalSize] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 + }; + + SubSample subSamples[kNumSubsamples] = { + {0, 64} + }; + + attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted, + subSamples, kNumSubsamples); +} + +TEST_F(AesCtrDecryptorTest, DecryptsAlignedBifurcatedEncryptedBlock) { + const size_t kTotalSize = 64; + const size_t kNumSubsamples = 2; + + // Test vectors from NIST-800-38A + Key key = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + + Iv iv = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + + uint8_t encrypted[kTotalSize] = { + 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, + 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce, + 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, + 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff, + 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e, + 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab, + 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1, + 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee + }; + + uint8_t decrypted[kTotalSize] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 + }; + + SubSample subSamples[kNumSubsamples] = { + {0, 32}, + {0, 32} + }; + + attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted, + subSamples, kNumSubsamples); +} + +TEST_F(AesCtrDecryptorTest, DecryptsUnalignedBifurcatedEncryptedBlock) { + const size_t kTotalSize = 64; + const size_t kNumSubsamples = 2; + + // Test vectors from NIST-800-38A + Key key = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + + Iv iv = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + + uint8_t encrypted[kTotalSize] = { + 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, + 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce, + 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, + 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff, + 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e, + 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab, + 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1, + 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee + }; + + uint8_t decrypted[kTotalSize] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 + }; + + SubSample subSamples[kNumSubsamples] = { + {0, 29}, + {0, 35} + }; + + attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted, + subSamples, kNumSubsamples); +} + +TEST_F(AesCtrDecryptorTest, DecryptsOneMixedSubSample) { + const size_t kTotalSize = 72; + const size_t kNumSubsamples = 1; + + // Based on test vectors from NIST-800-38A + Key key = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + + Iv iv = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + + uint8_t encrypted[kTotalSize] = { + // 8 clear bytes + 0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb, + // 64 encrypted bytes + 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, + 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce, + 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, + 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff, + 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e, + 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab, + 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1, + 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee + }; + + uint8_t decrypted[kTotalSize] = { + 0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb, + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 + }; + + SubSample subSamples[kNumSubsamples] = { + {8, 64} + }; + + attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted, + subSamples, kNumSubsamples); +} + +TEST_F(AesCtrDecryptorTest, DecryptsAlignedMixedSubSamples) { + const size_t kTotalSize = 80; + const size_t kNumSubsamples = 2; + + // Based on test vectors from NIST-800-38A + Key key = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + + Iv iv = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + + uint8_t encrypted[kTotalSize] = { + // 8 clear bytes + 0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb, + // 32 encrypted bytes + 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, + 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce, + 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, + 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff, + // 8 clear bytes + 0x94, 0xba, 0x88, 0x2e, 0x0e, 0x12, 0x11, 0x55, + // 32 encrypted bytes + 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e, + 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab, + 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1, + 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee + }; + + uint8_t decrypted[kTotalSize] = { + 0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb, + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x94, 0xba, 0x88, 0x2e, 0x0e, 0x12, 0x11, 0x55, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 + }; + + SubSample subSamples[kNumSubsamples] = { + {8, 32}, + {8, 32} + }; + + attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted, + subSamples, kNumSubsamples); +} + +TEST_F(AesCtrDecryptorTest, DecryptsUnalignedMixedSubSamples) { + const size_t kTotalSize = 80; + const size_t kNumSubsamples = 2; + + // Based on test vectors from NIST-800-38A + Key key = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + + Iv iv = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + + uint8_t encrypted[kTotalSize] = { + // 8 clear bytes + 0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb, + // 30 encrypted bytes + 0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, + 0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce, + 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, + 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, + // 8 clear bytes + 0x94, 0xba, 0x88, 0x2e, 0x0e, 0x12, 0x11, 0x55, + // 34 encrypted bytes + 0xfd, 0xff, 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, + 0xd3, 0x5e, 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, + 0x3e, 0xab, 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, + 0x03, 0xd1, 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, + 0x9c, 0xee + }; + + uint8_t decrypted[kTotalSize] = { + 0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb, + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x94, 0xba, + 0x88, 0x2e, 0x0e, 0x12, 0x11, 0x55, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 + }; + + SubSample subSamples[kNumSubsamples] = { + {8, 30}, + {8, 34} + }; + + attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted, + subSamples, kNumSubsamples); +} + +TEST_F(AesCtrDecryptorTest, DecryptsComplexMixedSubSamples) { + const size_t kTotalSize = 72; + const size_t kNumSubsamples = 6; + + // Based on test vectors from NIST-800-38A + Key key = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + + Iv iv = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + + uint8_t encrypted[kTotalSize] = { + // 4 clear bytes + 0xf0, 0x13, 0xca, 0xc7, + // 1 encrypted bytes + 0x87, + // 9 encrypted bytes + 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b, + 0xef, + // 11 clear bytes + 0x81, 0x4f, 0x24, 0x87, 0x0e, 0xde, 0xba, 0xad, + 0x11, 0x9b, 0x46, + // 20 encrypted bytes + 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce, + 0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff, + 0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, + // 8 clear bytes + 0x94, 0xba, 0x88, 0x2e, 0x0e, 0x12, 0x11, 0x55, + // 3 clear bytes + 0x10, 0xf5, 0x22, + // 14 encrypted bytes + 0xfd, 0xff, 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, + 0xd3, 0x5e, 0x5b, 0x4f, 0x09, 0x02, + // 2 clear bytes + 0x02, 0x01 + }; + + uint8_t decrypted[kTotalSize] = { + 0xf0, 0x13, 0xca, 0xc7, 0x6b, 0xc1, 0xbe, 0xe2, + 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x81, 0x4f, + 0x24, 0x87, 0x0e, 0xde, 0xba, 0xad, 0x11, 0x9b, + 0x46, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, + 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, + 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x94, 0xba, 0x88, + 0x2e, 0x0e, 0x12, 0x11, 0x55, 0x10, 0xf5, 0x22, + 0x8e, 0x51, 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, + 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x02, 0x01 + }; + + SubSample subSamples[kNumSubsamples] = { + {4, 1}, + {0, 9}, + {11, 20}, + {8, 0}, + {3, 14}, + {2, 0} + }; + + attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted, + subSamples, kNumSubsamples); +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/tests/Android.mk b/drm/mediadrm/plugins/clearkey/tests/Android.mk new file mode 100644 index 0000000..ac5bb21 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/tests/Android.mk @@ -0,0 +1,52 @@ +# +# Copyright (C) 2014 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. +# +# ---------------------------------------------------------------- +# Builds ClearKey Drm Tests +# +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := ClearKeyDrmUnitTest +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := \ + AesCtrDecryptorUnittest.cpp \ + InitDataParserUnittest.cpp \ + JsonWebKeyUnittest.cpp \ + +LOCAL_C_INCLUDES := \ + bionic \ + external/gtest/include \ + external/jsmn \ + external/openssl/include \ + external/stlport/stlport \ + frameworks/av/drm/mediadrm/plugins/clearkey \ + frameworks/av/include \ + frameworks/native/include \ + +LOCAL_STATIC_LIBRARIES := \ + libgtest \ + libgtest_main \ + +LOCAL_SHARED_LIBRARIES := \ + libcrypto \ + libdrmclearkeyplugin \ + liblog \ + libstagefright_foundation \ + libstlport \ + libutils \ + +include $(BUILD_NATIVE_TEST) diff --git a/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp b/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp new file mode 100644 index 0000000..4ba65ed --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2014 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. + */ + +#include <gtest/gtest.h> +#include <string.h> + +#include <media/stagefright/foundation/AString.h> +#include <media/stagefright/foundation/base64.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +#include "InitDataParser.h" + +namespace clearkeydrm { + +using namespace android; + +namespace { + const size_t kKeyIdSize = 16; + const String8 kCencType("cenc"); + const String8 kWebMType("webm"); + const String8 kBase64Padding("="); +} + +class InitDataParserTest : public ::testing::Test { + protected: + status_t attemptParse(const Vector<uint8_t>& initData, + const String8& initDataType, + Vector<uint8_t>* licenseRequest) { + InitDataParser parser; + return parser.parse(initData, initDataType, licenseRequest); + } + + void attemptParseExpectingSuccess(const Vector<uint8_t>& initData, + const String8& initDataType, + const Vector<String8>& expectedKeys) { + const String8 kRequestPrefix("{\"kids\":["); + const String8 kRequestSuffix("],\"type\":\"temporary\"}"); + Vector<uint8_t> request; + ASSERT_EQ(android::OK, attemptParse(initData, initDataType, &request)); + + String8 requestString(reinterpret_cast<const char*>(request.array()), + request.size()); + EXPECT_EQ(0, requestString.find(kRequestPrefix)); + EXPECT_EQ(requestString.size() - kRequestSuffix.size(), + requestString.find(kRequestSuffix)); + for (size_t i = 0; i < expectedKeys.size(); ++i) { + AString encodedIdAString; + android::encodeBase64(expectedKeys[i], kKeyIdSize, + &encodedIdAString); + String8 encodedId(encodedIdAString.c_str()); + encodedId.removeAll(kBase64Padding); + EXPECT_TRUE(requestString.contains(encodedId)); + } + } + + void attemptParseExpectingFailure(const Vector<uint8_t>& initData, + const String8& initDataType) { + Vector<uint8_t> request; + ASSERT_NE(android::OK, attemptParse(initData, initDataType, &request)); + EXPECT_EQ(0, request.size()); + } +}; + +TEST_F(InitDataParserTest, ParsesSingleKeyPssh) { + uint8_t pssh[52] = { + 0, 0, 0, 52, // Total Size + 'p', 's', 's', 'h', // PSSH + 1, 0, 0, 0, // Version + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID + 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, + 0, 0, 0, 1, // Key Count + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1 + 0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, // "01234567890ABCDE" + 0, 0, 0, 0 // Data Size (always 0) + }; + Vector<uint8_t> initData; + initData.appendArray(pssh, 52); + + Vector<String8> expectedKeys; + expectedKeys.push(String8("01234567890ABCDE")); + + attemptParseExpectingSuccess(initData, kCencType, expectedKeys); +} + +TEST_F(InitDataParserTest, ParsesMultipleKeyPssh) { + uint8_t pssh[84] = { + 0, 0, 0, 84, // Total Size + 'p', 's', 's', 'h', // PSSH + 1, 0, 0, 0, // Version + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID + 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, + 0, 0, 0, 3, // Key Count + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1 + 0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, // "01234567890ABCDE" + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x4b, 0x65, 0x79, // Key ID #2 + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x4b, 0x65, 0x79, // "ClearKeyClearKey" + 0x20, 0x47, 0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x20, // Key ID #3 + 0x20, 0x47, 0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x20, // " GOOGLE GOOGLE " + 0, 0, 0, 0 // Data Size (always 0) + }; + Vector<uint8_t> initData; + initData.appendArray(pssh, 84); + + Vector<String8> expectedKeys; + expectedKeys.push(String8("01234567890ABCDE")); + expectedKeys.push(String8("ClearKeyClearKey")); + expectedKeys.push(String8(" GOOGLE GOOGLE ")); + + attemptParseExpectingSuccess(initData, kCencType, expectedKeys); +} + +TEST_F(InitDataParserTest, ParsesWebM) { + uint8_t initDataRaw[16] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID + 0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, // "01234567890ABCDE" + }; + Vector<uint8_t> initData; + initData.appendArray(initDataRaw, 16); + + Vector<String8> expectedKeys; + expectedKeys.push(String8("01234567890ABCDE")); + + attemptParseExpectingSuccess(initData, kWebMType, expectedKeys); +} + +TEST_F(InitDataParserTest, FailsForPsshTooSmall) { + uint8_t pssh[16] = { + 0, 0, 0, 52, + 'p', 's', 's', 'h', + 1, 0, 0, 0, + 0x10, 0x77, 0xef, 0xec + }; + Vector<uint8_t> initData; + initData.appendArray(pssh, 16); + + attemptParseExpectingFailure(initData, kCencType); +} + +TEST_F(InitDataParserTest, FailsForWebMTooSmall) { + uint8_t initDataRaw[8] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37 + }; + Vector<uint8_t> initData; + initData.appendArray(initDataRaw, 8); + + attemptParseExpectingFailure(initData, kWebMType); +} + +TEST_F(InitDataParserTest, FailsForPsshBadSystemId) { + uint8_t pssh[52] = { + 0, 0, 0, 52, // Total Size + 'p', 's', 's', 'h', // PSSH + 1, 0, 0, 0, // Version + 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, // System ID + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, + 0, 0, 0, 1, // Key Count + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1 + 0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, // "01234567890ABCDE" + 0, 0, 0, 0 // Data Size (always 0) + }; + Vector<uint8_t> initData; + initData.appendArray(pssh, 52); + + attemptParseExpectingFailure(initData, kCencType); +} + +TEST_F(InitDataParserTest, FailsForPsshBadSize) { + uint8_t pssh[52] = { + 0, 0, 70, 200, // Total Size + 'p', 's', 's', 'h', // PSSH + 1, 0, 0, 0, // Version + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID + 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, + 0, 0, 0, 1, // Key Count + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1 + 0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, // "01234567890ABCDE" + 0, 0, 0, 0 // Data Size (always 0) + }; + Vector<uint8_t> initData; + initData.appendArray(pssh, 52); + + attemptParseExpectingFailure(initData, kCencType); +} + +TEST_F(InitDataParserTest, FailsForPsshWrongVersion) { + uint8_t pssh[52] = { + 0, 0, 0, 52, // Total Size + 'p', 's', 's', 'h', // PSSH + 0, 0, 0, 0, // Version + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID + 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, + 0, 0, 0, 1, // Key Count + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1 + 0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, // "01234567890ABCDE" + 0, 0, 0, 0 // Data Size (always 0) + }; + Vector<uint8_t> initData; + initData.appendArray(pssh, 52); + + attemptParseExpectingFailure(initData, kCencType); +} + +TEST_F(InitDataParserTest, FailsForPsshBadKeyCount) { + uint8_t pssh[52] = { + 0, 0, 0, 52, // Total Size + 'p', 's', 's', 'h', // PSSH + 1, 0, 0, 0, // Version + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID + 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, + 0, 0, 0, 7, // Key Count + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1 + 0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, // "01234567890ABCDE" + 0, 0, 0, 0 // Data Size (always 0) + }; + Vector<uint8_t> initData; + initData.appendArray(pssh, 52); + + attemptParseExpectingFailure(initData, kCencType); +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp b/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp new file mode 100644 index 0000000..c3b0d84 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2014 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. + */ +#include <utils/Log.h> + +#include "JsonWebKey.h" + +#include "gtest/gtest.h" +#include "Utils.h" + +namespace clearkeydrm { +using android::String8; +using android::Vector; + +class JsonWebKeyTest : public ::testing::Test { + protected: + JsonWebKey* jwk; + + JsonWebKeyTest() { + jwk = new JsonWebKey; + } + + virtual ~JsonWebKeyTest() { + if (jwk) + delete jwk; + } +}; + +void stringFromVector(const Vector<uint8_t>& input, + String8* converted) { + converted->clear(); + if (input.isEmpty()) { + return; + } + + for (size_t i = 0; i < input.size(); ++i) { + converted->appendFormat("%c", input.itemAt(i)); + } +} + +void verifyKeys(const KeyMap& keys, const String8* clearKeys) { + if (keys.isEmpty()) { + return; + } + + String8 keyString; + for (size_t i = 0; i < keys.size(); ++i) { + stringFromVector(keys.valueAt(i), &keyString); + EXPECT_EQ(keyString, clearKeys[i]); + } +} + +TEST_F(JsonWebKeyTest, NoSymmetricKey) { + const String8 js( + "{" + "[{" + "\"kty\":\"rsa\"," + "\"alg\":\"A128KW1\"," + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\"," + "\"k\":\"1-GawgguFyGrWKav7AX4VKUg\"" + "}]" + "}"); + + KeyMap keys; + EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys)); + EXPECT_TRUE(keys.isEmpty()); +} + +TEST_F(JsonWebKeyTest, NoKeysTag) { + const String8 js( + "{" + "[{" + "\"kty\":\"oct\"," + "\"alg\":\"A128KW1\"," + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\"," + "\"k\":\"1-GawgguFyGrWKav7AX4VKUg\"" + "}," + "{" + "\"kty\":\"oct\"," + "\"alg\":\"A128KW2\"," + "\"k\":\"R29vZCBkYXkh\"," + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\"" + "}]" + "}"); + + KeyMap keys; + EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys)); + EXPECT_TRUE(keys.isEmpty()); +} + +TEST_F(JsonWebKeyTest, NoKeyId) { + const String8 js( + "{" + "\"keys\":" + "[{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW1\"" + "\"k\":\"SGVsbG8gRnJpZW5kISE=\"" + "}" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW2\"" + "\"k\":\"R29vZCBkYXkh\"" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\"" + "}]" + "}"); + + KeyMap keys; + EXPECT_TRUE(jwk->extractKeysFromJsonWebKeySet(js, &keys)); + EXPECT_TRUE(keys.size() == 1); + + const String8 clearKeys("Good day!"); + verifyKeys(keys, &clearKeys); +} + +TEST_F(JsonWebKeyTest, NoKey) { + const String8 js( + "{" + "\"keys\":" + "[{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW1\"" + "\"kid\":\"`\"" + "}" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW2\"" + "\"k\":\"R29vZCBkYXkh\"" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\"" + "}]" + "}"); + + KeyMap keys; + EXPECT_TRUE(jwk->extractKeysFromJsonWebKeySet(js, &keys)); + EXPECT_TRUE(keys.size() == 1); + + const String8 clearKeys("Good day!"); + verifyKeys(keys, &clearKeys); +} + +TEST_F(JsonWebKeyTest, MalformedKey) { + const String8 js( + "{" + "\"keys\":" + "[{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW1\"" + "\"k\":\"GawgguFyGrWKav7AX4V???\"" + "\"kid\":\"67ef0gd8pvfd0=\"" + "}" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW1\"" + "\"k\":\"GawgguFyGrWKav7AX4V???\"" + "\"kid\":" + "}" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW1\"" + ":\"GawgguFyGrWKav7AX4V???\"" + "\"kid\":\"67ef0gd8pvfd0=\"" + "}" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW3\"" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\"" + "\"k\":\"R29vZCBkYXkh\"" + "}]" + "}"); + + KeyMap keys; + EXPECT_TRUE(jwk->extractKeysFromJsonWebKeySet(js, &keys)); + EXPECT_TRUE(keys.size() == 1); + + const String8 clearKeys("Good day!"); + verifyKeys(keys, &clearKeys); +} + +TEST_F(JsonWebKeyTest, EmptyJsonWebKey) { + const String8 js; + KeyMap keys; + EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys)); + EXPECT_TRUE(keys.isEmpty()); +} + +TEST_F(JsonWebKeyTest, MalformedJsonWebKey) { + // Missing begin array '[' + const String8 js( + "{" + "\"keys\":" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW1\"" + "\"k\":\"GawgguFyGrWKav7AX4VKUg\"" + "\"kid\":\"67ef0gd8pvfd0=\"" + "}" + "]" + "}"); + + KeyMap keys; + EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys)); + EXPECT_TRUE(keys.isEmpty()); +} + +TEST_F(JsonWebKeyTest, SameKeyId) { + const String8 js( + "{" + "\"keys\":" + "[{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW1\"" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\"" + "\"k\":\"SGVsbG8gRnJpZW5kISE\"" + "}" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW1\"" + "\"k\":\"SGVsbG8gRnJpZW5kIQ\"" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\"" + "}" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW3\"" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\"" + "\"k\":\"R29vZCBkYXkh\"" + "}]" + "}"); + + KeyMap keys; + jwk->extractKeysFromJsonWebKeySet(js, &keys); + EXPECT_TRUE(keys.size() == 2); + + const String8 clearKeys[] = + { String8("Hello Friend!"), String8("Good day!") }; + verifyKeys(keys, clearKeys); +} + +TEST_F(JsonWebKeyTest, ExtractWellFormedKeys) { + const String8 js( + "{" + "\"keys\":" + "[{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW1\"" + "}" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW2\"" + "\"k\":\"SGVsbG8gRnJpZW5kIQ\"" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\"" + "}" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW3\"" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\"" + "\"k\":\"R29vZCBkYXkh\"" + "}]" + "}"); + + KeyMap keys; + jwk->extractKeysFromJsonWebKeySet(js, &keys); + EXPECT_TRUE(keys.size() == 2); + + const String8 clearKeys[] = + { String8("Hello Friend!"), String8("Good day!") }; + verifyKeys(keys, clearKeys); +} + +TEST_F(JsonWebKeyTest, ExtractKeys) { + const String8 js( + "{" + "\"keys\":" + "[{" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\"" + "\"k\":\"SGVsbG8gRnJpZW5kISE\"" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW1\"" + "}" + "{" + "\"kty\":\"oct\"" + "\"alg\":\"A128KW2\"" + "\"k\":\"SGVsbG8gRnJpZW5kIQ\"" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\"" + "}" + "{" + "\"kty\":\"rsa\"" + "\"alg\":\"A128KW-rsa\"" + "\"k\":\"R29vZCBkYXkh\"" + "\"kid\":\"rsa-67ef0gd8pvfd0=\"" + "}" + "{" + "\"alg\":\"A128KW3\"" + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\"" + "\"k\":\"R29vZCBkYXkh\"" + "\"kty\":\"oct\"" + "}]" + "}"); + + KeyMap keys; + jwk->extractKeysFromJsonWebKeySet(js, &keys); + EXPECT_TRUE(keys.size() == 3); + + const String8 clearKeys[] = + { String8("Hello Friend!!"), String8("Hello Friend!"), + String8("Good day!") }; + verifyKeys(keys, clearKeys); +} + +} // namespace clearkeydrm diff --git a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp index 69fa7a0..6efc712 100644 --- a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp +++ b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp @@ -45,7 +45,7 @@ namespace android { // MockDrmFactory bool MockDrmFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) { - return (!memcmp(uuid, mock_uuid, sizeof(uuid))); + return (!memcmp(uuid, mock_uuid, sizeof(mock_uuid))); } bool MockDrmFactory::isContentTypeSupported(const String8 &mimeType) @@ -65,7 +65,7 @@ namespace android { // MockCryptoFactory bool MockCryptoFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) const { - return (!memcmp(uuid, mock_uuid, sizeof(uuid))); + return (!memcmp(uuid, mock_uuid, sizeof(mock_uuid))); } status_t MockCryptoFactory::createPlugin(const uint8_t uuid[16], const void *data, @@ -254,7 +254,9 @@ namespace android { return OK; } - status_t MockDrmPlugin::getProvisionRequest(Vector<uint8_t> &request, + status_t MockDrmPlugin::getProvisionRequest(String8 const &certType, + String8 const &certAuthority, + Vector<uint8_t> &request, String8 &defaultUrl) { Mutex::Autolock lock(mLock); @@ -282,7 +284,9 @@ namespace android { return OK; } - status_t MockDrmPlugin::provideProvisionResponse(Vector<uint8_t> const &response) + status_t MockDrmPlugin::provideProvisionResponse(Vector<uint8_t> const &response, + Vector<uint8_t> &certificate, + Vector<uint8_t> &wrappedKey) { Mutex::Autolock lock(mLock); ALOGD("MockDrmPlugin::provideProvisionResponse(%s)", @@ -600,6 +604,33 @@ namespace android { return OK; } + status_t MockDrmPlugin::signRSA(Vector<uint8_t> const &sessionId, + String8 const &algorithm, + Vector<uint8_t> const &message, + Vector<uint8_t> const &wrappedKey, + Vector<uint8_t> &signature) + { + Mutex::Autolock lock(mLock); + ALOGD("MockDrmPlugin::signRSA(sessionId=%s, algorithm=%s, keyId=%s, " + "message=%s, signature=%s)", + vectorToString(sessionId).string(), + algorithm.string(), + vectorToString(message).string(), + vectorToString(wrappedKey).string(), + vectorToString(signature).string()); + + // Properties used in mock test, set by mock plugin and verifed cts test app + // byte[] wrappedKey -> mock-wrappedkey + // byte[] message -> mock-message + // byte[] signature -> mock-signature + mByteArrayProperties.add(String8("mock-sessionid"), sessionId); + mStringProperties.add(String8("mock-algorithm"), algorithm); + mByteArrayProperties.add(String8("mock-message"), message); + mByteArrayProperties.add(String8("mock-wrappedkey"), wrappedKey); + mByteArrayProperties.add(String8("mock-signature"), signature); + return OK; + } + ssize_t MockDrmPlugin::findSession(Vector<uint8_t> const &sessionId) const { ALOGD("findSession: nsessions=%d, size=%d", mSessions.size(), sessionId.size()); diff --git a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h index 2297f9b..97d7052 100644 --- a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h +++ b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h @@ -76,10 +76,14 @@ namespace android { status_t queryKeyStatus(Vector<uint8_t> const &sessionId, KeyedVector<String8, String8> &infoMap) const; - status_t getProvisionRequest(Vector<uint8_t> &request, - String8 &defaultUrl); + status_t getProvisionRequest(String8 const &certType, + String8 const &certAuthority, + Vector<uint8_t> &request, + String8 &defaultUrl); - status_t provideProvisionResponse(Vector<uint8_t> const &response); + status_t provideProvisionResponse(Vector<uint8_t> const &response, + Vector<uint8_t> &certificate, + Vector<uint8_t> &wrappedKey); status_t getSecureStops(List<Vector<uint8_t> > &secureStops); status_t releaseSecureStops(Vector<uint8_t> const &ssRelease); @@ -122,6 +126,12 @@ namespace android { Vector<uint8_t> const &signature, bool &match); + status_t signRSA(Vector<uint8_t> const &sessionId, + String8 const &algorithm, + Vector<uint8_t> const &message, + Vector<uint8_t> const &wrappedKey, + Vector<uint8_t> &signature); + private: String8 vectorToString(Vector<uint8_t> const &vector) const; String8 arrayToString(uint8_t const *array, size_t len) const; |