Bug 1289968 - [Part1] Ensure ClearKey doesn't assume keyIds can only be 16 bytes. r=cpearce draft
authorChris Pearce <cpearce@mozilla.com>
Mon, 01 Aug 2016 16:28:10 +1200
changeset 414590 d12cb26a1cf5a7a9d80bee4592b1dc31a541bc8b
parent 412167 feff79e5b1374439f17c5ea10a559acf1380a8d5
child 414591 f4ff5ee4db4eb815232abde3d450dfa8986ef7ed
push id29717
push userkikuo@mozilla.com
push dateFri, 16 Sep 2016 19:25:31 +0000
reviewerscpearce
bugs1289968
milestone51.0a1
Bug 1289968 - [Part1] Ensure ClearKey doesn't assume keyIds can only be 16 bytes. r=cpearce MozReview-Commit-ID: Hf2IQsAHa4r
dom/media/test/test_eme_initDataTypes.html
media/gmp-clearkey/0.1/ClearKeyBase64.cpp
media/gmp-clearkey/0.1/ClearKeyBase64.h
media/gmp-clearkey/0.1/ClearKeySession.cpp
media/gmp-clearkey/0.1/ClearKeyUtils.cpp
media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
--- a/dom/media/test/test_eme_initDataTypes.html
+++ b/dom/media/test/test_eme_initDataTypes.html
@@ -61,24 +61,16 @@ var tests = [
   {
     name: "SessionType in license doesn't match MediaKeySession's sessionType",
     initDataType: 'keyids',
     initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"]}',
     sessionType: 'persistent-license',
     expectPass: false,
   },
   {
-    name: "One valid and one invalid kid",
-    initDataType: 'keyids',
-    initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A", "invalid"]}',
-    expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"],"type":"temporary"}',
-    sessionType: 'temporary',
-    expectPass: true,
-  },
-  {
     name: "Invalid initData",
     initDataType: 'keyids',
     initData: 'invalid initData',
     sessionType: 'temporary',
     expectPass: false,
   },
   {
     name: "'webm' initDataType",
@@ -87,18 +79,19 @@ var tests = [
     expectedRequest: '{"kids":["YAYeAX5Hfod-V9ANHtANHg"],"type":"temporary"}',
     sessionType: 'temporary',
     expectPass: true,
   },
   {
     name: "'webm' initDataType with non 16 byte keyid",
     initDataType: 'webm',
     initData: 'YAYeAX5Hfod',
+    expectedRequest: '{\"kids\":[\"YAYeAX5Hfoc\"],\"type\":\"temporary\"}',
     sessionType: 'temporary',
-    expectPass: false,
+    expectPass: true,
   },
 ];
 
 function PrepareInitData(initDataType, initData)
 {
   if (initDataType == "keyids") {
     return new TextEncoder().encode(initData);
   } else if (initDataType == "webm") {
--- a/media/gmp-clearkey/0.1/ClearKeyBase64.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyBase64.cpp
@@ -54,28 +54,35 @@ Decode6Bit(string& aStr)
       break;
     }
   }
 
   return true;
 }
 
 bool
-DecodeBase64KeyOrId(const string& aEncoded, vector<uint8_t>& aOutDecoded)
+DecodeBase64(const string& aEncoded, vector<uint8_t>& aOutDecoded)
 {
+  if (aEncoded.empty()) {
+    aOutDecoded.clear();
+    return true;
+  }
+  if (aEncoded.size() == 1) {
+    // Invalid Base64 encoding.
+    return false;
+  }
   string encoded = aEncoded;
-  if (!Decode6Bit(encoded) ||
-    encoded.size() != 22) { // Can't decode to 16 byte CENC key or keyId.
+  if (!Decode6Bit(encoded)) {
     return false;
   }
 
   // The number of bytes we haven't yet filled in the current byte, mod 8.
   int shift = 0;
 
-  aOutDecoded.resize(16);
+  aOutDecoded.resize((encoded.size() * 3) / 4);
   vector<uint8_t>::iterator out = aOutDecoded.begin();
   for (size_t i = 0; i < encoded.length(); i++) {
     if (!shift) {
       *out = encoded[i] << 2;
     }
     else {
       *out |= encoded[i] >> (6 - shift);
       out++;
--- a/media/gmp-clearkey/0.1/ClearKeyBase64.h
+++ b/media/gmp-clearkey/0.1/ClearKeyBase64.h
@@ -16,16 +16,13 @@
 
 #ifndef __ClearKeyBase64_h__
 #define __ClearKeyBase64_h__
 
 #include <vector>
 #include <string>
 #include <stdint.h>
 
-// Decodes a base64 encoded CENC Key or KeyId into it's raw bytes. Note that
-// CENC Keys or KeyIds are 16 bytes long, so encoded they should be 22 bytes
-// plus any padding. Fails (returns false) on input that is more than 22 bytes
-// long after padding is stripped. Returns true on success.
+// Decodes a base64 encoded string. Returns true on success.
 bool
-DecodeBase64KeyOrId(const std::string& aEncoded, std::vector<uint8_t>& aOutDecoded);
+DecodeBase64(const std::string& aEncoded, std::vector<uint8_t>& aOutDecoded);
 
 #endif
--- a/media/gmp-clearkey/0.1/ClearKeySession.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySession.cpp
@@ -65,17 +65,17 @@ ClearKeySession::Init(uint32_t aCreateSe
   } 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, kGMPInvalidAccessError, message, strlen(message));
       return;
     }
-  } else if (aInitDataType == "webm" && aInitDataSize == 16) {
+  } else if (aInitDataType == "webm") {
     // "webm" initData format is simply the raw bytes of the keyId.
     vector<uint8_t> keyId;
     keyId.assign(aInitData, aInitData+aInitDataSize);
     mKeyIds.push_back(keyId);
   }
 
   if (!mKeyIds.size()) {
     const char message[] = "Couldn't parse init data";
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
@@ -305,25 +305,16 @@ GetNextLabel(ParserContext& aCtx, string
       return true;
     }
   }
 
   return false;
 }
 
 static bool
