зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1228461 - Implement keyids initData type for ClearKey. r=gerald
This commit is contained in:
Родитель
ad73151891
Коммит
efc8956d13
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче