Bug 1109457 - Add persistent session to our ClearKey CDM. r=edwin

This commit is contained in:
Chris Pearce 2014-12-19 09:54:34 +13:00
parent 682a5b2f5c
commit a23d2bdf73
18 changed files with 2915 additions and 69 deletions

View File

@ -118,6 +118,7 @@ class GMPRecordClient {
class GMPRecordIterator {
public:
// Retrieve the name for the current record.
// aOutName is null terminated at character at index (*aOutNameLength).
// Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has
// reached the end.
virtual GMPErr GetName(const char ** aOutName, uint32_t * aOutNameLength) = 0;

View File

@ -3,9 +3,12 @@ const KEYSYSTEM_TYPE = "org.w3.clearkey";
function bail(message)
{
return function(err) {
if (err) {
message += "; " + String(err)
}
ok(false, message);
if (err) {
info(err);
info(String(err));
}
SimpleTest.finish();
}
@ -70,13 +73,13 @@ function Log(token, msg) {
info(TimeStamp(token) + " " + msg);
}
function UpdateSessionFunc(test, token) {
function UpdateSessionFunc(test, token, sessionType) {
return function(ev) {
var msgStr = ArrayBufferToString(ev.message);
var msg = JSON.parse(msgStr);
Log(token, "got message from CDM: " + msgStr);
is(msg.type, test.sessionType, TimeStamp(token) + " key session type should match");
is(msg.type, sessionType, TimeStamp(token) + " key session type should match");
ok(msg.kids, TimeStamp(token) + " message event should contain key ID array");
var outKeys = [];
@ -211,24 +214,24 @@ function SetupEME(test, token, params)
.then(function(keySystemAccess) {
return keySystemAccess.createMediaKeys();
}, bail(token + " Failed to request key system access."))
.then(function(mediaKeys) {
Log(token, "created MediaKeys object ok");
mediaKeys.sessions = [];
return v.setMediaKeys(mediaKeys);
}, bail("failed to create MediaKeys object"))
.then(function() {
Log(token, "set MediaKeys on <video> element ok");
var session = v.mediaKeys.createSession(test.sessionType);
var sessionType = (params && params.sessionType) ? params.sessionType : "temporary";
var session = v.mediaKeys.createSession(sessionType);
if (params && params.onsessioncreated) {
params.onsessioncreated(session);
}
session.addEventListener("message", UpdateSessionFunc(test, token));
session.addEventListener("message", UpdateSessionFunc(test, token, sessionType));
return session.generateRequest(ev.initDataType, ev.initData);
}, onSetKeysFail)
.then(function() {
Log(token, "generated request");
}, bail(token + " Failed to request key system access2."));

View File

@ -364,6 +364,8 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1082984
[test_eme_canvas_blocked.html]
skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
[test_eme_persistent_sessions.html]
skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
[test_eme_playback.html]
skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
[test_eme_requestKeySystemAccess.html]

View File

@ -0,0 +1,156 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test Encrypted Media Extensions</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="manifest.js"></script>
<script type="text/javascript" src="eme.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
var manager = new MediaTestManager;
function UsableKeyIdsMatch(usableKeyIds, expectedKeyIds) {
var hexKeyIds = usableKeyIds.map(function(keyId) {
return Base64ToHex(window.btoa(ArrayBufferToString(keyId)));
}).sort();
var expected = Object.keys(expectedKeyIds).sort();
if (expected.length != hexKeyIds.length) {
return false;
}
for (var i = 0; i < hexKeyIds.length; i++) {
if (hexKeyIds[i] != expected[i]){
return false;
}
}
return true;
}
function AwaitAllKeysUsable(session, keys, token) {
return new Promise(function(resolve, reject) {
function listener(event) {
session.getUsableKeyIds().then(function(usableKeyIds) {
var u = UsableKeyIdsMatch(usableKeyIds, keys);
if (UsableKeyIdsMatch(usableKeyIds, keys)) {
Log(token, "resolving AwaitAllKeysUsable promise");
session.removeEventListener("keyschange", listener);
resolve();
}
}, bail(token + " failed to get usableKeyIds"));
}
session.addEventListener("keyschange", listener);
});
}
function AwaitAllKeysNotUsable(session, token) {
return new Promise(function(resolve, reject) {
function listener(event) {
session.getUsableKeyIds().then(function(usableKeyIds) {
if (usableKeyIds.length == 0) {
session.removeEventListener("keyschange", listener);
resolve();
}
}, bail(token + " failed to get usableKeyIds"));
}
session.addEventListener("keyschange", listener);
});
}
function startTest(test, token)
{
manager.started(token);
var recreatedSession; // will have remove() called on it.
var keySystemAccess;
var v = SetupEME(test, token,
{
onsessioncreated: function(session) {
Log(token, "Session created");
var sessionId;
initialSession = session;
// Once the session has loaded and has all its keys usable, close
// all sessions without calling remove() on them.
AwaitAllKeysUsable(session, test.keys, token).then(
function() {
sessionId = session.sessionId;
Log(token, "Closing session with id=" + sessionId);
session.close();
}
);
// Once the session is closed, reload the MediaKeys and reload the session
session.closed.then(function() {
return navigator.requestMediaKeySystemAccess(KEYSYSTEM_TYPE)
}, bail("close promise rejected"))
.then(function(requestedKeySystemAccess) {
keySystemAccess = requestedKeySystemAccess;
return keySystemAccess.createMediaKeys();
}, bail(token + " Failed to request key system access."))
.then(function(mediaKeys) {
Log(token, "re-created MediaKeys object ok");
recreatedSession = mediaKeys.createSession("persistent");
Log(token, "Created recreatedSession, loading sessionId=" + sessionId);
return Promise.all([AwaitAllKeysUsable(recreatedSession, test.keys, token), recreatedSession.load(sessionId)]);
}, bail(token + " failed to create mediaKeys"))
.then(function() {
Log(token, "re-loaded persistent session, all keys still usable");
return Promise.all([AwaitAllKeysNotUsable(recreatedSession, token), recreatedSession.remove()]);
}, bail(token + " failed to get reload session or keys"))
.then(function() {
Log(token, "removed session, all keys unusable.");
// Attempt to recreate the session, the attempt should fail.
return keySystemAccess.createMediaKeys();
}, bail(token + " failed to remove session"))
.then(function(mediaKeys) {
Log(token, "re-re-created MediaKeys object ok");
// Trying to load the removed persistent session should fail.
return mediaKeys.createSession("persistent").load(sessionId);
}, bail(token + " failed to create mediaKeys"))
.then(function(suceeded) {
is(suceeded, false, token + " we expect the third session creation to fail, as the session should have been removed.");
manager.finished(token);
}, bail(token + " failure to load session."));
},
sessionType: "persistent",
}
);
v.addEventListener("error", bail(token + " got error event"));
LoadTest(test, v, token);
}
function beginTest() {
manager.runTests(gEMETests, startTest);
}
var prefs = [
[ "media.mediasource.enabled", true ],
[ "media.mediasource.mp4.enabled", true ],
];
if (/Linux/.test(navigator.userAgent) ||
!document.createElement('video').canPlayType("video/mp4")) {
// XXX remove once we have mp4 PlatformDecoderModules on all platforms.
prefs.push([ "media.fragmented-mp4.exposed", true ]);
prefs.push([ "media.fragmented-mp4.use-blank-decoder", true ]);
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
</script>
</pre>
</body>
</html>

View File

@ -2,12 +2,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <sstream>
#include <stdint.h>
#include <stdio.h>
#include "ClearKeyDecryptionManager.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "ClearKeyPersistence.h"
#include "gmp-task-utils.h"
#include "mozilla/Assertions.h"
#include "mozilla/NullPtr.h"
@ -15,7 +17,6 @@
using namespace mozilla;
using namespace std;
class ClearKeyDecryptor
{
public:
@ -29,6 +30,8 @@ public:
uint32_t AddRef();
uint32_t Release();
const Key& DecryptionKey() const { return mKey; }
private:
struct DecryptTask : public GMPTask
{
@ -79,15 +82,22 @@ private:
Key mKey;
};
ClearKeyDecryptionManager::ClearKeyDecryptionManager()
{
CK_LOGD("ClearKeyDecryptionManager ctor");
// The ClearKeyDecryptionManager maintains a self reference which is
// removed when the host is finished with the interface and calls
// DecryptingComplete(). We make ClearKeyDecryptionManager refcounted so
// that the tasks to that we dispatch to call functions on it won't end up
// derefing a null reference after DecryptingComplete() is called.
AddRef();
}
ClearKeyDecryptionManager::~ClearKeyDecryptionManager()
{
CK_LOGD("ClearKeyDecryptionManager dtor");
MOZ_ASSERT(mRefCount == 1);
}
void
@ -97,19 +107,7 @@ ClearKeyDecryptionManager::Init(GMPDecryptorCallback* aCallback)
mCallback = aCallback;
mCallback->SetCapabilities(GMP_EME_CAP_DECRYPT_AUDIO |
GMP_EME_CAP_DECRYPT_VIDEO);
}
static string
GetNewSessionId()
{
static uint32_t sNextSessionId = 0;
string sessionId;
stringstream ss;
ss << ++sNextSessionId;
ss >> sessionId;
return sessionId;
ClearKeyPersistence::EnsureInitialized();
}
void
@ -129,10 +127,18 @@ ClearKeyDecryptionManager::CreateSession(uint32_t aPromiseId,
return;
}
string sessionId = GetNewSessionId();
if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
aPromiseId,
aInitData,
aInitDataSize,
aSessionType)) {
return;
}
string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType);
MOZ_ASSERT(mSessions.find(sessionId) == mSessions.end());
ClearKeySession* session = new ClearKeySession(sessionId, mCallback);
ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
session->Init(aPromiseId, aInitData, aInitDataSize);
mSessions[sessionId] = session;
@ -157,7 +163,7 @@ ClearKeyDecryptionManager::CreateSession(uint32_t aPromiseId,
// Send a request for needed key data.
string request;
ClearKeyUtils::MakeKeyRequest(neededKeys, request);
ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
mCallback->SessionMessage(&sessionId[0], sessionId.length(),
(uint8_t*)&request[0], request.length(),
"" /* destination url */, 0);
@ -168,10 +174,73 @@ ClearKeyDecryptionManager::LoadSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
// TODO implement "persistent" sessions.
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
CK_LOGD("ClearKeyDecryptionManager::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
ClearKeyDecryptionManager::PersistentSessionDataLoaded(GMPErr aStatus,
uint32_t aPromiseId,
const string& aSessionId,
const uint8_t* aKeyData,
uint32_t aKeyDataSize)
{
if (GMP_FAILED(aStatus) ||
Contains(mSessions, aSessionId) ||
(aKeyDataSize % (2 * CLEARKEY_KEY_LEN)) != 0) {
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
return;
}
ClearKeySession* session = new ClearKeySession(aSessionId,
mCallback,
kGMPPersistentSession);
mSessions[aSessionId] = session;
// TODO: currently we have to resolve the load-session promise before we
// can mark the keys as usable. We should really do this before marking
// the keys usable, but we need to fix Gecko first.
mCallback->ResolveLoadSessionPromise(aPromiseId, true);
uint32_t numKeys = aKeyDataSize / (2 * CLEARKEY_KEY_LEN);
for (uint32_t i = 0; i < numKeys; i ++) {
const uint8_t* base = aKeyData + 2 * CLEARKEY_KEY_LEN * i;
KeyId keyId(base, base + CLEARKEY_KEY_LEN);
MOZ_ASSERT(keyId.size() == CLEARKEY_KEY_LEN);
Key key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
MOZ_ASSERT(key.size() == CLEARKEY_KEY_LEN);
session->AddKeyId(keyId);
if (!Contains(mDecryptors, keyId)) {
mDecryptors[keyId] = new ClearKeyDecryptor(mCallback, key);
}
mDecryptors[keyId]->AddRef();
mCallback->KeyIdUsable(aSessionId.c_str(), aSessionId.size(),
&keyId[0], keyId.size());
}
}
void
@ -184,20 +253,21 @@ ClearKeyDecryptionManager::UpdateSession(uint32_t aPromiseId,
CK_LOGD("ClearKeyDecryptionManager::UpdateSession");
string sessionId(aSessionId, aSessionId + aSessionIdLength);
if (mSessions.find(sessionId) == mSessions.end() || !mSessions[sessionId]) {
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;
// Parse the response for any (key ID, key) pairs.
vector<KeyIdPair> keyPairs;
if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs)) {
if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
mCallback->RejectPromise(aPromiseId, kGMPInvalidAccessError, nullptr, 0);
return;
}
mCallback->ResolvePromise(aPromiseId);
for (auto it = keyPairs.begin(); it != keyPairs.end(); it++) {
KeyId& keyId = it->mKeyId;
@ -210,6 +280,43 @@ ClearKeyDecryptionManager::UpdateSession(uint32_t aPromiseId,
mDecryptors[keyId]->AddRef();
}
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
ClearKeyDecryptionManager::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 (!Contains(mDecryptors, keyId)) {
continue;
}
MOZ_ASSERT(keyId.size() == CLEARKEY_KEY_LEN);
aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end());
const Key& key = mDecryptors[keyId]->DecryptionKey();
MOZ_ASSERT(key.size() == CLEARKEY_KEY_LEN);
aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end());
}
}
void
@ -220,25 +327,39 @@ ClearKeyDecryptionManager::CloseSession(uint32_t aPromiseId,
CK_LOGD("ClearKeyDecryptionManager::CloseSession");
string sessionId(aSessionId, aSessionId + aSessionIdLength);
ClearKeySession* session = mSessions[sessionId];
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;
MOZ_ASSERT(session);
const vector<KeyId>& keyIds = session->GetKeyIds();
ClearInMemorySessionData(session);
mCallback->ResolvePromise(aPromiseId);
mCallback->SessionClosed(aSessionId, aSessionIdLength);
}
void
ClearKeyDecryptionManager::ClearInMemorySessionData(ClearKeySession* aSession)
{
MOZ_ASSERT(aSession);
const vector<KeyId>& keyIds = aSession->GetKeyIds();
for (auto it = keyIds.begin(); it != keyIds.end(); it++) {
MOZ_ASSERT(mDecryptors.find(*it) != mDecryptors.end());
if (!mDecryptors[*it]->Release()) {
mDecryptors.erase(*it);
mCallback->KeyIdNotUsable(aSessionId, aSessionIdLength,
mCallback->KeyIdNotUsable(aSession->Id().c_str(), aSession->Id().size(),
&(*it)[0], it->size());
}
}
mSessions.erase(sessionId);
delete session;
mCallback->ResolvePromise(aPromiseId);
mSessions.erase(aSession->Id());
delete aSession;
}
void
@ -246,10 +367,39 @@ ClearKeyDecryptionManager::RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
// TODO implement "persistent" sessions.
CK_LOGD("ClearKeyDecryptionManager::RemoveSession");
mCallback->RejectPromise(aPromiseId, kGMPInvalidAccessError,
nullptr /* message */, 0 /* messageLen */);
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;
MOZ_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
@ -270,8 +420,11 @@ ClearKeyDecryptionManager::Decrypt(GMPBuffer* aBuffer,
CK_LOGD("ClearKeyDecryptionManager::Decrypt");
KeyId keyId(aMetadata->KeyId(), aMetadata->KeyId() + aMetadata->KeyIdSize());
if (mDecryptors.find(keyId) == mDecryptors.end() || !mDecryptors[keyId]) {
auto itr = mDecryptors.find(keyId);
if (itr == mDecryptors.end() || !(itr->second)) {
CK_LOGD("ClearKeyDecryptionManager::Decrypt GMPNoKeyErr");
mCallback->Decrypted(aBuffer, GMPNoKeyErr);
return;
}
mDecryptors[keyId]->QueueDecrypt(aBuffer, aMetadata);
@ -290,7 +443,7 @@ ClearKeyDecryptionManager::DecryptingComplete()
delete it->second;
}
delete this;
Release();
}
void

View File

@ -13,14 +13,14 @@
#include "ClearKeyUtils.h"
#include "gmp-api/gmp-decryption.h"
#include "ScopedNSSTypes.h"
#include "RefCounted.h"
class ClearKeyDecryptor;
class ClearKeyDecryptionManager MOZ_FINAL : public GMPDecryptor
, public RefCounted
{
public:
ClearKeyDecryptionManager();
~ClearKeyDecryptionManager();
virtual void Init(GMPDecryptorCallback* aCallback) MOZ_OVERRIDE;
@ -58,7 +58,18 @@ public:
virtual void DecryptingComplete() MOZ_OVERRIDE;
void PersistentSessionDataLoaded(GMPErr aStatus,
uint32_t aPromiseId,
const std::string& aSessionId,
const uint8_t* aKeyData,
uint32_t aKeyDataSize);
private:
~ClearKeyDecryptionManager();
void ClearInMemorySessionData(ClearKeySession* aSession);
void Serialize(const ClearKeySession* aSession, std::vector<uint8_t>& aOutKeyData);
GMPDecryptorCallback* mCallback;
std::map<KeyId, ClearKeyDecryptor*> mDecryptors;

View File

@ -0,0 +1,236 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ClearKeyPersistence.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "ClearKeyDecryptionManager.h"
#include "mozilla/RefPtr.h"
#include <stdint.h>
#include <set>
#include <vector>
#include <sstream>
using namespace mozilla;
using namespace std;
// Whether we've loaded the persistent session ids from GMPStorage yet.
enum PersistentKeyState {
UNINITIALIZED,
LOADING,
LOADED
};
static PersistentKeyState sPersistentKeyState = UNINITIALIZED;
// Set of session Ids of the persistent sessions created or residing in
// storage.
static set<uint32_t> sPersistentSessionIds;
static vector<GMPTask*> sTasksBlockedOnSessionIdLoad;
static void
ReadAllRecordsFromIterator(GMPRecordIterator* aRecordIterator,
void* aUserArg,
GMPErr aStatus)
{
MOZ_ASSERT(sPersistentKeyState == LOADING);
if (GMP_SUCCEEDED(aStatus)) {
// Extract the record names which are valid uint32_t's; they're
// the persistent session ids.
const char* name = nullptr;
uint32_t len = 0;
while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) {
if (ClearKeyUtils::IsValidSessionId(name, len)) {
MOZ_ASSERT(name[len] == 0);
sPersistentSessionIds.insert(atoi(name));
}
aRecordIterator->NextRecord();
}
}
sPersistentKeyState = LOADED;
aRecordIterator->Close();
for (size_t i = 0; i < sTasksBlockedOnSessionIdLoad.size(); i++) {
sTasksBlockedOnSessionIdLoad[i]->Run();
sTasksBlockedOnSessionIdLoad[i]->Destroy();
}
sTasksBlockedOnSessionIdLoad.clear();
}
/* static */ void
ClearKeyPersistence::EnsureInitialized()
{
if (sPersistentKeyState == UNINITIALIZED) {
sPersistentKeyState = LOADING;
if (GMP_FAILED(EnumRecordNames(&ReadAllRecordsFromIterator))) {
sPersistentKeyState = LOADED;
}
}
}
/* static */ string
ClearKeyPersistence::GetNewSessionId(GMPSessionType aSessionType)
{
static uint32_t sNextSessionId = 1;
// Ensure we don't re-use a session id that was persisted.
while (Contains(sPersistentSessionIds, sNextSessionId)) {
sNextSessionId++;
}
string sessionId;
stringstream ss;
ss << sNextSessionId;
ss >> sessionId;
if (aSessionType == kGMPPersistentSession) {
sPersistentSessionIds.insert(sNextSessionId);
}
sNextSessionId++;
return sessionId;
}
class CreateSessionTask : public GMPTask {
public:
CreateSessionTask(ClearKeyDecryptionManager* aTarget,
uint32_t aPromiseId,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
: mTarget(aTarget)
, mPromiseId(aPromiseId)
, mSessionType(aSessionType)
{
mInitData.insert(mInitData.end(),
aInitData,
aInitData + aInitDataSize);
}
virtual void Run() MOZ_OVERRIDE {
mTarget->CreateSession(mPromiseId,
"cenc",
strlen("cenc"),
&mInitData.front(),
mInitData.size(),
mSessionType);
}
virtual void Destroy() MOZ_OVERRIDE {
delete this;
}
private:
RefPtr<ClearKeyDecryptionManager> mTarget;
uint32_t mPromiseId;
vector<uint8_t> mInitData;
GMPSessionType mSessionType;
};
/* static */ bool
ClearKeyPersistence::DeferCreateSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
uint32_t aPromiseId,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
{
if (sPersistentKeyState >= LOADED) {
return false;
}
GMPTask* t = new CreateSessionTask(aInstance,
aPromiseId,
aInitData,
aInitDataSize,
aSessionType);
sTasksBlockedOnSessionIdLoad.push_back(t);
return true;
}
class LoadSessionTask : public GMPTask {
public:
LoadSessionTask(ClearKeyDecryptionManager* aTarget,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
: mTarget(aTarget)
, mPromiseId(aPromiseId)
, mSessionId(aSessionId, aSessionId + aSessionIdLength)
{
}
virtual void Run() MOZ_OVERRIDE {
mTarget->LoadSession(mPromiseId,
mSessionId.c_str(),
mSessionId.size());
}
virtual void Destroy() MOZ_OVERRIDE {
delete this;
}
private:
RefPtr<ClearKeyDecryptionManager> mTarget;
uint32_t mPromiseId;
string mSessionId;
};
/* static */ bool
ClearKeyPersistence::DeferLoadSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
if (sPersistentKeyState >= LOADED) {
return false;
}
GMPTask* t = new LoadSessionTask(aInstance,
aPromiseId,
aSessionId,
aSessionIdLength);
sTasksBlockedOnSessionIdLoad.push_back(t);
return true;
}
/* static */ bool
ClearKeyPersistence::IsPersistentSessionId(const string& aSessionId)
{
return Contains(sPersistentSessionIds, atoi(aSessionId.c_str()));
}
class LoadSessionFromKeysTask : public ReadContinuation {
public:
LoadSessionFromKeysTask(ClearKeyDecryptionManager* aTarget,
const string& aSessionId,
uint32_t aPromiseId)
: mTarget(aTarget)
, mSessionId(aSessionId)
, mPromiseId(aPromiseId)
{
}
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aLength) MOZ_OVERRIDE
{
mTarget->PersistentSessionDataLoaded(aStatus, mPromiseId, mSessionId, aData, aLength);
}
private:
RefPtr<ClearKeyDecryptionManager> mTarget;
string mSessionId;
uint32_t mPromiseId;
};
/* static */ void
ClearKeyPersistence::LoadSessionData(ClearKeyDecryptionManager* aInstance,
const string& aSid,
uint32_t aPromiseId)
{
LoadSessionFromKeysTask* loadTask =
new LoadSessionFromKeysTask(aInstance, aSid, aPromiseId);
ReadData(aSid, loadTask);
}
/* static */ void
ClearKeyPersistence::PersistentSessionRemoved(const string& aSessionId)
{
sPersistentSessionIds.erase(atoi(aSessionId.c_str()));
}

View File

@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __ClearKeyPersistence_h__
#define __ClearKeyPersistence_h__
#include <string>
#include "gmp-decryption.h"
class ClearKeyDecryptionManager;
class ClearKeyPersistence {
public:
static void EnsureInitialized();
static std::string GetNewSessionId(GMPSessionType aSessionType);
static bool DeferCreateSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
uint32_t aPromiseId,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType);
static bool DeferLoadSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength);
static bool IsPersistentSessionId(const std::string& aSid);
static void LoadSessionData(ClearKeyDecryptionManager* aInstance,
const std::string& aSid,
uint32_t aPromiseId);
static void PersistentSessionRemoved(const std::string& aSid);
};
#endif // __ClearKeyPersistence_h__

View File

@ -4,6 +4,8 @@
#include "ClearKeySession.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "gmp-task-utils.h"
#include "gmp-api/gmp-decryption.h"
#include "mozilla/Endian.h"
@ -12,9 +14,11 @@
using namespace mozilla;
ClearKeySession::ClearKeySession(const std::string& aSessionId,
GMPDecryptorCallback* aCallback)
GMPDecryptorCallback* aCallback,
GMPSessionType aSessionType)
: mSessionId(aSessionId)
, mCallback(aCallback)
, mSessionType(aSessionType)
{
CK_LOGD("ClearKeySession ctor %p", this);
}
@ -36,7 +40,18 @@ ClearKeySession::Init(uint32_t aPromiseId,
mCallback->RejectPromise(aPromiseId, kGMPAbortError, message, strlen(message));
return;
}
mCallback->ResolveNewSessionPromise(aPromiseId,
mSessionId.data(), mSessionId.length());
}
GMPSessionType
ClearKeySession::Type() const
{
return mSessionType;
}
void
ClearKeySession::AddKeyId(const KeyId& aKeyId)
{
mKeyIds.push_back(aKeyId);
}