-DecodeKey(string& aEncoded, Key& aOutDecoded)
-{
-  return
-    DecodeBase64KeyOrId(aEncoded, aOutDecoded) &&
-    // Key should be 128 bits long.
-    aOutDecoded.size() == CLEARKEY_KEY_LEN;
-}
-
-static bool
 ParseKeyObject(ParserContext& aCtx, KeyIdPair& aOutKey)
 {
   EXPECT_SYMBOL(aCtx, '{');
 
   // Reject empty objects as invalid licenses.
   if (PeekSymbol(aCtx) == '}') {
     GetNextSymbol(aCtx);
     return false;
@@ -358,18 +349,18 @@ ParseKeyObject(ParserContext& aCtx, KeyI
     if (!sym || sym == '}') {
       break;
     }
     EXPECT_SYMBOL(aCtx, ',');
   }
 
   return !key.empty() &&
          !keyId.empty() &&
-         DecodeBase64KeyOrId(keyId, aOutKey.mKeyId) &&
-         DecodeKey(key, aOutKey.mKey) &&
+         DecodeBase64(keyId, aOutKey.mKeyId) &&
+         DecodeBase64(key, aOutKey.mKey) &&
          GetNextSymbol(aCtx) == '}';
 }
 
 static bool
 ParseKeys(ParserContext& aCtx, vector<KeyIdPair>& aOutKeys)
 {
   // Consume start of array.
   EXPECT_SYMBOL(aCtx, '[');
@@ -446,22 +437,22 @@ 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)) {
+    if (!GetNextLabel(aCtx, label) || !DecodeBase64(label, keyId)) {
       return false;
     }
-    assert(!keyId.empty());
-    aOutKeyIds.push_back(keyId);
+    if (!keyId.empty()) {
+      aOutKeyIds.push_back(keyId);
+    }
 
     uint8_t sym = PeekSymbol(aCtx);
     if (!sym || sym == ']') {
       break;
     }
 
     EXPECT_SYMBOL(aCtx, ',');
   }
@@ -488,17 +479,20 @@ ClearKeyUtils::ParseKeyIdsInitData(const
   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;
+      if (!ParseKeyIds(ctx, aOutKeyIds) ||
+          aOutKeyIds.empty()) {
+        return false;
+      }
     } else if (label == "type") {
       // Consume type string.
       if (!GetNextLabel(ctx, aOutSessionType)) return false;
     } else {
       SkipToken(ctx);
     }
 
     // Check for end of object.
--- a/media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
+++ b/media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
@@ -8,26 +8,25 @@
 #include <algorithm>
 #include <stdint.h>
 #include <vector>
 
 #include "../ClearKeyBase64.cpp"
 #include "../ClearKeyCencParser.cpp"
 #include "../ArrayUtils.h"
 
-
 using namespace std;
 
 struct B64Test {
-  const char* b64;
-  uint8_t raw[16];
+  string b64;
+  vector<uint8_t> raw;
   bool shouldPass;
 };
 
