diff options
Diffstat (limited to 'media/gmp-clearkey/0.1/ClearKeySessionManager.cpp')
-rw-r--r-- | media/gmp-clearkey/0.1/ClearKeySessionManager.cpp | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp new file mode 100644 index 0000000000..4dbb062992 --- /dev/null +++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp @@ -0,0 +1,418 @@ +/* + * Copyright 2015, Mozilla Foundation and contributors + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "ClearKeyDecryptionManager.h" +#include "ClearKeySessionManager.h" +#include "ClearKeyUtils.h" +#include "ClearKeyStorage.h" +#include "ClearKeyPersistence.h" +#include "gmp-task-utils.h" +#include <assert.h> + +using namespace std; + +ClearKeySessionManager::ClearKeySessionManager() + : mDecryptionManager(ClearKeyDecryptionManager::Get()) +{ + CK_LOGD("ClearKeySessionManager ctor %p", this); + AddRef(); + + if (GetPlatform()->createthread(&mThread) != GMPNoErr) { + CK_LOGD("failed to create thread in clearkey cdm"); + mThread = nullptr; + } +} + +ClearKeySessionManager::~ClearKeySessionManager() +{ + CK_LOGD("ClearKeySessionManager dtor %p", this); +} + +void +ClearKeySessionManager::Init(GMPDecryptorCallback* aCallback, + bool aDistinctiveIdentifierAllowed, + bool aPersistentStateAllowed) +{ + CK_LOGD("ClearKeySessionManager::Init"); + mCallback = aCallback; + ClearKeyPersistence::EnsureInitialized(); +} + +void +ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const char* aInitDataType, + uint32_t aInitDataTypeSize, + const uint8_t* aInitData, + uint32_t aInitDataSize, + GMPSessionType aSessionType) +{ + CK_LOGD("ClearKeySessionManager::CreateSession type:%s", aInitDataType); + + string initDataType(aInitDataType, aInitDataType + aInitDataTypeSize); + // initDataType must be "cenc", "keyids", or "webm". + if (initDataType != "cenc" && + initDataType != "keyids" && + initDataType != "webm") { + string message = "'" + initDataType + "' is an initDataType unsupported by ClearKey"; + mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError, + message.c_str(), message.size()); + return; + } + + if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this, + aCreateSessionToken, + aPromiseId, + initDataType, + aInitData, + aInitDataSize, + aSessionType)) { + return; + } + + string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType); + assert(mSessions.find(sessionId) == mSessions.end()); + + ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType); + session->Init(aCreateSessionToken, aPromiseId, initDataType, aInitData, aInitDataSize); + mSessions[sessionId] = session; + + const vector<KeyId>& sessionKeys = session->GetKeyIds(); + vector<KeyId> neededKeys; + for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) { + // Need to request this key ID from the client. We always send a key + // request, whether or not another session has sent a request with the same + // key ID. Otherwise a script can end up waiting for another script to + // respond to the request (which may not necessarily happen). + neededKeys.push_back(*it); + mDecryptionManager->ExpectKeyId(*it); + } + + if (neededKeys.empty()) { + CK_LOGD("No keys needed from client."); + return; + } + + // Send a request for needed key data. + string request; + ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType); + mCallback->SessionMessage(&sessionId[0], sessionId.length(), + kGMPLicenseRequest, + (uint8_t*)&request[0], request.length()); +} + +void +ClearKeySessionManager::LoadSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) +{ + CK_LOGD("ClearKeySessionManager::LoadSession"); + + if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) { + mCallback->ResolveLoadSessionPromise(aPromiseId, false); + return; + } + + if (ClearKeyPersistence::DeferLoadSessionIfNotReady(this, + aPromiseId, + aSessionId, + aSessionIdLength)) { + return; + } + + string sid(aSessionId, aSessionId + aSessionIdLength); + if (!ClearKeyPersistence::IsPersistentSessionId(sid)) { + mCallback->ResolveLoadSessionPromise(aPromiseId, false); + return; + } + + // Callsback PersistentSessionDataLoaded with results... + ClearKeyPersistence::LoadSessionData(this, sid, aPromiseId); +} + +void +ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus, + uint32_t aPromiseId, + const string& aSessionId, + const uint8_t* aKeyData, + uint32_t aKeyDataSize) +{ + CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded"); + if (GMP_FAILED(aStatus) || + Contains(mSessions, aSessionId) || + (aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) { + mCallback->ResolveLoadSessionPromise(aPromiseId, false); + return; + } + + ClearKeySession* session = new ClearKeySession(aSessionId, + mCallback, + kGMPPersistentSession); + mSessions[aSessionId] = session; + + uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN); + + vector<GMPMediaKeyInfo> key_infos; + vector<KeyIdPair> keyPairs; + for (uint32_t i = 0; i < numKeys; i ++) { + const uint8_t* base = aKeyData + 2 * CENC_KEY_LEN * i; + + KeyIdPair keyPair; + + keyPair.mKeyId = KeyId(base, base + CENC_KEY_LEN); + assert(keyPair.mKeyId.size() == CENC_KEY_LEN); + + keyPair.mKey = Key(base + CENC_KEY_LEN, base + 2 * CENC_KEY_LEN); + assert(keyPair.mKey.size() == CENC_KEY_LEN); + + session->AddKeyId(keyPair.mKeyId); + + mDecryptionManager->ExpectKeyId(keyPair.mKeyId); + mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey); + mKeyIds.insert(keyPair.mKey); + + keyPairs.push_back(keyPair); + key_infos.push_back(GMPMediaKeyInfo(&keyPairs[i].mKeyId[0], + keyPairs[i].mKeyId.size(), + kGMPUsable)); + } + mCallback->BatchedKeyStatusChanged(&aSessionId[0], aSessionId.size(), + key_infos.data(), key_infos.size()); + + mCallback->ResolveLoadSessionPromise(aPromiseId, true); +} + +void +ClearKeySessionManager::UpdateSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aResponse, + uint32_t aResponseSize) +{ + CK_LOGD("ClearKeySessionManager::UpdateSession"); + string sessionId(aSessionId, aSessionId + aSessionIdLength); + + auto itr = mSessions.find(sessionId); + if (itr == mSessions.end() || !(itr->second)) { + CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession."); + mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0); + return; + } + ClearKeySession* session = itr->second; + + // Verify the size of session response. + if (aResponseSize >= kMaxSessionResponseLength) { + CK_LOGW("Session response size is not within a reasonable size."); + mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0); + return; + } + + // Parse the response for any (key ID, key) pairs. + vector<KeyIdPair> keyPairs; + if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) { + CK_LOGW("ClearKey CDM failed to parse JSON Web Key."); + mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0); + return; + } + + vector<GMPMediaKeyInfo> key_infos; + for (size_t i = 0; i < keyPairs.size(); i++) { + KeyIdPair& keyPair = keyPairs[i]; + mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey); + mKeyIds.insert(keyPair.mKeyId); + key_infos.push_back(GMPMediaKeyInfo(&keyPair.mKeyId[0], + keyPair.mKeyId.size(), + kGMPUsable)); + } + mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdLength, + key_infos.data(), key_infos.size()); + + if (session->Type() != kGMPPersistentSession) { + mCallback->ResolvePromise(aPromiseId); + return; + } + + // Store the keys on disk. We store a record whose name is the sessionId, + // and simply append each keyId followed by its key. + vector<uint8_t> keydata; + Serialize(session, keydata); + GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId); + static const char* message = "Couldn't store cenc key init data"; + GMPTask* reject = WrapTask(mCallback, + &GMPDecryptorCallback::RejectPromise, + aPromiseId, + kGMPInvalidStateError, + message, + strlen(message)); + StoreData(sessionId, keydata, resolve, reject); +} + +void +ClearKeySessionManager::Serialize(const ClearKeySession* aSession, + std::vector<uint8_t>& aOutKeyData) +{ + const std::vector<KeyId>& keyIds = aSession->GetKeyIds(); + for (size_t i = 0; i < keyIds.size(); i++) { + const KeyId& keyId = keyIds[i]; + if (!mDecryptionManager->HasKeyForKeyId(keyId)) { + continue; + } + assert(keyId.size() == CENC_KEY_LEN); + aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end()); + const Key& key = mDecryptionManager->GetDecryptionKey(keyId); + assert(key.size() == CENC_KEY_LEN); + aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end()); + } +} + +void +ClearKeySessionManager::CloseSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) +{ + CK_LOGD("ClearKeySessionManager::CloseSession"); + + string sessionId(aSessionId, aSessionId + aSessionIdLength); + auto itr = mSessions.find(sessionId); + if (itr == mSessions.end()) { + CK_LOGW("ClearKey CDM couldn't close non-existent session."); + mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0); + return; + } + + ClearKeySession* session = itr->second; + assert(session); + + ClearInMemorySessionData(session); + mCallback->SessionClosed(aSessionId, aSessionIdLength); + mCallback->ResolvePromise(aPromiseId); +} + +void +ClearKeySessionManager::ClearInMemorySessionData(ClearKeySession* aSession) +{ + mSessions.erase(aSession->Id()); + delete aSession; +} + +void +ClearKeySessionManager::RemoveSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) +{ + CK_LOGD("ClearKeySessionManager::RemoveSession"); + string sessionId(aSessionId, aSessionId + aSessionIdLength); + auto itr = mSessions.find(sessionId); + if (itr == mSessions.end()) { + CK_LOGW("ClearKey CDM couldn't remove non-existent session."); + mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0); + return; + } + + ClearKeySession* session = itr->second; + assert(session); + string sid = session->Id(); + bool isPersistent = session->Type() == kGMPPersistentSession; + ClearInMemorySessionData(session); + + if (!isPersistent) { + mCallback->ResolvePromise(aPromiseId); + return; + } + + ClearKeyPersistence::PersistentSessionRemoved(sid); + + // Overwrite the record storing the sessionId's key data with a zero + // length record to delete it. + vector<uint8_t> emptyKeydata; + GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId); + static const char* message = "Could not remove session"; + GMPTask* reject = WrapTask(mCallback, + &GMPDecryptorCallback::RejectPromise, + aPromiseId, + kGMPInvalidAccessError, + message, + strlen(message)); + StoreData(sessionId, emptyKeydata, resolve, reject); +} + +void +ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId, + const uint8_t* aServerCert, + uint32_t aServerCertSize) +{ + // ClearKey CDM doesn't support this method by spec. + CK_LOGD("ClearKeySessionManager::SetServerCertificate"); + mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError, + nullptr /* message */, 0 /* messageLen */); +} + +void +ClearKeySessionManager::Decrypt(GMPBuffer* aBuffer, + GMPEncryptedBufferMetadata* aMetadata) +{ + CK_LOGD("ClearKeySessionManager::Decrypt"); + + if (!mThread) { + CK_LOGW("No decrypt thread"); + mCallback->Decrypted(aBuffer, GMPGenericErr); + return; + } + + mThread->Post(WrapTaskRefCounted(this, + &ClearKeySessionManager::DoDecrypt, + aBuffer, aMetadata)); +} + +void +ClearKeySessionManager::DoDecrypt(GMPBuffer* aBuffer, + GMPEncryptedBufferMetadata* aMetadata) +{ + CK_LOGD("ClearKeySessionManager::DoDecrypt"); + + GMPErr rv = mDecryptionManager->Decrypt(aBuffer->Data(), aBuffer->Size(), + CryptoMetaData(aMetadata)); + CK_LOGD("DeDecrypt finished with code %x\n", rv); + mCallback->Decrypted(aBuffer, rv); +} + +void +ClearKeySessionManager::Shutdown() +{ + CK_LOGD("ClearKeySessionManager::Shutdown %p", this); + + for (auto it = mSessions.begin(); it != mSessions.end(); it++) { + delete it->second; + } + mSessions.clear(); +} + +void +ClearKeySessionManager::DecryptingComplete() +{ + CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this); + + GMPThread* thread = mThread; + thread->Join(); + + Shutdown(); + mDecryptionManager = nullptr; + Release(); +} |