View File

@ -6,34 +6,39 @@
#define __ClearKeySession_h__
#include "ClearKeyUtils.h"
#include "gmp-decryption.h"
class GMPBuffer;
class GMPDecryptorCallback;
class GMPDecryptorHost;
class GMPEncryptedBufferMetadata;
/**
* Currently useless; will be fleshed out later with support for persistent
* key sessions.
*/
class ClearKeySession
{
public:
ClearKeySession(const std::string& aSessionId,
GMPDecryptorCallback* aCallback);
explicit ClearKeySession(const std::string& aSessionId,
GMPDecryptorCallback* aCallback,
GMPSessionType aSessionType);
~ClearKeySession();
const std::vector<KeyId>& GetKeyIds() { return mKeyIds; }
const std::vector<KeyId>& GetKeyIds() const { return mKeyIds; }
void Init(uint32_t aPromiseId,
const uint8_t* aInitData, uint32_t aInitDataSize);
GMPSessionType Type() const;
void AddKeyId(const KeyId& aKeyId);
const std::string Id() const { return mSessionId; }
private:
std::string mSessionId;
const std::string mSessionId;
std::vector<KeyId> mKeyIds;
GMPDecryptorCallback* mCallback;
const GMPSessionType mSessionType;
};
#endif // __ClearKeySession_h__