-B64Test tests[] = {
+const B64Test tests[] = {
   {
     "AAAAADk4AU4AAAAAAAAAAA",
     { 0x0, 0x0, 0x0, 0x0, 0x39, 0x38, 0x1, 0x4e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 },
     true
   },
   {
     "h2mqp1zAJjDIC34YXEXBxA==",
     { 0x87, 0x69, 0xaa, 0xa7, 0x5c, 0xc0, 0x26, 0x30, 0xc8, 0xb, 0x7e, 0x18, 0x5c, 0x45, 0xc1, 0xc4 },
@@ -36,45 +35,65 @@ B64Test tests[] = {
   {
     "flcdA35XHQN-Vx0DflcdAw",
     { 0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3, 0x7e, 0x57, 0x1d, 0x3 },
     true
   },
   {
     "flczM35XMzN-VzMzflczMw",
     { 0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33, 0x7e, 0x57, 0x33, 0x33 },
-    true
+    true,
   },
   {
     "flcdBH5XHQR-Vx0EflcdBA",
     { 0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4, 0x7e, 0x57, 0x1d, 0x4 },
     true
   },
   {
     "fldERH5XRER-V0REfldERA",
     { 0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44, 0x7e, 0x57, 0x44, 0x44 },
     true
   },
+  {
+    "fuzzbiz=",
+    { 0x7e, 0xec, 0xf3, 0x6e, 0x2c },
+    true
+  },
+  {
+    "fuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbiz",
+    {
+      0x7e, 0xec, 0xf3, 0x6e, 0x2c, 0xdf, 0xbb, 0x3c, 0xdb, 0x8b,
+      0x37, 0xee, 0xcf, 0x36, 0xe2, 0xcd, 0xfb, 0xb3, 0xcd, 0xb8,
+      0xb3, 0x7e, 0xec, 0xf3, 0x6e, 0x2c, 0xdf, 0xbb, 0x3c, 0xdb,
+      0x8b, 0x37, 0xee, 0xcf, 0x36, 0xe2, 0xcd, 0xfb, 0xb3, 0xcd,
+      0xb8, 0xb3
+    },
+    true
+  },
+  { "", { }, true },
+  { "00", { 0xd3 }, true },
+  { "000", { 0xd3, 0x4d }, true },
+
+  { "invalid", { 0x8a, 0x7b, 0xda, 0x96, 0x27 }, true },
+  { "invalic", { 0x8a, 0x7b, 0xda, 0x96, 0x27 }, true },
+
   // Failure tests
-  { "", { 0 }, false }, // empty
-  { "fuzzbiz", { 0 }, false }, // Too short
-  { "fuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbizfuzzbiz", { 0 }, false }, // too long
-
+  { "A", { }, false }, // 1 character is too few.
+  { "_", { }, false }, // 1 character is too few.
 };
 
-TEST(ClearKey, DecodeBase64KeyOrId) {
-  for (size_t i = 0; i < MOZ_ARRAY_LENGTH(tests); i++) {
+TEST(ClearKey, DecodeBase64) {
+  for (const B64Test& test : tests) {
     vector<uint8_t> v;
-    const B64Test& test = tests[i];
-    bool rv = DecodeBase64KeyOrId(string(test.b64), v);
-    EXPECT_EQ(rv, test.shouldPass);
+    bool rv = DecodeBase64(string(test.b64), v);
+    EXPECT_EQ(test.shouldPass, rv);
     if (test.shouldPass) {
-      EXPECT_EQ(v.size(), 16u);
-      for (size_t k = 0; k < 16; k++) {
-        EXPECT_EQ(v[k], test.raw[k]);
+      EXPECT_EQ(test.raw.size(), v.size());
+      for (size_t k = 0; k < test.raw.size(); k++) {
+        EXPECT_EQ(test.raw[k], v[k]);
       }
     }
   }
 }
 
 // This is the CENC initData from Google's web-platform tests.
 // https://github.com/w3c/web-platform-tests/blob/master/encrypted-media/Google/encrypted-media-utils.js#L50
 const uint8_t gGoogleWPTCencInitData[] = {
@@ -181,17 +200,17 @@ const uint8_t g2xGoogleWPTCencInitData[]
   0x70, 0x73, 0x73, 0x68,                          // 'pssh'
   0x01,                                            // version = 1
   0x00, 0x00, 0x00,                                // flags
   0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02,  // Common SystemID
   0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
   0x00, 0x00, 0x00, 0x01,                          // key count
   0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,  // key
   0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
-  0x00, 0x00, 0x00, 0x00                           // datasize  
+  0x00, 0x00, 0x00, 0x00                           // datasize
 };
 
 TEST(ClearKey, ParseCencInitData) {
   std::vector<std::vector<uint8_t>> keyIds;
 
   ParseCENCInitData(gGoogleWPTCencInitData, MOZ_ARRAY_LENGTH(gGoogleWPTCencInitData), keyIds);
   EXPECT_EQ(keyIds.size(), 1u);
   EXPECT_EQ(keyIds[0].size(), 16u);