Bug 1228461 - Implement keyids initData type for ClearKey. r=gerald

This commit is contained in:
Chris Pearce 2015-11-27 17:13:40 +13:00
Родитель ad73151891
Коммит efc8956d13
10 изменённых файлов: 232 добавлений и 14 удалений

Просмотреть файл

@ -443,6 +443,9 @@ GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
for (const nsString& candidate : aCandidate.mInitDataTypes.Value()) {
if (candidate.EqualsLiteral("cenc")) {
initDataTypes.AppendElement(candidate);
} else if (candidate.EqualsLiteral("keyids") &&
aKeySystem.EqualsLiteral("org.w3.clearkey")) {
initDataTypes.AppendElement(candidate);
}
}
if (initDataTypes.IsEmpty()) {

Просмотреть файл

@ -619,6 +619,8 @@ skip-if = (os == 'win' && os_version == '5.1') || (os != 'win' && toolkit != 'go
[test_eme_session_callable_value.html]
[test_eme_canvas_blocked.html]
skip-if = toolkit == 'android' # bug 1149374
[test_eme_key_ids_initdata.html]
skip-if = toolkit == 'android' # bug 1149374
[test_eme_non_mse_fails.html]
skip-if = toolkit == 'android' # bug 1149374
[test_eme_request_notifications.html]

Просмотреть файл

@ -0,0 +1,111 @@
<!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 tests = [
{
name: "One keyId",
initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"]}',
expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"],"type":"temporary"}',
sessionType: 'temporary',
expectPass: true,
},
{
name: "Two keyIds",
initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"temporary"}',
sessionType: 'temporary',
expectPass: true,
},
{
name: "Two keyIds, temporary session",
initData: '{"type":"temporary", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"temporary"}',
sessionType: 'temporary',
expectPass: true,
},
{
name: "Two keyIds, persistent session, type before kids",
initData: '{"type":"persistent", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"persistent"}',
sessionType: 'persistent',
expectPass: true,
},
{
name: "Invalid keyId",
initData: '{"kids":["0"]}',
sessionType: 'temporary',
expectPass: false,
},
{
name: "Empty keyId",
initData: '{"kids":[""]}',
sessionType: 'temporary',
expectPass: false,
},
{
name: "SessionType in license doesn't match MediaKeySession's sessionType",
initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"]}',
sessionType: 'persistent',
expectPass: false,
},
{
name: "One valid and one invalid kid",
initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A", "invalid"]}',
expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"],"type":"temporary"}',
sessionType: 'temporary',
expectPass: true,
},
{
name: "Invalid initData",
initData: 'invalid initData',
sessionType: 'temporary',
expectPass: false,
},
];
function Test(test) {
return new Promise(function(resolve, reject) {
navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{initDataTypes: ['keyids']}]).then(
(access) => access.createMediaKeys()
).then(
(mediaKeys) => {
var session = mediaKeys.createSession(test.sessionType);
var initData = new TextEncoder().encode(test.initData);
session.addEventListener("message", function(event) {
is(event.messageType, "license-request", "'" + test.name + "' MediaKeyMessage type should be license-request.");
var text = new TextDecoder().decode(event.message);
is(text, test.expectedRequest, "'" + test.name + "' got expected response.");
is(text == test.expectedRequest, test.expectPass,
"'" + test.name + "' expected to " + (test.expectPass ? "pass" : "fail"));
resolve();
});
return session.generateRequest('keyids', initData);
}
).catch((x) => {
ok(!test.expectPass, "'" + test.name + "' expected to fail.");
resolve();
});
});
}
function beginTest() {
Promise.all(tests.map(Test)).then(function() { SimpleTest.finish(); });
}
SimpleTest.waitForExplicitFinish();
SetupEMEPref(beginTest);
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -113,12 +113,14 @@ public:
CreateSessionTask(ClearKeySessionManager* aTarget,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
: mTarget(aTarget)
, mCreateSessionToken(aCreateSessionToken)
, mPromiseId(aPromiseId)
, mInitDataType(aInitDataType)
, mSessionType(aSessionType)
{
mInitData.insert(mInitData.end(),
@ -128,8 +130,8 @@ public:
virtual void Run() override {
mTarget->CreateSession(mCreateSessionToken,
mPromiseId,
"cenc",
strlen("cenc"),
mInitDataType.c_str(),
mInitDataType.size(),
&mInitData.front(),
mInitData.size(),
mSessionType);
@ -141,6 +143,7 @@ private:
RefPtr<ClearKeySessionManager> mTarget;
uint32_t mCreateSessionToken;
uint32_t mPromiseId;
const string mInitDataType;
vector<uint8_t> mInitData;
GMPSessionType mSessionType;
};
@ -150,6 +153,7 @@ private:
ClearKeyPersistence::DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
@ -160,6 +164,7 @@ ClearKeyPersistence::DeferCreateSessionIfNotReady(ClearKeySessionManager* aInsta
GMPTask* t = new CreateSessionTask(aInstance,
aCreateSessionToken,
aPromiseId,
aInitDataType,
aInitData,
aInitDataSize,
aSessionType);

Просмотреть файл

@ -31,6 +31,7 @@ public:
static bool DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const std::string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType);

Просмотреть файл

@ -54,13 +54,25 @@ ClearKeySession::~ClearKeySession()
void
ClearKeySession::Init(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const std::string& aInitDataType,
const uint8_t* aInitData, uint32_t aInitDataSize)
{
CK_LOGD("ClearKeySession::Init");
ClearKeyUtils::ParseInitData(aInitData, aInitDataSize, mKeyIds);
if (aInitDataType == "cenc") {
ClearKeyUtils::ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
} else if (aInitDataType == "keyids") {
std::string sessionType;
ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds, sessionType);
if (sessionType != ClearKeyUtils::SessionTypeToString(mSessionType)) {
const char message[] = "Session type specified in keyids init data doesn't match session type.";
mCallback->RejectPromise(aPromiseId, kGMPAbortError, message, strlen(message));
return;
}
}
if (!mKeyIds.size()) {
const char message[] = "Couldn't parse cenc key init data";
const char message[] = "Couldn't parse init data";
mCallback->RejectPromise(aPromiseId, kGMPAbortError, message, strlen(message));
return;
}

Просмотреть файл

@ -38,6 +38,7 @@ public:
void Init(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData, uint32_t aInitDataSize);
GMPSessionType Type() const;

Просмотреть файл

@ -101,8 +101,9 @@ ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
{
CK_LOGD("ClearKeySessionManager::CreateSession type:%s", aInitDataType);
// initDataType must be "cenc".
if (strcmp("cenc", aInitDataType)) {
string initDataType(aInitDataType, aInitDataType + aInitDataTypeSize);
// initDataType must be "cenc" or "keyids".
if (initDataType != "cenc" && initDataType != "keyids") {
mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
nullptr /* message */, 0 /* messageLen */);
return;
@ -111,6 +112,7 @@ ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
aCreateSessionToken,
aPromiseId,
initDataType,
aInitData,
aInitDataSize,
aSessionType)) {
@ -121,7 +123,7 @@ ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
assert(mSessions.find(sessionId) == mSessions.end());
ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
session->Init(aCreateSessionToken, aPromiseId, aInitData, aInitDataSize);
session->Init(aCreateSessionToken, aPromiseId, initDataType, aInitData, aInitDataSize);
mSessions[sessionId] = session;
const vector<KeyId>& sessionKeys = session->GetKeyIds();

Просмотреть файл

@ -132,8 +132,9 @@ EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded)
}
/* static */ void
ClearKeyUtils::ParseInitData(const uint8_t* aInitData, uint32_t aInitDataSize,
vector<KeyId>& aOutKeys)
ClearKeyUtils::ParseCENCInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
vector<KeyId>& aOutKeyIds)
{
using mozilla::BigEndian;
@ -182,7 +183,7 @@ ClearKeyUtils::ParseInitData(const uint8_t* aInitData, uint32_t aInitDataSize,
}
for (uint32_t i = 0; i < kidCount; i++) {
aOutKeys.push_back(KeyId(data, data + CLEARKEY_KEY_LEN));
aOutKeyIds.push_back(KeyId(data, data + CLEARKEY_KEY_LEN));
data += CLEARKEY_KEY_LEN;
}
}
@ -195,7 +196,7 @@ ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
{
assert(aKeyIDs.size() && aOutRequest.empty());
aOutRequest.append("{ \"kids\":[");
aOutRequest.append("{\"kids\":[");
for (size_t i = 0; i < aKeyIDs.size(); i++) {
if (i) {
aOutRequest.append(",");
@ -208,7 +209,7 @@ ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
aOutRequest.append("\"");
}
aOutRequest.append("], \"type\":");
aOutRequest.append("],\"type\":");
aOutRequest.append("\"");
aOutRequest.append(SessionTypeToString(aSessionType));
@ -508,6 +509,80 @@ ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
return true;
}
static bool
ParseKeyIds(ParserContext& aCtx, vector<KeyId>& aOutKeyIds)
{
// Consume start of array.
EXPECT_SYMBOL(aCtx, '[');
while (true) {
string label;
vector<uint8_t> keyId;
if (!GetNextLabel(aCtx, label) ||
!DecodeBase64KeyOrId(label, keyId)) {
return false;
}
assert(!keyId.empty());
aOutKeyIds.push_back(keyId);
uint8_t sym = PeekSymbol(aCtx);
if (!sym || sym == ']') {
break;
}
EXPECT_SYMBOL(aCtx, ',');
}
return GetNextSymbol(aCtx) == ']';
}
/* static */ bool
ClearKeyUtils::ParseKeyIdsInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
vector<KeyId>& aOutKeyIds,
string& aOutSessionType)
{
aOutSessionType = "temporary";
ParserContext ctx;
ctx.mIter = aInitData;
ctx.mEnd = aInitData + aInitDataSize;
// Consume '{' from start of object.
EXPECT_SYMBOL(ctx, '{');
while (true) {
string label;
// Consume member kids.
if (!GetNextLabel(ctx, label)) return false;
EXPECT_SYMBOL(ctx, ':');
if (label == "kids") {
// Parse "kids" array.
if (!ParseKeyIds(ctx, aOutKeyIds)) return false;
} else if (label == "type") {
// Consume type string.
if (!GetNextLabel(ctx, aOutSessionType)) return false;
} else {
SkipToken(ctx);
}
// Check for end of object.
if (PeekSymbol(ctx) == '}') {
break;
}
// Consume ',' between object members.
EXPECT_SYMBOL(ctx, ',');
}
// Consume '}' from end of object.
EXPECT_SYMBOL(ctx, '}');
return true;
}
/* static */ const char*
ClearKeyUtils::SessionTypeToString(GMPSessionType aSessionType)
{

Просмотреть файл

@ -54,8 +54,14 @@ public:
static void DecryptAES(const std::vector<uint8_t>& aKey,
std::vector<uint8_t>& aData, std::vector<uint8_t>& aIV);
static void ParseInitData(const uint8_t* aInitData, uint32_t aInitDataSize,
std::vector<Key>& aOutKeys);
static void ParseCENCInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
std::vector<Key>& aOutKeyIds);
static bool ParseKeyIdsInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
std::vector<KeyId>& aOutKeyIds,
std::string& aOutSessionType);
static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
std::string& aOutRequest,