View File

@ -0,0 +1,174 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ClearKeyStorage.h"
#include "ClearKeyUtils.h"
#include "gmp-task-utils.h"
#include "mozilla/Assertions.h"
#include "mozilla/NullPtr.h"
#include "mozilla/ArrayUtils.h"
#include <vector>
static GMPErr
RunOnMainThread(GMPTask* aTask)
{
return GetPlatform()->runonmainthread(aTask);
}
class WriteRecordClient : public GMPRecordClient {
public:
GMPErr Init(GMPRecord* aRecord,
GMPTask* aOnSuccess,
GMPTask* aOnFailure,
const uint8_t* aData,
uint32_t aDataSize) {
mRecord = aRecord;
mOnSuccess = aOnSuccess;
mOnFailure = aOnFailure;
mData.insert(mData.end(), aData, aData + aDataSize);
return mRecord->Open();
}
virtual void OpenComplete(GMPErr aStatus) MOZ_OVERRIDE {
if (GMP_FAILED(aStatus) ||
GMP_FAILED(mRecord->Write(&mData.front(), mData.size()))) {
RunOnMainThread(mOnFailure);
mOnSuccess->Destroy();
}
}
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aDataSize) MOZ_OVERRIDE {
MOZ_ASSERT(false, "Should not reach here.");
}
virtual void WriteComplete(GMPErr aStatus) MOZ_OVERRIDE {
// Note: Call Close() before running continuation, in case the
// continuation tries to open the same record; if we call Close()
// after running the continuation, the Close() call will arrive
// just after the Open() call succeeds, immediately closing the
// record we just opened.
mRecord->Close();
if (GMP_SUCCEEDED(aStatus)) {
RunOnMainThread(mOnSuccess);
mOnFailure->Destroy();
} else {
RunOnMainThread(mOnFailure);
mOnSuccess->Destroy();
}
delete this;
}
private:
GMPRecord* mRecord;
GMPTask* mOnSuccess;
GMPTask* mOnFailure;
std::vector<uint8_t> mData;
};
GMPErr
OpenRecord(const char* aName,
uint32_t aNameLength,
GMPRecord** aOutRecord,
GMPRecordClient* aClient)
{
return GetPlatform()->createrecord(aName, aNameLength, aOutRecord, aClient);
}
void
StoreData(const std::string& aRecordName,
const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure)
{
GMPRecord* record;
WriteRecordClient* client = new WriteRecordClient();
if (GMP_FAILED(OpenRecord(aRecordName.c_str(),
aRecordName.size(),
&record,
client)) ||
GMP_FAILED(client->Init(record,
aOnSuccess,
aOnFailure,
&aData.front(),
aData.size()))) {
RunOnMainThread(aOnFailure);
aOnSuccess->Destroy();
}
}
class ReadRecordClient : public GMPRecordClient {
public:
ReadRecordClient()
: mRecord(nullptr)
, mContinuation(nullptr)
{}
~ReadRecordClient() {
delete mContinuation;
}
GMPErr Init(GMPRecord* aRecord,
ReadContinuation* aContinuation) {
mRecord = aRecord;
mContinuation = aContinuation;
return mRecord->Open();
}
virtual void OpenComplete(GMPErr aStatus) MOZ_OVERRIDE {
auto err = mRecord->Read();
if (GMP_FAILED(err)) {
mContinuation->ReadComplete(err, nullptr, 0);
delete this;
}
}
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aDataSize) MOZ_OVERRIDE {
// Note: Call Close() before running continuation, in case the
// continuation tries to open the same record; if we call Close()
// after running the continuation, the Close() call will arrive
// just after the Open() call succeeds, immediately closing the
// record we just opened.
mRecord->Close();
mContinuation->ReadComplete(GMPNoErr, aData, aDataSize);
delete this;
}
virtual void WriteComplete(GMPErr aStatus) MOZ_OVERRIDE {
MOZ_ASSERT(false, "Should not reach here.");
}
private:
GMPRecord* mRecord;
ReadContinuation* mContinuation;
};
void
ReadData(const std::string& aRecordName,
ReadContinuation* aContinuation)
{
MOZ_ASSERT(aContinuation);
GMPRecord* record;
ReadRecordClient* client = new ReadRecordClient();
auto err = OpenRecord(aRecordName.c_str(),
aRecordName.size(),
&record,
client);
if (GMP_FAILED(err) ||
GMP_FAILED(client->Init(record, aContinuation))) {
aContinuation->ReadComplete(err, nullptr, 0);
delete aContinuation;
}
}
GMPErr
EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc)
{
return GetPlatform()->getrecordenumerator(aRecvIteratorFunc, nullptr);
}

