Bug 1287000 - Ensure ClearKey's CENC PSSH parser can handle PSSH boxes with a 0 size field. r=jwwang draft
authorChris Pearce <cpearce@mozilla.com>
Fri, 15 Jul 2016 09:31:07 +1200
changeset 388825 e9a1b439f8b371653c2c97322a2db64cafef6dd8
parent 388749 711963e8daa312ae06409f8ab5c06612cb0b8f7b
child 525606 304a9f0ac87ea68ee42b936c247859738514e8a8
push id23242
push usercpearce@mozilla.com
push dateMon, 18 Jul 2016 02:38:47 +0000
reviewersjwwang
bugs1287000
milestone50.0a1
Bug 1287000 - Ensure ClearKey's CENC PSSH parser can handle PSSH boxes with a 0 size field. r=jwwang Google's Web Platform EME tests contain a CENC PSSH box with a 0 size field. Our existing PSSH parser in our ClearKey plugin doesn't handle this well, it gets stuck in an infinite loop. We should really handle everything that Chrome handles, so we should handle this input. We also shouldn't really be using raw pointers in the PSSH parser. So rewrite the PSSH parser to use a ByteReader, and handle an invalid 0 sized common SystemID box. Also add gtests for the parser, and skip over PSSH boxes with unknown SystemIDs (if they have valid sizes that is). MozReview-Commit-ID: CdVPpphAJV
media/gmp-clearkey/0.1/ClearKeyCencParser.cpp
media/gmp-clearkey/0.1/ClearKeyCencParser.h
media/gmp-clearkey/0.1/ClearKeySession.cpp
media/gmp-clearkey/0.1/ClearKeyUtils.cpp
media/gmp-clearkey/0.1/ClearKeyUtils.h
media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
media/gmp-clearkey/0.1/moz.build
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyCencParser.cpp
@@ -0,0 +1,189 @@
+/*
+ * 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 "ClearKeyCencParser.h"
+
+#include "mozilla/Assertions.h"
+#include "ArrayUtils.h"
+#include "BigEndian.h"
+#include <memory.h>
+#include <algorithm>
+#include <assert.h>
+#include <limits>
+
+// Stripped down version of mp4_demuxer::ByteReader, stripped down to make it
+// easier to link into ClearKey DLL and gtest.
+class ByteReader
+{
+public:
+  ByteReader(const uint8_t* aData, size_t aSize)
+    : mPtr(aData), mRemaining(aSize), mLength(aSize)
+  {
+  }
+
+  size_t Offset() const
+  {
+    return mLength - mRemaining;
+  }
+
+  size_t Remaining() const { return mRemaining; }
+
+  size_t Length() const { return mLength; }
+
+  bool CanRead8() const { return mRemaining >= 1; }
+
+  uint8_t ReadU8()
+  {
+    auto ptr = Read(1);
+    if (!ptr) {
+      MOZ_ASSERT(false);
+      return 0;
+    }
+    return *ptr;
+  }
+
+  bool CanRead32() const { return mRemaining >= 4; }
+
+  uint32_t ReadU32()
+  {
+    auto ptr = Read(4);
+    if (!ptr) {
+      MOZ_ASSERT(false);
+      return 0;
+    }
+    return mozilla::BigEndian::readUint32(ptr);
+  }
+
+  const uint8_t* Read(size_t aCount)
+  {
+    if (aCount > mRemaining) {
+      mRemaining = 0;
+      return nullptr;
+    }
+    mRemaining -= aCount;
+
+    const uint8_t* result = mPtr;
+    mPtr += aCount;
+
+    return result;
+  }
+
+  const uint8_t* Seek(size_t aOffset)
+  {
+    if (aOffset > mLength) {
+      MOZ_ASSERT(false);
+      return nullptr;
+    }
+
+    mPtr = mPtr - Offset() + aOffset;
+    mRemaining = mLength - aOffset;
+    return mPtr;
+  }
+
+private:
+  const uint8_t* mPtr;
+  size_t mRemaining;
+  const size_t mLength;
+};
+
+#define FOURCC(a,b,c,d) ((a << 24) + (b << 16) + (c << 8) + d)
+
+ // System ID identifying the cenc v2 pssh box format; specified at:
+ // https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
+const uint8_t kSystemID[] = {
+  0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+  0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
+};
+
+void
+ParseCENCInitData(const uint8_t* aInitData,
+                  uint32_t aInitDataSize,
+                  std::vector<std::vector<uint8_t>>& aOutKeyIds)
+{
+  ByteReader reader(aInitData, aInitDataSize);
+  while (reader.CanRead32()) {
+    // Box size. For the common system Id, ignore this, as some useragents
+    // handle invalid box sizes.
+    const size_t start = reader.Offset();
+    const size_t size = reader.ReadU32();
+    if (size > std::numeric_limits<size_t>::max() - start) {
+      // Ensure 'start + size' calculation below can't overflow.
+      return;
+    }
+    const size_t end = std::min<size_t>(start + size, reader.Length());
+
+    // PSSH box type.
+    if (!reader.CanRead32()) {
+      return;
+    }
+    uint32_t box = reader.ReadU32();
+    if (box != FOURCC('p','s','s','h')) {
+      reader.Seek(std::max<size_t>(reader.Offset(), end));
+      continue;
+    }
+
+    // 1 byte version, 3 bytes flags.
+    if (!reader.CanRead32()) {
+      return;
+    }
+    uint8_t version = reader.ReadU8();
+    if (version != 1) {
+      // Ignore pssh boxes with wrong version.
+      reader.Seek(std::max<size_t>(reader.Offset(), end));
+      continue;
+    }
+    reader.Read(3); // skip flags.
+
+    // SystemID
+    const uint8_t* sid = reader.Read(sizeof(kSystemID));
+    if (!sid) {
+      // Insufficinet bytes to read SystemID.
+      return;
+    }
+    if (memcmp(kSystemID, sid, sizeof(kSystemID))) {
+      // Ignore pssh boxes with wrong system ID.
+      reader.Seek(std::max<size_t>(reader.Offset(), end));
+      continue;
+    }
+
+    if (!reader.CanRead32()) {
+      return;
+    }
+    uint32_t kidCount = reader.ReadU32();
+
+    for (uint32_t i = 0; i < kidCount; i++) {
+      if (reader.Remaining() < CLEARKEY_KEY_LEN) {
+        // Not enough remaining to read key.
+        return;
+      }
+      const uint8_t* kid = reader.Read(CLEARKEY_KEY_LEN);
+      aOutKeyIds.push_back(std::vector<uint8_t>(kid, kid + CLEARKEY_KEY_LEN));
+    }
+
+    // Size of extra data. EME CENC format spec says datasize should
+    // always be 0. We explicitly read the datasize, in case the box
+    // size was 0, so that we get to the end of the box.
+    if (!reader.CanRead32()) {
+      return;
+    }
+    reader.ReadU32();
+
+    // Jump forwards to the end of the box, skipping any padding.
+    if (size) {
+      reader.Seek(end);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeyCencParser.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#ifndef __ClearKeyCencParser_h__
+#define __ClearKeyCencParser_h__
+
+#include <stdint.h>
+#include <vector>
+
+#define CLEARKEY_KEY_LEN ((size_t)16)
+
+void
+ParseCENCInitData(const uint8_t* aInitData,
+                  uint32_t aInitDataSize,
+                  std::vector<std::vector<uint8_t>>& aOutKeyIds);
+
+#endif
--- a/media/gmp-clearkey/0.1/ClearKeySession.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySession.cpp
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
 #include "BigEndian.h"
 #include "ClearKeyDecryptionManager.h"
 #include "ClearKeySession.h"
 #include "ClearKeyUtils.h"
 #include "ClearKeyStorage.h"
+#include "ClearKeyCencParser.h"
 #include "gmp-task-utils.h"
 #include "gmp-api/gmp-decryption.h"
 #include <assert.h>
 #include <string.h>
 
 using namespace mozilla;
 
 ClearKeySession::ClearKeySession(const std::string& aSessionId,
@@ -55,17 +56,17 @@ void
 ClearKeySession::Init(uint32_t aCreateSessionToken,
                       uint32_t aPromiseId,
                       const std::string& aInitDataType,
                       const uint8_t* aInitData, uint32_t aInitDataSize)
 {
   CK_LOGD("ClearKeySession::Init");
 
   if (aInitDataType == "cenc") {
-    ClearKeyUtils::ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
+    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;
     }
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
@@ -25,25 +25,16 @@
 #include "ArrayUtils.h"
 #include <assert.h>
 #include <memory.h>
 #include "BigEndian.h"
 #include "openaes/oaes_lib.h"
 
 using namespace std;
 
-#define FOURCC(a,b,c,d) ((a << 24) + (b << 16) + (c << 8) + d)
-
-// System ID identifying the cenc v2 pssh box format; specified at:
-// https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
-const uint8_t kSystemID[] = {
-  0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
-  0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
-};
-
 void
 CK_Log(const char* aFmt, ...)
 {
   va_list ap;
 
   va_start(ap, aFmt);
   vprintf(aFmt, ap);
   va_end(ap);
@@ -127,74 +118,16 @@ EncodeBase64Web(vector<uint8_t> aBinary,
     assert(idx < MOZ_ARRAY_LENGTH(sAlphabet)); // out of bounds index for 'sAlphabet'
     out[i] = sAlphabet[idx];
   }
 
   return true;
 }
 
 /* static */ void
