зеркало из https://github.com/mozilla/gecko-dev.git
714 строки
24 KiB
C++
714 строки
24 KiB
C++
/*
|
|
* 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 "ClearKeySessionManager.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "content_decryption_module.h"
|
|
|
|
#include "ClearKeyDecryptionManager.h"
|
|
#include "ClearKeyPersistence.h"
|
|
#include "ClearKeyStorage.h"
|
|
#include "ClearKeyUtils.h"
|
|
#include "psshparser/PsshParser.h"
|
|
|
|
using namespace cdm;
|
|
|
|
using std::function;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
ClearKeySessionManager::ClearKeySessionManager(Host_10* aHost)
|
|
: mDecryptionManager(ClearKeyDecryptionManager::Get()) {
|
|
CK_LOGD("ClearKeySessionManager ctor %p", this);
|
|
AddRef();
|
|
|
|
mHost = aHost;
|
|
mPersistence = new ClearKeyPersistence(mHost);
|
|
}
|
|
|
|
ClearKeySessionManager::~ClearKeySessionManager() {
|
|
CK_LOGD("ClearKeySessionManager dtor %p", this);
|
|
}
|
|
|
|
void ClearKeySessionManager::Init(bool aDistinctiveIdentifierAllowed,
|
|
bool aPersistentStateAllowed) {
|
|
CK_LOGD("ClearKeySessionManager::Init");
|
|
|
|
RefPtr<ClearKeySessionManager> self(this);
|
|
function<void()> onPersistentStateLoaded = [self]() {
|
|
while (!self->mDeferredInitialize.empty()) {
|
|
function<void()> func = self->mDeferredInitialize.front();
|
|
self->mDeferredInitialize.pop();
|
|
|
|
func();
|
|
}
|
|
if (self->mHost) {
|
|
// The session manager should be the last thing the ClearKey CDM is
|
|
// waiting on to be initialized.
|
|
self->mHost->OnInitialized(true);
|
|
}
|
|
};
|
|
|
|
mPersistence->EnsureInitialized(aPersistentStateAllowed,
|
|
std::move(onPersistentStateLoaded));
|
|
}
|
|
|
|
void ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
|
|
InitDataType aInitDataType,
|
|
const uint8_t* aInitData,
|
|
uint32_t aInitDataSize,
|
|
SessionType aSessionType) {
|
|
CK_LOGD("ClearKeySessionManager::CreateSession type:%u", aInitDataType);
|
|
|
|
// Copy the init data so it is correctly captured by the lambda
|
|
vector<uint8_t> initData(aInitData, aInitData + aInitDataSize);
|
|
|
|
RefPtr<ClearKeySessionManager> self(this);
|
|
function<void()> deferrer = [self, aPromiseId, aInitDataType, initData,
|
|
aSessionType]() {
|
|
self->CreateSession(aPromiseId, aInitDataType, initData.data(),
|
|
initData.size(), aSessionType);
|
|
};
|
|
|
|
// If we haven't loaded, don't do this yet
|
|
if (MaybeDeferTillInitialized(std::move(deferrer))) {
|
|
CK_LOGD("Deferring CreateSession");
|
|
return;
|
|
}
|
|
|
|
CK_LOGARRAY("ClearKeySessionManager::CreateSession initdata: ", aInitData,
|
|
aInitDataSize);
|
|
|
|
// If 'DecryptingComplete' has been called mHost will be null so we can't
|
|
// won't be able to resolve our promise
|
|
if (!mHost) {
|
|
CK_LOGD("ClearKeySessionManager::CreateSession: mHost is nullptr");
|
|
return;
|
|
}
|
|
|
|
// initDataType must be "cenc", "keyids", or "webm".
|
|
if (aInitDataType != InitDataType::kCenc &&
|
|
aInitDataType != InitDataType::kKeyIds &&
|
|
aInitDataType != InitDataType::kWebM) {
|
|
string message = "initDataType is not supported by ClearKey";
|
|
mHost->OnRejectPromise(aPromiseId, Exception::kExceptionNotSupportedError,
|
|
0, message.c_str(), message.size());
|
|
|
|
return;
|
|
}
|
|
|
|
string sessionId = mPersistence->GetNewSessionId(aSessionType);
|
|
assert(mSessions.find(sessionId) == mSessions.end());
|
|
|
|
ClearKeySession* session = new ClearKeySession(sessionId, aSessionType);
|
|
|
|
if (!session->Init(aInitDataType, aInitData, aInitDataSize)) {
|
|
CK_LOGD("Failed to initialize session: %s", sessionId.c_str());
|
|
|
|
const static char* message = "Failed to initialize session";
|
|
mHost->OnRejectPromise(aPromiseId, Exception::kExceptionInvalidStateError,
|
|
0, message, strlen(message));
|
|
delete session;
|
|
|
|
return;
|
|
}
|
|
|
|
mSessions[sessionId] = session;
|
|
mLastSessionId = sessionId;
|
|
|
|
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);
|
|
|
|
// Resolve the promise with the new session information.
|
|
mHost->OnResolveNewSessionPromise(aPromiseId, sessionId.c_str(),
|
|
sessionId.size());
|
|
|
|
mHost->OnSessionMessage(sessionId.c_str(), sessionId.size(),
|
|
MessageType::kLicenseRequest, request.c_str(),
|
|
request.size());
|
|
}
|
|
|
|
void ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
|
|
const char* aSessionId,
|
|
uint32_t aSessionIdLength) {
|
|
CK_LOGD("ClearKeySessionManager::LoadSession");
|
|
|
|
// Copy the sessionId into a string so the lambda captures it properly.
|
|
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
|
|
|
// Hold a reference to the SessionManager so that it isn't released before
|
|
// we try to use it.
|
|
RefPtr<ClearKeySessionManager> self(this);
|
|
function<void()> deferrer = [self, aPromiseId, sessionId]() {
|
|
self->LoadSession(aPromiseId, sessionId.data(), sessionId.size());
|
|
};
|
|
|
|
if (MaybeDeferTillInitialized(std::move(deferrer))) {
|
|
CK_LOGD("Deferring LoadSession");
|
|
return;
|
|
}
|
|
|
|
// If the SessionManager has been shutdown mHost will be null and we won't
|
|
// be able to resolve the promise.
|
|
if (!mHost) {
|
|
return;
|
|
}
|
|
|
|
if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
|
|
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
|
|
return;
|
|
}
|
|
|
|
if (!mPersistence->IsPersistentSessionId(sessionId)) {
|
|
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
|
|
return;
|
|
}
|
|
|
|
function<void(const uint8_t*, uint32_t)> success =
|
|
[self, sessionId, aPromiseId](const uint8_t* data, uint32_t size) {
|
|
self->PersistentSessionDataLoaded(aPromiseId, sessionId, data, size);
|
|
};
|
|
|
|
function<void()> failure = [self, aPromiseId] {
|
|
if (!self->mHost) {
|
|
return;
|
|
}
|
|
// As per the API described in ContentDecryptionModule_8
|
|
self->mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
|
|
};
|
|
|
|
ReadData(mHost, sessionId, std::move(success), std::move(failure));
|
|
}
|
|
|
|
void ClearKeySessionManager::PersistentSessionDataLoaded(
|
|
uint32_t aPromiseId, const string& aSessionId, const uint8_t* aKeyData,
|
|
uint32_t aKeyDataSize) {
|
|
CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded");
|
|
|
|
// Check that the SessionManager has not been shut down before we try and
|
|
// resolve any promises.
|
|
if (!mHost) {
|
|
return;
|
|
}
|
|
|
|
if (Contains(mSessions, aSessionId) ||
|
|
(aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) {
|
|
// As per the instructions in ContentDecryptionModule_8
|
|
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
|
|
return;
|
|
}
|
|
|
|
ClearKeySession* session =
|
|
new ClearKeySession(aSessionId, SessionType::kPersistentLicense);
|
|
|
|
mSessions[aSessionId] = session;
|
|
mLastSessionId = aSessionId;
|
|
|
|
uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
|
|
|
|
vector<KeyInformation> keyInfos;
|
|
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);
|
|
|
|
KeyInformation keyInfo = {};
|
|
keyInfo.key_id = &keyPairs.back().mKeyId[0];
|
|
keyInfo.key_id_size = keyPair.mKeyId.size();
|
|
keyInfo.status = KeyStatus::kUsable;
|
|
|
|
keyInfos.push_back(keyInfo);
|
|
}
|
|
|
|
mHost->OnSessionKeysChange(&aSessionId[0], aSessionId.size(), true,
|
|
keyInfos.data(), keyInfos.size());
|
|
|
|
mHost->OnResolveNewSessionPromise(aPromiseId, aSessionId.c_str(),
|
|
aSessionId.size());
|
|
}
|
|
|
|
void ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
|
|
const char* aSessionId,
|
|
uint32_t aSessionIdLength,
|
|
const uint8_t* aResponse,
|
|
uint32_t aResponseSize) {
|
|
CK_LOGD("ClearKeySessionManager::UpdateSession");
|
|
|
|
// Copy the method arguments so we can capture them in the lambda
|
|
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
|
vector<uint8_t> response(aResponse, aResponse + aResponseSize);
|
|
|
|
// Hold a reference to the SessionManager so it isn't released before we
|
|
// callback.
|
|
RefPtr<ClearKeySessionManager> self(this);
|
|
function<void()> deferrer = [self, aPromiseId, sessionId, response]() {
|
|
self->UpdateSession(aPromiseId, sessionId.data(), sessionId.size(),
|
|
response.data(), response.size());
|
|
};
|
|
|
|
// If we haven't fully loaded, defer calling this method
|
|
if (MaybeDeferTillInitialized(std::move(deferrer))) {
|
|
CK_LOGD("Deferring LoadSession");
|
|
return;
|
|
}
|
|
|
|
// Make sure the SessionManager has not been shutdown before we try and
|
|
// resolve any promises.
|
|
if (!mHost) {
|
|
return;
|
|
}
|
|
|
|
CK_LOGD("Updating session: %s", sessionId.c_str());
|
|
|
|
auto itr = mSessions.find(sessionId);
|
|
if (itr == mSessions.end() || !(itr->second)) {
|
|
CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
|
|
CK_LOGD("Unable to find session: %s", sessionId.c_str());
|
|
mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
|
|
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.");
|
|
CK_LOGD("Failed to parse response for session %s", sessionId.c_str());
|
|
|
|
mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
|
|
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.");
|
|
|
|
mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
|
|
nullptr, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
vector<KeyInformation> keyInfos;
|
|
for (size_t i = 0; i < keyPairs.size(); i++) {
|
|
KeyIdPair& keyPair = keyPairs[i];
|
|
mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
|
|
mKeyIds.insert(keyPair.mKeyId);
|
|
|
|
KeyInformation keyInfo = {};
|
|
keyInfo.key_id = &keyPair.mKeyId[0];
|
|
keyInfo.key_id_size = keyPair.mKeyId.size();
|
|
keyInfo.status = KeyStatus::kUsable;
|
|
|
|
keyInfos.push_back(keyInfo);
|
|
}
|
|
|
|
mHost->OnSessionKeysChange(aSessionId, aSessionIdLength, true,
|
|
keyInfos.data(), keyInfos.size());
|
|
|
|
if (session->Type() != SessionType::kPersistentLicense) {
|
|
mHost->OnResolvePromise(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);
|
|
|
|
function<void()> resolve = [self, aPromiseId]() {
|
|
if (!self->mHost) {
|
|
return;
|
|
}
|
|
self->mHost->OnResolvePromise(aPromiseId);
|
|
};
|
|
|
|
function<void()> reject = [self, aPromiseId]() {
|
|
if (!self->mHost) {
|
|
return;
|
|
}
|
|
|
|
static const char* message = "Couldn't store cenc key init data";
|
|
self->mHost->OnRejectPromise(aPromiseId,
|
|
Exception::kExceptionInvalidStateError, 0,
|
|
message, strlen(message));
|
|
};
|
|
|
|
WriteData(mHost, sessionId, keydata, std::move(resolve), std::move(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");
|
|
|
|
// Copy the sessionId into a string so we capture it properly.
|
|
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
|
// Hold a reference to the session manager, so it doesn't get deleted
|
|
// before we need to use it.
|
|
RefPtr<ClearKeySessionManager> self(this);
|
|
function<void()> deferrer = [self, aPromiseId, sessionId]() {
|
|
self->CloseSession(aPromiseId, sessionId.data(), sessionId.size());
|
|
};
|
|
|
|
// If we haven't loaded, call this method later.
|
|
if (MaybeDeferTillInitialized(std::move(deferrer))) {
|
|
CK_LOGD("Deferring CloseSession");
|
|
return;
|
|
}
|
|
|
|
// If DecryptingComplete has been called mHost will be null and we won't
|
|
// be able to resolve our promise.
|
|
if (!mHost) {
|
|
return;
|
|
}
|
|
|
|
auto itr = mSessions.find(sessionId);
|
|
if (itr == mSessions.end()) {
|
|
CK_LOGW("ClearKey CDM couldn't close non-existent session.");
|
|
mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
|
|
nullptr, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
ClearKeySession* session = itr->second;
|
|
assert(session);
|
|
|
|
ClearInMemorySessionData(session);
|
|
|
|
mHost->OnSessionClosed(aSessionId, aSessionIdLength);
|
|
mHost->OnResolvePromise(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");
|
|
|
|
// Copy the sessionId into a string so it can be captured for the lambda.
|
|
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
|
|
|
// Hold a reference to the SessionManager, so it isn't released before we
|
|
// try and use it.
|
|
RefPtr<ClearKeySessionManager> self(this);
|
|
function<void()> deferrer = [self, aPromiseId, sessionId]() {
|
|
self->RemoveSession(aPromiseId, sessionId.data(), sessionId.size());
|
|
};
|
|
|
|
// If we haven't fully loaded, defer calling this method.
|
|
if (MaybeDeferTillInitialized(std::move(deferrer))) {
|
|
CK_LOGD("Deferring RemoveSession");
|
|
return;
|
|
}
|
|
|
|
// Check that the SessionManager has not been shutdown before we try and
|
|
// resolve any promises.
|
|
if (!mHost) {
|
|
return;
|
|
}
|
|
|
|
auto itr = mSessions.find(sessionId);
|
|
if (itr == mSessions.end()) {
|
|
CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
|
|
|
|
mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
|
|
nullptr, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
ClearKeySession* session = itr->second;
|
|
assert(session);
|
|
string sid = session->Id();
|
|
bool isPersistent = session->Type() == SessionType::kPersistentLicense;
|
|
ClearInMemorySessionData(session);
|
|
|
|
if (!isPersistent) {
|
|
mHost->OnResolvePromise(aPromiseId);
|
|
return;
|
|
}
|
|
|
|
mPersistence->PersistentSessionRemoved(sid);
|
|
|
|
vector<uint8_t> emptyKeydata;
|
|
|
|
function<void()> resolve = [self, aPromiseId]() {
|
|
if (!self->mHost) {
|
|
return;
|
|
}
|
|
self->mHost->OnResolvePromise(aPromiseId);
|
|
};
|
|
|
|
function<void()> reject = [self, aPromiseId]() {
|
|
if (!self->mHost) {
|
|
return;
|
|
}
|
|
static const char* message = "Could not remove session";
|
|
self->mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
|
|
message, strlen(message));
|
|
};
|
|
|
|
WriteData(mHost, sessionId, emptyKeydata, std::move(resolve),
|
|
std::move(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");
|
|
mHost->OnRejectPromise(aPromiseId, Exception::kExceptionNotSupportedError, 0,
|
|
nullptr /* message */, 0 /* messageLen */);
|
|
}
|
|
|
|
Status ClearKeySessionManager::Decrypt(const InputBuffer_2& aBuffer,
|
|
DecryptedBlock* aDecryptedBlock) {
|
|
CK_LOGD("ClearKeySessionManager::Decrypt");
|
|
|
|
CK_LOGARRAY("Key: ", aBuffer.key_id, aBuffer.key_id_size);
|
|
|
|
Buffer* buffer = mHost->Allocate(aBuffer.data_size);
|
|
assert(buffer != nullptr);
|
|
assert(buffer->Data() != nullptr);
|
|
assert(buffer->Capacity() >= aBuffer.data_size);
|
|
|
|
memcpy(buffer->Data(), aBuffer.data, aBuffer.data_size);
|
|
|
|
Status status = Status::kSuccess;
|
|
// According to the comment `If |iv_size| = 0, the data is unencrypted.`
|
|
// Use iv_size to determine if the sample is encrypted.
|
|
if (aBuffer.iv_size != 0) {
|
|
status = mDecryptionManager->Decrypt(buffer->Data(), buffer->Size(),
|
|
CryptoMetaData(&aBuffer));
|
|
}
|
|
|
|
aDecryptedBlock->SetDecryptedBuffer(buffer);
|
|
aDecryptedBlock->SetTimestamp(aBuffer.timestamp);
|
|
|
|
return status;
|
|
}
|
|
|
|
void ClearKeySessionManager::DecryptingComplete() {
|
|
CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this);
|
|
|
|
for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
|
|
delete it->second;
|
|
}
|
|
mSessions.clear();
|
|
mLastSessionId = std::nullopt;
|
|
|
|
mDecryptionManager = nullptr;
|
|
mHost = nullptr;
|
|
|
|
Release();
|
|
}
|
|
|
|
bool ClearKeySessionManager::MaybeDeferTillInitialized(
|
|
function<void()>&& aMaybeDefer) {
|
|
if (mPersistence->IsLoaded()) {
|
|
return false;
|
|
}
|
|
|
|
mDeferredInitialize.emplace(std::move(aMaybeDefer));
|
|
return true;
|
|
}
|
|
|
|
void ClearKeySessionManager::OnQueryOutputProtectionStatus(
|
|
QueryResult aResult, uint32_t aLinkMask, uint32_t aOutputProtectionMask) {
|
|
MOZ_ASSERT(mHasOutstandingOutputProtectionQuery,
|
|
"Should only be called if a query is outstanding");
|
|
CK_LOGD("ClearKeySessionManager::OnQueryOutputProtectionStatus");
|
|
mHasOutstandingOutputProtectionQuery = false;
|
|
|
|
if (aResult == QueryResult::kQueryFailed) {
|
|
// Indicate the query failed. This can happen if we're in shutdown.
|
|
NotifyOutputProtectionStatus(KeyStatus::kInternalError);
|
|
return;
|
|
}
|
|
|
|
if (aLinkMask & OutputLinkTypes::kLinkTypeNetwork) {
|
|
NotifyOutputProtectionStatus(KeyStatus::kOutputRestricted);
|
|
return;
|
|
}
|
|
|
|
NotifyOutputProtectionStatus(KeyStatus::kUsable);
|
|
}
|
|
|
|
void ClearKeySessionManager::QueryOutputProtectionStatusIfNeeded() {
|
|
MOZ_ASSERT(
|
|
mHost,
|
|
"Should not query protection status if we're shutdown (mHost == null)!");
|
|
CK_LOGD(
|
|
"ClearKeySessionManager::UpdateOutputProtectionStatusAndQueryIfNeeded");
|
|
if (mLastOutputProtectionQueryTime.IsNull()) {
|
|
// We haven't perfomed a check yet, get a query going.
|
|
MOZ_ASSERT(
|
|
!mHasOutstandingOutputProtectionQuery,
|
|
"Shouldn't have an outstanding query if we haven't recorded a time");
|
|
QueryOutputProtectionStatusFromHost();
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(!mLastOutputProtectionQueryTime.IsNull(),
|
|
"Should have already handled the case where we don't yet have a "
|
|
"previous check time");
|
|
const mozilla::TimeStamp now = mozilla::TimeStamp::NowLoRes();
|
|
const mozilla::TimeDuration timeSinceQuery =
|
|
now - mLastOutputProtectionQueryTime;
|
|
|
|
// The time between output protection checks to the host. I.e. if this amount
|
|
// of time has passed since the last check with the host, another should be
|
|
// performed (provided the first check has been handled).
|
|
static const mozilla::TimeDuration kOutputProtectionQueryInterval =
|
|
mozilla::TimeDuration::FromSeconds(0.2);
|
|
// The number of kOutputProtectionQueryInterval intervals we can miss before
|
|
// we decide a check has failed. I.e. if this value is 2, if we have not
|
|
// received a reply to a check after kOutputProtectionQueryInterval * 2
|
|
// time, we consider the check failed.
|
|
constexpr uint32_t kMissedIntervalsBeforeFailure = 2;
|
|
// The length of time after which we will restrict output until we get a
|
|
// query response.
|
|
static const mozilla::TimeDuration kTimeToWaitBeforeFailure =
|
|
kOutputProtectionQueryInterval * kMissedIntervalsBeforeFailure;
|
|
|
|
if ((timeSinceQuery > kOutputProtectionQueryInterval) &&
|
|
!mHasOutstandingOutputProtectionQuery) {
|
|
// We don't have an outstanding query and enough time has passed we should
|
|
// query again.
|
|
QueryOutputProtectionStatusFromHost();
|
|
return;
|
|
}
|
|
|
|
if ((timeSinceQuery > kTimeToWaitBeforeFailure) &&
|
|
mHasOutstandingOutputProtectionQuery) {
|
|
// A reponse was not received fast enough, notify.
|
|
NotifyOutputProtectionStatus(KeyStatus::kInternalError);
|
|
}
|
|
}
|
|
|
|
void ClearKeySessionManager::QueryOutputProtectionStatusFromHost() {
|
|
MOZ_ASSERT(
|
|
mHost,
|
|
"Should not query protection status if we're shutdown (mHost == null)!");
|
|
CK_LOGD("ClearKeySessionManager::QueryOutputProtectionStatusFromHost");
|
|
if (mHost) {
|
|
mLastOutputProtectionQueryTime = mozilla::TimeStamp::NowLoRes();
|
|
mHost->QueryOutputProtectionStatus();
|
|
mHasOutstandingOutputProtectionQuery = true;
|
|
}
|
|
}
|
|
|
|
void ClearKeySessionManager::NotifyOutputProtectionStatus(KeyStatus aStatus) {
|
|
MOZ_ASSERT(aStatus == KeyStatus::kUsable ||
|
|
aStatus == KeyStatus::kOutputRestricted ||
|
|
aStatus == KeyStatus::kInternalError,
|
|
"aStatus should have an expected value");
|
|
CK_LOGD("ClearKeySessionManager::NotifyOutputProtectionStatus");
|
|
if (!mLastSessionId.has_value()) {
|
|
// If we don't have a session id, either because we're too early, or are
|
|
// shutting down, don't notify.
|
|
return;
|
|
}
|
|
|
|
string& lastSessionId = mLastSessionId.value();
|
|
|
|
// Use 'output-protection' as the key ID. This helps tests disambiguate key
|
|
// status updates related to this.
|
|
const uint8_t kKeyId[] = {'o', 'u', 't', 'p', 'u', 't', '-', 'p', 'r',
|
|
'o', 't', 'e', 'c', 't', 'i', 'o', 'n'};
|
|
KeyInformation keyInfo = {};
|
|
keyInfo.key_id = kKeyId;
|
|
keyInfo.key_id_size = std::size(kKeyId);
|
|
keyInfo.status = aStatus;
|
|
|
|
vector<KeyInformation> keyInfos;
|
|
keyInfos.push_back(keyInfo);
|
|
|
|
// At time of writing, Gecko's higher level handling doesn't use this arg.
|
|
// However, we set it to false to mimic Chromium's similar case. Since
|
|
// Clearkey is used to test the Chromium CDM path, it doesn't hurt to try
|
|
// and mimic their behaviour.
|
|
bool hasAdditionalUseableKey = false;
|
|
mHost->OnSessionKeysChange(lastSessionId.c_str(), lastSessionId.size(),
|
|
hasAdditionalUseableKey, keyInfos.data(),
|
|
keyInfos.size());
|
|
}
|