View File

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __ClearKeyStorage_h__
#define __ClearKeyStorage_h__
#include "gmp-errors.h"
#include "gmp-platform.h"
#include <string>
#include <vector>
#include <stdint.h>
class GMPTask;
// Responsible for ensuring that both aOnSuccess and aOnFailure are destroyed.
void StoreData(const std::string& aRecordName,
const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure);
class ReadContinuation {
public:
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aLength) = 0;
virtual ~ReadContinuation() {}
};
// Deletes aContinuation after running it to report the result.
void ReadData(const std::string& aSessionId,
ReadContinuation* aContinuation);
GMPErr EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc);
#endif // __ClearKeyStorage_h__

View File

@ -177,7 +177,8 @@ ClearKeyUtils::ParseInitData(const uint8_t* aInitData, uint32_t aInitDataSize,
/* static */ void
ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
string& aOutRequest)
string& aOutRequest,
GMPSessionType aSessionType)
{
MOZ_ASSERT(aKeyIDs.size() && aOutRequest.empty());
@ -195,9 +196,10 @@ ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
aOutRequest.append("\"");
}
aOutRequest.append("], \"type\":");
// TODO implement "persistent" session type
aOutRequest.append("\"temporary\"");
aOutRequest.append("}");
aOutRequest.append("\"");
aOutRequest.append(SessionTypeToString(aSessionType));
aOutRequest.append("\"}");
}
#define EXPECT_SYMBOL(CTX, X) do { \
@ -509,7 +511,8 @@ ParseKeys(ParserContext& aCtx, vector<KeyIdPair>& aOutKeys)
/* static */ bool
ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
vector<KeyIdPair>& aOutKeys)
vector<KeyIdPair>& aOutKeys,
GMPSessionType aSessionType)
{
ParserContext ctx;
ctx.mIter = aKeyData;
@ -531,8 +534,7 @@ ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
// Consume type string.
string type;
if (!GetNextLabel(ctx, type)) return false;
// XXX todo support "persistent" session type
if (type != "temporary") {
if (type != SessionTypeToString(aSessionType)) {
return false;
}
} else {
@ -553,3 +555,32 @@ ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
return true;
}
/* static */ const char*
ClearKeyUtils::SessionTypeToString(GMPSessionType aSessionType)
{
switch (aSessionType) {
case kGMPTemporySession: return "temporary";
case kGMPPersistentSession: return "persistent";
default: {
MOZ_ASSERT(false, "Should not reach here.");
return "invalid";
}
}
}
/* static */ bool
ClearKeyUtils::IsValidSessionId(const char* aBuff, uint32_t aLength)
{
if (aLength > 10) {
// 10 is the max number of characters in UINT32_MAX when
// represented as a string; ClearKey session ids are integers.
return false;
}
for (uint32_t i = 0; i < aLength; i++) {
if (!isdigit(aBuff[i])) {
return false;
}
}
return true;
}

View File

@ -8,6 +8,7 @@
#include <stdint.h>
#include <string>
#include <vector>
#include "gmp-decryption.h"
#define CLEARKEY_KEY_LEN ((size_t)16)
@ -44,10 +45,22 @@ public:
std::vector<Key>& aOutKeys);
static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
std::string& aOutRequest);
std::string& aOutRequest,
GMPSessionType aSessionType);
static bool ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
std::vector<KeyIdPair>& aOutKeys);
std::vector<KeyIdPair>& aOutKeys,
GMPSessionType aSessionType);
static const char* SessionTypeToString(GMPSessionType aSessionType);
static bool IsValidSessionId(const char* aBuff, uint32_t aLength);
};
template<class Container, class Element>
inline bool
Contains(const Container& aContainer, const Element& aElement)
{
return aContainer.find(aElement) != aContainer.end();
}
#endif // __ClearKeyUtils_h__