-ClearKeyUtils::ParseCENCInitData(const uint8_t* aInitData,
-                                 uint32_t aInitDataSize,
-                                 vector<KeyId>& aOutKeyIds)
-{
-  using mozilla::BigEndian;
-
-  uint32_t size = 0;
-  for (uint32_t offset = 0; offset + sizeof(uint32_t) < aInitDataSize; offset += size) {
-    const uint8_t* data = aInitData + offset;
-    size = BigEndian::readUint32(data); data += sizeof(uint32_t);
-
-    CK_LOGD("Looking for pssh at offset %u", offset);
-
-    if (size + offset > aInitDataSize) {
-      CK_LOGE("Box size %u overflows init data buffer", size);
-      return;
-    }
-
-    if (size < 36) {
-      // Too small to be a cenc2 pssh box
-      continue;
-    }
-
-    uint32_t box = BigEndian::readUint32(data); data += sizeof(uint32_t);
-    if (box != FOURCC('p','s','s','h')) {
-      CK_LOGE("ClearKey CDM passed non-pssh initData");
-      return;
-    }
-
-    uint32_t head = BigEndian::readUint32(data); data += sizeof(uint32_t);
-    CK_LOGD("Got version %u pssh box, length %u", head & 0xff, size);
-
-    if ((head >> 24) != 1) {
-      // Ignore pssh boxes with wrong version
-      CK_LOGD("Ignoring pssh box with wrong version");
-      continue;
-    }
-
-    if (memcmp(kSystemID, data, sizeof(kSystemID))) {
-      // Ignore pssh boxes with wrong system ID
-      continue;
-    }
-    data += sizeof(kSystemID);
-
-    uint32_t kidCount = BigEndian::readUint32(data); data += sizeof(uint32_t);
-    if (data + kidCount * CLEARKEY_KEY_LEN > aInitData + aInitDataSize) {
-      CK_LOGE("pssh key IDs overflow init data buffer");
-      return;
-    }
-
-    for (uint32_t i = 0; i < kidCount; i++) {
-      aOutKeyIds.push_back(KeyId(data, data + CLEARKEY_KEY_LEN));
-      data += CLEARKEY_KEY_LEN;
-    }
-  }
-}
-
-/* static */ void
 ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
                               string& aOutRequest,
                               GMPSessionType aSessionType)
 {
   assert(aKeyIDs.size() && aOutRequest.empty());
 
   aOutRequest.append("{\"kids\":[");
   for (size_t i = 0; i < aKeyIDs.size(); i++) {
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.h
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.h
@@ -18,18 +18,16 @@
 #define __ClearKeyUtils_h__
 
 #include <stdint.h>
 #include <string>
 #include <vector>
 #include <assert.h>
 #include "gmp-api/gmp-decryption.h"
 
-#define CLEARKEY_KEY_LEN ((size_t)16)
-
 #if 0
 void CK_Log(const char* aFmt, ...);
 #define CK_LOGE(...) CK_Log(__VA_ARGS__)
 #define CK_LOGD(...) CK_Log(__VA_ARGS__)
 #define CK_LOGW(...) CK_Log(__VA_ARGS__)
 #else
 #define CK_LOGE(...)
 #define CK_LOGD(...)
@@ -49,20 +47,16 @@ struct KeyIdPair
 };
 
 class ClearKeyUtils
 {
 public:
   static void DecryptAES(const std::vector<uint8_t>& aKey,
                          std::vector<uint8_t>& aData, std::vector<uint8_t>& aIV);
 
-  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,
                              GMPSessionType aSessionType);
--- a/media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
+++ b/media/gmp-clearkey/0.1/gtest/TestClearKeyUtils.cpp
@@ -5,16 +5,17 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 #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];
@@ -68,8 +69,168 @@ TEST(ClearKey, DecodeBase64KeyOrId) {
     if (test.shouldPass) {
       EXPECT_EQ(v.size(), 16u);
       for (size_t k = 0; k < 16; k++) {
         EXPECT_EQ(v[k], test.raw[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[] = {
+  0x00, 0x00, 0x00, 0x00,                          // size = 0
+  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
+};
+
+// Example CENC initData from the EME spec format registry:
+// https://w3c.github.io/encrypted-media/format-registry/initdata/cenc.html
+const uint8_t gW3SpecExampleCencInitData[] = {
+  0x00, 0x00, 0x00, 0x4c, 0x70, 0x73, 0x73, 0x68, // BMFF box header (76 bytes, 'pssh')
+  0x01, 0x00, 0x00, 0x00,                         // Full box header (version = 1, flags = 0)
+  0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // SystemID
+  0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+  0x00, 0x00, 0x00, 0x02,                         // KID_count (2)
+  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // First KID ("0123456789012345")
+  0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+  0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, // Second KID ("ABCDEFGHIJKLMNOP")
+  0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
+  0x00, 0x00, 0x00, 0x00                         // Size of Data (0)
+};
+
+// Invalid box size, would overflow if used.
+const uint8_t gOverflowBoxSize[] = {
+  0xff, 0xff, 0xff, 0xff,                          // size = UINT32_MAX
+};
+
+// Invalid box size, but retrievable data.
+const uint8_t gMalformedCencInitData[] = {
+  0x00, 0x00, 0xff, 0xff,                          // size = too big a number
+  0x70, 0x73, 0x73, 0x68,                          // 'pssh'
+  0x01,                                            // version = 1
+  0xff, 0xff, 0xff,                                // flags
+  0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02,  // Common SystemID
+  0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
+  0xff, 0xff, 0xff, 0xff,                          // key count = UINT32_MAX
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // key
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff                           // datasize
+};
+
+// Non PSSH box, followed by Non common SystemID PSSH, followed by common SystemID PSSH box.
+const uint8_t gLeadingNonCommonCencInitData[] = {
+  0x00, 0x00, 0x00, 0x09,                          // size = 9
+  0xff, 0xff, 0xff, 0xff,                          // something other than 'pssh'
+  0xff,                                            // odd number of bytes of garbage to throw off the parser
+
+  0x00, 0x00, 0x00, 0x5c,                          // size = 92
+  0x70, 0x73, 0x73, 0x68,                          // 'pssh'
+  0x01,                                            // version = 1
+  0x00, 0x00, 0x00,                                // flags
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // Invalid SystemID
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // Some data to pad out the box.
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+
+  // gW3SpecExampleCencInitData
+  0x00, 0x00, 0x00, 0x4c, 0x70, 0x73, 0x73, 0x68, // BMFF box header (76 bytes, 'pssh')
+  0x01, 0x00, 0x00, 0x00,                         // Full box header (version = 1, flags = 0)
+  0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // SystemID
+  0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+  0x00, 0x00, 0x00, 0x02,                         // KID_count (2)
+  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // First KID ("0123456789012345")
+  0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+  0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, // Second KID ("ABCDEFGHIJKLMNOP")
+  0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
+  0x00, 0x00, 0x00, 0x00                         // Size of Data (0)
+};
+
+const uint8_t gNonPSSHBoxZeroSize[] = {
+  0x00, 0x00, 0x00, 0x00,                          // size = 0
+  0xff, 0xff, 0xff, 0xff,                          // something other than 'pssh'
+};
+
+// Two lots of the google init data. To ensure we handle
+// multiple boxes with size 0.
+const uint8_t g2xGoogleWPTCencInitData[] = {
+  0x00, 0x00, 0x00, 0x00,                          // size = 0
+  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,                          // size = 0
+  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  
+};
+
+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);
+  EXPECT_EQ(memcmp(&keyIds[0].front(), &gGoogleWPTCencInitData[32], 16), 0);
+
+  keyIds.clear();
+  ParseCENCInitData(gW3SpecExampleCencInitData, MOZ_ARRAY_LENGTH(gW3SpecExampleCencInitData), keyIds);
+  EXPECT_EQ(keyIds.size(), 2u);
+  EXPECT_EQ(keyIds[0].size(), 16u);
+  EXPECT_EQ(memcmp(&keyIds[0].front(), &gW3SpecExampleCencInitData[32], 16), 0);
+  EXPECT_EQ(memcmp(&keyIds[1].front(), &gW3SpecExampleCencInitData[48], 16), 0);
+
+  keyIds.clear();
+  ParseCENCInitData(gOverflowBoxSize, MOZ_ARRAY_LENGTH(gOverflowBoxSize), keyIds);
+  EXPECT_EQ(keyIds.size(), 0u);
+
+  keyIds.clear();
+  ParseCENCInitData(gMalformedCencInitData, MOZ_ARRAY_LENGTH(gMalformedCencInitData), keyIds);
+  EXPECT_EQ(keyIds.size(), 1u);
+  EXPECT_EQ(keyIds[0].size(), 16u);
+  EXPECT_EQ(memcmp(&keyIds[0].front(), &gMalformedCencInitData[32], 16), 0);
+
+  keyIds.clear();
+  ParseCENCInitData(gLeadingNonCommonCencInitData, MOZ_ARRAY_LENGTH(gLeadingNonCommonCencInitData), keyIds);
+  EXPECT_EQ(keyIds.size(), 2u);
+  EXPECT_EQ(keyIds[0].size(), 16u);
+  EXPECT_EQ(memcmp(&keyIds[0].front(), &gW3SpecExampleCencInitData[32], 16), 0);
+  EXPECT_EQ(memcmp(&keyIds[1].front(), &gW3SpecExampleCencInitData[48], 16), 0);
+
+  keyIds.clear();
+  ParseCENCInitData(gNonPSSHBoxZeroSize, MOZ_ARRAY_LENGTH(gNonPSSHBoxZeroSize), keyIds);
+  EXPECT_EQ(keyIds.size(), 0u);
+
+  keyIds.clear();
+  ParseCENCInitData(g2xGoogleWPTCencInitData, MOZ_ARRAY_LENGTH(g2xGoogleWPTCencInitData), keyIds);
+  EXPECT_EQ(keyIds.size(), 2u);
+  EXPECT_EQ(keyIds[0].size(), 16u);
+  EXPECT_EQ(keyIds[1].size(), 16u);
+  EXPECT_EQ(memcmp(&keyIds[0].front(), &g2xGoogleWPTCencInitData[32], 16), 0);
+  EXPECT_EQ(memcmp(&keyIds[1].front(), &g2xGoogleWPTCencInitData[84], 16), 0);
+
+}
--- a/media/gmp-clearkey/0.1/moz.build
+++ b/media/gmp-clearkey/0.1/moz.build
@@ -8,16 +8,17 @@ SharedLibrary('clearkey')
 
 FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
 
 FINAL_TARGET_PP_FILES += ['clearkey.info.in']
 
 UNIFIED_SOURCES += [
     'ClearKeyAsyncShutdown.cpp',
     'ClearKeyBase64.cpp',
+    'ClearKeyCencParser.cpp',
     'ClearKeyDecryptionManager.cpp',
     'ClearKeyPersistence.cpp',
     'ClearKeySession.cpp',
     'ClearKeySessionManager.cpp',
     'ClearKeyStorage.cpp',
     'ClearKeyUtils.cpp',
     'gmp-clearkey.cpp',
 ]