summaryrefslogtreecommitdiffstats
path: root/media/psshparser
diff options
context:
space:
mode:
Diffstat (limited to 'media/psshparser')
-rw-r--r--media/psshparser/PsshParser.cpp208
-rw-r--r--media/psshparser/PsshParser.h30
-rw-r--r--media/psshparser/gtest/TestPsshParser.cpp169
-rw-r--r--media/psshparser/gtest/moz.build13
-rw-r--r--media/psshparser/moz.build25
5 files changed, 445 insertions, 0 deletions
diff --git a/media/psshparser/PsshParser.cpp b/media/psshparser/PsshParser.cpp
new file mode 100644
index 000000000..e5d0acd26
--- /dev/null
+++ b/media/psshparser/PsshParser.cpp
@@ -0,0 +1,208 @@
+/*
+ * 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 "PsshParser.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Move.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
+};
+
+const uint8_t kPrimetimeID[] = {
+ 0xf2, 0x39, 0xe7, 0x69, 0xef, 0xa3, 0x48, 0x50,
+ 0x9c, 0x16, 0xa9, 0x03, 0xc6, 0x93, 0x2e, 0xfb
+};
+
+bool
+ParseCENCInitData(const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ std::vector<std::vector<uint8_t>>& aOutKeyIds)
+{
+ aOutKeyIds.clear();
+ std::vector<std::vector<uint8_t>> keyIds;
+ 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 false;
+ }
+ const size_t end = start + size;
+ if (end > reader.Length()) {
+ // Ridiculous sized box.
+ return false;
+ }
+
+ // PSSH box type.
+ if (!reader.CanRead32()) {
+ return false;
+ }
+ uint32_t box = reader.ReadU32();
+ if (box != FOURCC('p','s','s','h')) {
+ return false;
+ }
+
+ // 1 byte version, 3 bytes flags.
+ if (!reader.CanRead32()) {
+ return false;
+ }
+ 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) {
+ // Insufficient bytes to read SystemID.
+ return false;
+ }
+ if (!memcmp(kPrimetimeID, sid, sizeof(kSystemID))) {
+ // Allow legacy Primetime key system PSSH boxes, which
+ // don't conform to common encryption format.
+ return true;
+ }
+
+ 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 false;
+ }
+ uint32_t kidCount = reader.ReadU32();
+
+ if (kidCount * CENC_KEY_LEN > reader.Remaining()) {
+ // Not enough bytes remaining to read all keys.
+ return false;
+ }
+
+ for (uint32_t i = 0; i < kidCount; i++) {
+ const uint8_t* kid = reader.Read(CENC_KEY_LEN);
+ keyIds.push_back(std::vector<uint8_t>(kid, kid + CENC_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 false;
+ }
+ reader.ReadU32();
+
+ // Jump forwards to the end of the box, skipping any padding.
+ if (size) {
+ reader.Seek(end);
+ }
+ }
+ aOutKeyIds = mozilla::Move(keyIds);
+ return true;
+}
diff --git a/media/psshparser/PsshParser.h b/media/psshparser/PsshParser.h
new file mode 100644
index 000000000..2036070c3
--- /dev/null
+++ b/media/psshparser/PsshParser.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 CENC_KEY_LEN ((size_t)16)
+
+bool
+ParseCENCInitData(const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ std::vector<std::vector<uint8_t>>& aOutKeyIds);
+
+#endif
diff --git a/media/psshparser/gtest/TestPsshParser.cpp b/media/psshparser/gtest/TestPsshParser.cpp
new file mode 100644
index 000000000..972cc3e7d
--- /dev/null
+++ b/media/psshparser/gtest/TestPsshParser.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+#include "gtest/gtest.h"
+#include <algorithm>
+#include <stdint.h>
+#include <vector>
+
+#include "psshparser/PsshParser.h"
+#include "mozilla/ArrayUtils.h"
+
+using namespace std;
+
+// 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, 0x44, 0x70, 0x73, 0x73, 0x68, // BMFF box header (68 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
+};
+
+// Valid box size, but key count too large.
+const uint8_t gTooLargeKeyCountInitData[] = {
+ 0x00, 0x00, 0x00, 0x34, // 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 common SystemID PSSH.
+// No keys Ids can be extracted, but don't consider the box invalid.
+const uint8_t gNonCencInitData[] = {
+ 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,
+};
+
+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
+};
+
+const uint8_t gPrimetimePSSH[] = {
+ 0x00, 0x00, 0x00, 0x00, // size = 0
+ 0x70, 0x73, 0x73, 0x68, // 'pssh'
+ 0x01, // version = 1
+ 0x00, 0x00, 0x00, // flags
+ 0xf2, 0x39, 0xe7, 0x69, 0xef, 0xa3, 0x48, 0x50, // Primetime system Id
+ 0x9c, 0x16, 0xa9, 0x03, 0xc6, 0x93, 0x2e, 0xfb
+};
+
+TEST(PsshParser, ParseCencInitData) {
+ std::vector<std::vector<uint8_t>> keyIds;
+ bool rv;
+
+ rv = ParseCENCInitData(gGoogleWPTCencInitData, MOZ_ARRAY_LENGTH(gGoogleWPTCencInitData), keyIds);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(1u, keyIds.size());
+ EXPECT_EQ(16u, keyIds[0].size());
+ EXPECT_EQ(0, memcmp(&keyIds[0].front(), &gGoogleWPTCencInitData[32], 16));
+
+ rv = ParseCENCInitData(gW3SpecExampleCencInitData, MOZ_ARRAY_LENGTH(gW3SpecExampleCencInitData), keyIds);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(2u, keyIds.size());
+ EXPECT_EQ(16u, keyIds[0].size());
+ EXPECT_EQ(0, memcmp(&keyIds[0].front(), &gW3SpecExampleCencInitData[32], 16));
+ EXPECT_EQ(0, memcmp(&keyIds[1].front(), &gW3SpecExampleCencInitData[48], 16));
+
+ rv = ParseCENCInitData(gOverflowBoxSize, MOZ_ARRAY_LENGTH(gOverflowBoxSize), keyIds);
+ EXPECT_FALSE(rv);
+ EXPECT_EQ(0u, keyIds.size());
+
+ rv = ParseCENCInitData(gTooLargeKeyCountInitData, MOZ_ARRAY_LENGTH(gTooLargeKeyCountInitData), keyIds);
+ EXPECT_FALSE(rv);
+ EXPECT_EQ(0u, keyIds.size());
+
+ rv = ParseCENCInitData(gNonCencInitData, MOZ_ARRAY_LENGTH(gNonCencInitData), keyIds);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(0u, keyIds.size());
+
+ rv = ParseCENCInitData(gNonPSSHBoxZeroSize, MOZ_ARRAY_LENGTH(gNonPSSHBoxZeroSize), keyIds);
+ EXPECT_FALSE(rv);
+ EXPECT_EQ(0u, keyIds.size());
+
+ rv = ParseCENCInitData(g2xGoogleWPTCencInitData, MOZ_ARRAY_LENGTH(g2xGoogleWPTCencInitData), keyIds);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(2u, keyIds.size());
+ EXPECT_EQ(16u, keyIds[0].size());
+ EXPECT_EQ(16u, keyIds[1].size());
+ EXPECT_EQ(0, memcmp(&keyIds[0].front(), &g2xGoogleWPTCencInitData[32], 16));
+ EXPECT_EQ(0, memcmp(&keyIds[1].front(), &g2xGoogleWPTCencInitData[84], 16));
+
+ rv = ParseCENCInitData(gPrimetimePSSH, MOZ_ARRAY_LENGTH(gPrimetimePSSH), keyIds);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(0u, keyIds.size());
+}
diff --git a/media/psshparser/gtest/moz.build b/media/psshparser/gtest/moz.build
new file mode 100644
index 000000000..18b8db179
--- /dev/null
+++ b/media/psshparser/gtest/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ 'TestPsshParser.cpp',
+]
+
+USE_LIBS += ['psshparser']
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/media/psshparser/moz.build b/media/psshparser/moz.build
new file mode 100644
index 000000000..b369b3915
--- /dev/null
+++ b/media/psshparser/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files('*'):
+ BUG_COMPONENT = ('Core', 'Video/Audio')
+
+EXPORTS.psshparser += [
+ 'PsshParser.h',
+]
+
+UNIFIED_SOURCES += [
+ 'PsshParser.cpp',
+]
+
+Library('psshparser')
+
+DISABLE_STL_WRAPPING = True
+DEFINES['MOZ_NO_MOZALLOC'] = True
+
+TEST_DIRS += [
+ 'gtest',
+]