View File

@ -0,0 +1,34 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __RefCount_h__
#define __RefCount_h__
// Note: Not thread safe!
class RefCounted {
public:
void AddRef() {
++mRefCount;
}
void Release() {
if (mRefCount == 1) {
delete this;
} else {
--mRefCount;
}
}
protected:
RefCounted()
: mRefCount(0)
{
}
virtual ~RefCounted()
{
}
uint32_t mRefCount;
};
#endif // __RefCount_h__

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
// Original author: ekr@rtfm.com
#ifndef gmp_task_utils_h_
#define gmp_task_utils_h_
#include "gmp-platform.h"
class gmp_task_args_base : public GMPTask {
public:
virtual void Destroy() { delete this; }
virtual void Run() = 0;
};
// The generated file contains four major function templates
// (in variants for arbitrary numbers of arguments up to 10,
// which is why it is machine generated). The four templates
// are:
//
// WrapTask(o, m, ...) -- wraps a member function m of an object ptr o
// WrapTaskRet(o, m, ..., r) -- wraps a member function m of an object ptr o
// the function returns something that can
// be assigned to *r
// WrapTaskNM(f, ...) -- wraps a function f
// WrapTaskNMRet(f, ..., r) -- wraps a function f that returns something
// that can be assigned to *r
//
// All of these template functions return a GMPTask* which can be passed
// to DispatchXX().
#include "gmp-task-utils-generated.h"
#endif // gmp_task_utils_h_

View File

@ -10,7 +10,9 @@ FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
UNIFIED_SOURCES += [
'ClearKeyDecryptionManager.cpp',
'ClearKeyPersistence.cpp',
'ClearKeySession.cpp',
'ClearKeyStorage.cpp',
'ClearKeyUtils.cpp',
'gmp-clearkey.cpp',
'openaes/oaes_lib.c',