summaryrefslogtreecommitdiffstats
path: root/dom/media/eme/clearkey
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/media/eme/clearkey
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/eme/clearkey')
-rw-r--r--dom/media/eme/clearkey/ArrayUtils.h22
-rw-r--r--dom/media/eme/clearkey/BigEndian.h59
-rw-r--r--dom/media/eme/clearkey/ClearKeyBase64.cpp90
-rw-r--r--dom/media/eme/clearkey/ClearKeyBase64.h29
-rw-r--r--dom/media/eme/clearkey/ClearKeyDecryptionManager.cpp289
-rw-r--r--dom/media/eme/clearkey/ClearKeyDecryptionManager.h111
-rw-r--r--dom/media/eme/clearkey/ClearKeyPersistence.cpp153
-rw-r--r--dom/media/eme/clearkey/ClearKeyPersistence.h64
-rw-r--r--dom/media/eme/clearkey/ClearKeySession.cpp72
-rw-r--r--dom/media/eme/clearkey/ClearKeySession.h56
-rw-r--r--dom/media/eme/clearkey/ClearKeySessionManager.cpp713
-rw-r--r--dom/media/eme/clearkey/ClearKeySessionManager.h138
-rw-r--r--dom/media/eme/clearkey/ClearKeyStorage.cpp194
-rw-r--r--dom/media/eme/clearkey/ClearKeyStorage.h42
-rw-r--r--dom/media/eme/clearkey/ClearKeyUtils.cpp661
-rw-r--r--dom/media/eme/clearkey/ClearKeyUtils.h107
-rw-r--r--dom/media/eme/clearkey/RefCounted.h85
-rw-r--r--dom/media/eme/clearkey/gtest/TestClearKeyUtils.cpp81
-rw-r--r--dom/media/eme/clearkey/gtest/moz.build15
-rw-r--r--dom/media/eme/clearkey/moz.build37
20 files changed, 3018 insertions, 0 deletions
diff --git a/dom/media/eme/clearkey/ArrayUtils.h b/dom/media/eme/clearkey/ArrayUtils.h
new file mode 100644
index 0000000000..ae5f33b68e
--- /dev/null
+++ b/dom/media/eme/clearkey/ArrayUtils.h
@@ -0,0 +1,22 @@
+/*
+ * 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 __ArrayUtils_h__
+#define __ArrayUtils_h__
+
+#define MOZ_ARRAY_LENGTH(array_) (sizeof(array_) / sizeof(array_[0]))
+
+#endif
diff --git a/dom/media/eme/clearkey/BigEndian.h b/dom/media/eme/clearkey/BigEndian.h
new file mode 100644
index 0000000000..5ea1b6042f
--- /dev/null
+++ b/dom/media/eme/clearkey/BigEndian.h
@@ -0,0 +1,59 @@
+/*
+ * 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 __BigEndian_h__
+#define __BigEndian_h__
+
+#include <stdint.h>
+
+namespace mozilla {
+
+class BigEndian {
+ public:
+ static uint32_t readUint32(const void* aPtr) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
+ return uint32_t(p[0]) << 24 | uint32_t(p[1]) << 16 | uint32_t(p[2]) << 8 |
+ uint32_t(p[3]);
+ }
+
+ static uint16_t readUint16(const void* aPtr) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
+ return uint32_t(p[0]) << 8 | uint32_t(p[1]);
+ }
+
+ static uint64_t readUint64(const void* aPtr) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(aPtr);
+ return uint64_t(p[0]) << 56 | uint64_t(p[1]) << 48 | uint64_t(p[2]) << 40 |
+ uint64_t(p[3]) << 32 | uint64_t(p[4]) << 24 | uint64_t(p[5]) << 16 |
+ uint64_t(p[6]) << 8 | uint64_t(p[7]);
+ }
+
+ static void writeUint64(void* aPtr, uint64_t aValue) {
+ uint8_t* p = reinterpret_cast<uint8_t*>(aPtr);
+ p[0] = uint8_t(aValue >> 56) & 0xff;
+ p[1] = uint8_t(aValue >> 48) & 0xff;
+ p[2] = uint8_t(aValue >> 40) & 0xff;
+ p[3] = uint8_t(aValue >> 32) & 0xff;
+ p[4] = uint8_t(aValue >> 24) & 0xff;
+ p[5] = uint8_t(aValue >> 16) & 0xff;
+ p[6] = uint8_t(aValue >> 8) & 0xff;
+ p[7] = uint8_t(aValue) & 0xff;
+ }
+};
+
+} // namespace mozilla
+
+#endif // __BigEndian_h__
diff --git a/dom/media/eme/clearkey/ClearKeyBase64.cpp b/dom/media/eme/clearkey/ClearKeyBase64.cpp
new file mode 100644
index 0000000000..bb610afd1b
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeyBase64.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "ClearKeyBase64.h"
+
+#include <algorithm>
+
+using std::string;
+using std::vector;
+
+/**
+ * Take a base64-encoded string, convert (in-place) each character to its
+ * corresponding value in the [0x00, 0x3f] range, and truncate any padding.
+ */
+static bool Decode6Bit(string& aStr) {
+ for (size_t i = 0; i < aStr.length(); i++) {
+ if (aStr[i] >= 'A' && aStr[i] <= 'Z') {
+ aStr[i] -= 'A';
+ } else if (aStr[i] >= 'a' && aStr[i] <= 'z') {
+ aStr[i] -= 'a' - 26;
+ } else if (aStr[i] >= '0' && aStr[i] <= '9') {
+ aStr[i] -= '0' - 52;
+ } else if (aStr[i] == '-' || aStr[i] == '+') {
+ aStr[i] = 62;
+ } else if (aStr[i] == '_' || aStr[i] == '/') {
+ aStr[i] = 63;
+ } else {
+ // Truncate '=' padding at the end of the aString.
+ if (aStr[i] != '=') {
+ aStr.erase(i, string::npos);
+ return false;
+ }
+ aStr[i] = '\0';
+ aStr.resize(i);
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool 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)) {
+ return false;
+ }
+
+ // The number of bytes we haven't yet filled in the current byte, mod 8.
+ int shift = 0;
+
+ 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++;
+ if (out == aOutDecoded.end()) {
+ // Hit last 6bit octed in encoded, which is padding and can be ignored.
+ break;
+ }
+ *out = encoded[i] << (shift + 2);
+ }
+ shift = (shift + 2) % 8;
+ }
+
+ return true;
+}
diff --git a/dom/media/eme/clearkey/ClearKeyBase64.h b/dom/media/eme/clearkey/ClearKeyBase64.h
new file mode 100644
index 0000000000..39309c031e
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeyBase64.h
@@ -0,0 +1,29 @@
+/*
+ * 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 __ClearKeyBase64_h__
+#define __ClearKeyBase64_h__
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+// Decodes a base64 encoded string. Returns true on success.
+bool DecodeBase64(const std::string& aEncoded,
+ std::vector<uint8_t>& aOutDecoded);
+
+#endif
diff --git a/dom/media/eme/clearkey/ClearKeyDecryptionManager.cpp b/dom/media/eme/clearkey/ClearKeyDecryptionManager.cpp
new file mode 100644
index 0000000000..7eeca88985
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeyDecryptionManager.cpp
@@ -0,0 +1,289 @@
+/*
+ * 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 "ClearKeyDecryptionManager.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include <vector>
+#include <algorithm>
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Span.h"
+#include "psshparser/PsshParser.h"
+
+using namespace cdm;
+
+bool AllZero(const std::vector<uint32_t>& aBytes) {
+ return all_of(aBytes.begin(), aBytes.end(),
+ [](uint32_t b) { return b == 0; });
+}
+
+class ClearKeyDecryptor : public RefCounted {
+ public:
+ ClearKeyDecryptor();
+
+ void InitKey(const Key& aKey);
+ bool HasKey() const { return !mKey.empty(); }
+
+ Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+ const CryptoMetaData& aMetadata);
+
+ const Key& DecryptionKey() const { return mKey; }
+
+ private:
+ ~ClearKeyDecryptor();
+
+ Key mKey;
+};
+
+/* static */
+ClearKeyDecryptionManager* ClearKeyDecryptionManager::sInstance = nullptr;
+
+/* static */
+ClearKeyDecryptionManager* ClearKeyDecryptionManager::Get() {
+ if (!sInstance) {
+ sInstance = new ClearKeyDecryptionManager();
+ }
+ return sInstance;
+}
+
+ClearKeyDecryptionManager::ClearKeyDecryptionManager() {
+ CK_LOGD("ClearKeyDecryptionManager::ClearKeyDecryptionManager");
+}
+
+ClearKeyDecryptionManager::~ClearKeyDecryptionManager() {
+ CK_LOGD("ClearKeyDecryptionManager::~ClearKeyDecryptionManager");
+
+ sInstance = nullptr;
+
+ for (auto it = mDecryptors.begin(); it != mDecryptors.end(); it++) {
+ it->second->Release();
+ }
+ mDecryptors.clear();
+}
+
+bool ClearKeyDecryptionManager::HasSeenKeyId(const KeyId& aKeyId) const {
+ CK_LOGD("ClearKeyDecryptionManager::SeenKeyId %s",
+ mDecryptors.find(aKeyId) != mDecryptors.end() ? "t" : "f");
+ return mDecryptors.find(aKeyId) != mDecryptors.end();
+}
+
+bool ClearKeyDecryptionManager::IsExpectingKeyForKeyId(
+ const KeyId& aKeyId) const {
+ CK_LOGARRAY("ClearKeyDecryptionManager::IsExpectingKeyForId ", aKeyId.data(),
+ aKeyId.size());
+ const auto& decryptor = mDecryptors.find(aKeyId);
+ return decryptor != mDecryptors.end() && !decryptor->second->HasKey();
+}
+
+bool ClearKeyDecryptionManager::HasKeyForKeyId(const KeyId& aKeyId) const {
+ CK_LOGD("ClearKeyDecryptionManager::HasKeyForKeyId");
+ const auto& decryptor = mDecryptors.find(aKeyId);
+ return decryptor != mDecryptors.end() && decryptor->second->HasKey();
+}
+
+const Key& ClearKeyDecryptionManager::GetDecryptionKey(const KeyId& aKeyId) {
+ assert(HasKeyForKeyId(aKeyId));
+ return mDecryptors[aKeyId]->DecryptionKey();
+}
+
+void ClearKeyDecryptionManager::InitKey(KeyId aKeyId, Key aKey) {
+ CK_LOGD("ClearKeyDecryptionManager::InitKey ", aKeyId.data(), aKeyId.size());
+ if (IsExpectingKeyForKeyId(aKeyId)) {
+ CK_LOGARRAY("Initialized Key ", aKeyId.data(), aKeyId.size());
+ mDecryptors[aKeyId]->InitKey(aKey);
+ } else {
+ CK_LOGARRAY("Failed to initialize key ", aKeyId.data(), aKeyId.size());
+ }
+}
+
+void ClearKeyDecryptionManager::ExpectKeyId(KeyId aKeyId) {
+ CK_LOGD("ClearKeyDecryptionManager::ExpectKeyId ", aKeyId.data(),
+ aKeyId.size());
+ if (!HasSeenKeyId(aKeyId)) {
+ mDecryptors[aKeyId] = new ClearKeyDecryptor();
+ }
+ mDecryptors[aKeyId]->AddRef();
+}
+
+void ClearKeyDecryptionManager::ReleaseKeyId(KeyId aKeyId) {
+ CK_LOGD("ClearKeyDecryptionManager::ReleaseKeyId");
+ assert(HasSeenKeyId(aKeyId));
+
+ ClearKeyDecryptor* decryptor = mDecryptors[aKeyId];
+ if (!decryptor->Release()) {
+ mDecryptors.erase(aKeyId);
+ }
+}
+
+Status ClearKeyDecryptionManager::Decrypt(std::vector<uint8_t>& aBuffer,
+ const CryptoMetaData& aMetadata) {
+ return Decrypt(&aBuffer[0], aBuffer.size(), aMetadata);
+}
+
+Status ClearKeyDecryptionManager::Decrypt(uint8_t* aBuffer,
+ uint32_t aBufferSize,
+ const CryptoMetaData& aMetadata) {
+ CK_LOGD("ClearKeyDecryptionManager::Decrypt");
+ if (!HasKeyForKeyId(aMetadata.mKeyId)) {
+ CK_LOGARRAY("Unable to find decryptor for keyId: ", aMetadata.mKeyId.data(),
+ aMetadata.mKeyId.size());
+ return Status::kNoKey;
+ }
+
+ CK_LOGARRAY("Found decryptor for keyId: ", aMetadata.mKeyId.data(),
+ aMetadata.mKeyId.size());
+ return mDecryptors[aMetadata.mKeyId]->Decrypt(aBuffer, aBufferSize,
+ aMetadata);
+}
+
+ClearKeyDecryptor::ClearKeyDecryptor() { CK_LOGD("ClearKeyDecryptor ctor"); }
+
+ClearKeyDecryptor::~ClearKeyDecryptor() {
+ if (HasKey()) {
+ CK_LOGARRAY("ClearKeyDecryptor dtor; key = ", mKey.data(), mKey.size());
+ } else {
+ CK_LOGD("ClearKeyDecryptor dtor");
+ }
+}
+
+void ClearKeyDecryptor::InitKey(const Key& aKey) { mKey = aKey; }
+
+Status ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+ const CryptoMetaData& aMetadata) {
+ CK_LOGD("ClearKeyDecryptor::Decrypt");
+ // If the sample is split up into multiple encrypted subsamples, we need to
+ // stitch them into one continuous buffer for decryption.
+ std::vector<uint8_t> tmp(aBufferSize);
+ static_assert(sizeof(uintptr_t) == sizeof(uint8_t*),
+ "We need uintptr_t to be exactly the same size as a pointer");
+
+ // Decrypt CBCS case:
+ if (aMetadata.mEncryptionScheme == EncryptionScheme::kCbcs) {
+ mozilla::CheckedInt<uintptr_t> data = reinterpret_cast<uintptr_t>(aBuffer);
+ if (!data.isValid()) {
+ return Status::kDecryptError;
+ }
+ const uintptr_t endBuffer =
+ reinterpret_cast<uintptr_t>(aBuffer + aBufferSize);
+
+ if (aMetadata.NumSubsamples() == 0) {
+ if (data.value() > endBuffer) {
+ return Status::kDecryptError;
+ }
+ mozilla::Span<uint8_t> encryptedSpan =
+ mozilla::Span(reinterpret_cast<uint8_t*>(data.value()), aBufferSize);
+ if (!ClearKeyUtils::DecryptCbcs(mKey, aMetadata.mIV, encryptedSpan,
+ aMetadata.mCryptByteBlock,
+ aMetadata.mSkipByteBlock)) {
+ return Status::kDecryptError;
+ }
+ return Status::kSuccess;
+ }
+
+ for (size_t i = 0; i < aMetadata.NumSubsamples(); i++) {
+ data += aMetadata.mClearBytes[i];
+ if (!data.isValid() || data.value() > endBuffer) {
+ return Status::kDecryptError;
+ }
+ mozilla::CheckedInt<uintptr_t> dataAfterCipher =
+ data + aMetadata.mCipherBytes[i];
+ if (!dataAfterCipher.isValid() || dataAfterCipher.value() > endBuffer) {
+ // Trying to read past the end of the buffer!
+ return Status::kDecryptError;
+ }
+ mozilla::Span<uint8_t> encryptedSpan = mozilla::Span(
+ reinterpret_cast<uint8_t*>(data.value()), aMetadata.mCipherBytes[i]);
+ if (!ClearKeyUtils::DecryptCbcs(mKey, aMetadata.mIV, encryptedSpan,
+ aMetadata.mCryptByteBlock,
+ aMetadata.mSkipByteBlock)) {
+ return Status::kDecryptError;
+ }
+ data += aMetadata.mCipherBytes[i];
+ if (!data.isValid()) {
+ return Status::kDecryptError;
+ }
+ }
+ return Status::kSuccess;
+ }
+
+ // Decrypt CENC case:
+ if (aMetadata.NumSubsamples()) {
+ // Take all encrypted parts of subsamples and stitch them into one
+ // continuous encrypted buffer.
+ mozilla::CheckedInt<uintptr_t> data = reinterpret_cast<uintptr_t>(aBuffer);
+ const uintptr_t endBuffer =
+ reinterpret_cast<uintptr_t>(aBuffer + aBufferSize);
+ uint8_t* iter = &tmp[0];
+ for (size_t i = 0; i < aMetadata.NumSubsamples(); i++) {
+ data += aMetadata.mClearBytes[i];
+ if (!data.isValid() || data.value() > endBuffer) {
+ // Trying to read past the end of the buffer!
+ return Status::kDecryptError;
+ }
+ const uint32_t& cipherBytes = aMetadata.mCipherBytes[i];
+ mozilla::CheckedInt<uintptr_t> dataAfterCipher = data + cipherBytes;
+ if (!dataAfterCipher.isValid() || dataAfterCipher.value() > endBuffer) {
+ // Trying to read past the end of the buffer!
+ return Status::kDecryptError;
+ }
+
+ memcpy(iter, reinterpret_cast<uint8_t*>(data.value()), cipherBytes);
+
+ data = dataAfterCipher;
+ iter += cipherBytes;
+ }
+
+ tmp.resize((size_t)(iter - &tmp[0]));
+ } else {
+ memcpy(&tmp[0], aBuffer, aBufferSize);
+ }
+
+ // It is possible that we could be passed an unencrypted sample, if all
+ // encrypted sample lengths are zero, and in this case, a zero length
+ // IV is allowed.
+ assert(aMetadata.mIV.size() == 8 || aMetadata.mIV.size() == 16 ||
+ (aMetadata.mIV.empty() && AllZero(aMetadata.mCipherBytes)));
+
+ std::vector<uint8_t> iv(aMetadata.mIV);
+ iv.insert(iv.end(), CENC_KEY_LEN - aMetadata.mIV.size(), 0);
+
+ if (!ClearKeyUtils::DecryptAES(mKey, tmp, iv)) {
+ return Status::kDecryptError;
+ }
+
+ if (aMetadata.NumSubsamples()) {
+ // Take the decrypted buffer, split up into subsamples, and insert those
+ // subsamples back into their original position in the original buffer.
+ uint8_t* data = aBuffer;
+ uint8_t* iter = &tmp[0];
+ for (size_t i = 0; i < aMetadata.NumSubsamples(); i++) {
+ data += aMetadata.mClearBytes[i];
+ uint32_t cipherBytes = aMetadata.mCipherBytes[i];
+
+ memcpy(data, iter, cipherBytes);
+
+ data += cipherBytes;
+ iter += cipherBytes;
+ }
+ } else {
+ memcpy(aBuffer, &tmp[0], aBufferSize);
+ }
+
+ return Status::kSuccess;
+}
diff --git a/dom/media/eme/clearkey/ClearKeyDecryptionManager.h b/dom/media/eme/clearkey/ClearKeyDecryptionManager.h
new file mode 100644
index 0000000000..c21ff3119a
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeyDecryptionManager.h
@@ -0,0 +1,111 @@
+/*
+ * 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 __ClearKeyDecryptionManager_h__
+#define __ClearKeyDecryptionManager_h__
+
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <map>
+
+#include "content_decryption_module.h"
+
+#include "ClearKeyUtils.h"
+#include "RefCounted.h"
+
+class ClearKeyDecryptor;
+
+class CryptoMetaData {
+ public:
+ CryptoMetaData() = default;
+
+ explicit CryptoMetaData(const cdm::InputBuffer_2* aInputBuffer) {
+ Init(aInputBuffer);
+ }
+
+ void Init(const cdm::InputBuffer_2* aInputBuffer) {
+ if (!aInputBuffer) {
+ assert(!IsValid());
+ return;
+ }
+
+ mEncryptionScheme = aInputBuffer->encryption_scheme;
+ Assign(mKeyId, aInputBuffer->key_id, aInputBuffer->key_id_size);
+ Assign(mIV, aInputBuffer->iv, aInputBuffer->iv_size);
+ mCryptByteBlock = aInputBuffer->pattern.crypt_byte_block;
+ mSkipByteBlock = aInputBuffer->pattern.skip_byte_block;
+
+ for (uint32_t i = 0; i < aInputBuffer->num_subsamples; ++i) {
+ const cdm::SubsampleEntry& subsample = aInputBuffer->subsamples[i];
+ mClearBytes.push_back(subsample.clear_bytes);
+ mCipherBytes.push_back(subsample.cipher_bytes);
+ }
+ }
+
+ bool IsValid() const {
+ return !mKeyId.empty() && !mIV.empty() && !mCipherBytes.empty() &&
+ !mClearBytes.empty();
+ }
+
+ size_t NumSubsamples() const {
+ assert(mClearBytes.size() == mCipherBytes.size());
+ return mClearBytes.size();
+ }
+
+ cdm::EncryptionScheme mEncryptionScheme;
+ std::vector<uint8_t> mKeyId;
+ std::vector<uint8_t> mIV;
+ uint32_t mCryptByteBlock;
+ uint32_t mSkipByteBlock;
+ std::vector<uint32_t> mClearBytes;
+ std::vector<uint32_t> mCipherBytes;
+};
+
+class ClearKeyDecryptionManager : public RefCounted {
+ private:
+ ClearKeyDecryptionManager();
+ ~ClearKeyDecryptionManager();
+
+ static ClearKeyDecryptionManager* sInstance;
+
+ public:
+ static ClearKeyDecryptionManager* Get();
+
+ bool HasSeenKeyId(const KeyId& aKeyId) const;
+ bool HasKeyForKeyId(const KeyId& aKeyId) const;
+
+ const Key& GetDecryptionKey(const KeyId& aKeyId);
+
+ // Create a decryptor for the given KeyId if one does not already exist.
+ void InitKey(KeyId aKeyId, Key aKey);
+ void ExpectKeyId(KeyId aKeyId);
+ void ReleaseKeyId(KeyId aKeyId);
+
+ // Decrypts buffer *in place*.
+ cdm::Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+ const CryptoMetaData& aMetadata);
+ cdm::Status Decrypt(std::vector<uint8_t>& aBuffer,
+ const CryptoMetaData& aMetadata);
+
+ private:
+ bool IsExpectingKeyForKeyId(const KeyId& aKeyId) const;
+
+ std::map<KeyId, ClearKeyDecryptor*> mDecryptors;
+};
+
+#endif // __ClearKeyDecryptionManager_h__
diff --git a/dom/media/eme/clearkey/ClearKeyPersistence.cpp b/dom/media/eme/clearkey/ClearKeyPersistence.cpp
new file mode 100644
index 0000000000..0b81f81399
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeyPersistence.cpp
@@ -0,0 +1,153 @@
+/*
+ * 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 "ClearKeyPersistence.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <sstream>
+
+#include "ClearKeySessionManager.h"
+#include "ClearKeyStorage.h"
+#include "ClearKeyUtils.h"
+#include "RefCounted.h"
+
+using namespace cdm;
+
+using std::function;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+void ClearKeyPersistence::ReadAllRecordsFromIndex(
+ function<void()>&& aOnComplete) {
+ // Clear what we think the index file contains, we're about to read it again.
+ mPersistentSessionIds.clear();
+
+ // Hold a reference to the persistence manager, so it isn't released before
+ // we try and use it.
+ RefPtr<ClearKeyPersistence> self(this);
+ function<void(const uint8_t*, uint32_t)> onIndexSuccess =
+ [self, aOnComplete](const uint8_t* data, uint32_t size) {
+ CK_LOGD("ClearKeyPersistence: Loaded index file!");
+ const char* charData = (const char*)data;
+
+ stringstream ss(string(charData, charData + size));
+ string name;
+ while (getline(ss, name)) {
+ if (ClearKeyUtils::IsValidSessionId(name.data(), name.size())) {
+ self->mPersistentSessionIds.insert(atoi(name.c_str()));
+ }
+ }
+
+ self->mPersistentKeyState = PersistentKeyState::LOADED;
+ aOnComplete();
+ };
+
+ function<void()> onIndexFailed = [self, aOnComplete]() {
+ CK_LOGD(
+ "ClearKeyPersistence: Failed to load index file (it might not exist");
+ self->mPersistentKeyState = PersistentKeyState::LOADED;
+ aOnComplete();
+ };
+
+ string filename = "index";
+ ReadData(mHost, filename, std::move(onIndexSuccess),
+ std::move(onIndexFailed));
+}
+
+void ClearKeyPersistence::WriteIndex() {
+ function<void()> onIndexSuccess = []() {
+ CK_LOGD("ClearKeyPersistence: Wrote index file");
+ };
+
+ function<void()> onIndexFail = []() {
+ CK_LOGD("ClearKeyPersistence: Failed to write index file (this is bad)");
+ };
+
+ stringstream ss;
+
+ for (const uint32_t& sessionId : mPersistentSessionIds) {
+ ss << sessionId;
+ ss << '\n';
+ }
+
+ string dataString = ss.str();
+ uint8_t* dataArray = (uint8_t*)dataString.data();
+ vector<uint8_t> data(dataArray, dataArray + dataString.size());
+
+ string filename = "index";
+ WriteData(mHost, filename, data, std::move(onIndexSuccess),
+ std::move(onIndexFail));
+}
+
+ClearKeyPersistence::ClearKeyPersistence(Host_10* aHost) {
+ this->mHost = aHost;
+}
+
+void ClearKeyPersistence::EnsureInitialized(bool aPersistentStateAllowed,
+ function<void()>&& aOnInitialized) {
+ if (aPersistentStateAllowed &&
+ mPersistentKeyState == PersistentKeyState::UNINITIALIZED) {
+ mPersistentKeyState = LOADING;
+ ReadAllRecordsFromIndex(std::move(aOnInitialized));
+ } else {
+ mPersistentKeyState = PersistentKeyState::LOADED;
+ aOnInitialized();
+ }
+}
+
+bool ClearKeyPersistence::IsLoaded() const {
+ return mPersistentKeyState == PersistentKeyState::LOADED;
+}
+
+string ClearKeyPersistence::GetNewSessionId(SessionType aSessionType) {
+ static uint32_t sNextSessionId = 1;
+
+ // Ensure we don't re-use a session id that was persisted.
+ while (Contains(mPersistentSessionIds, sNextSessionId)) {
+ sNextSessionId++;
+ }
+
+ string sessionId;
+ stringstream ss;
+ ss << sNextSessionId;
+ ss >> sessionId;
+
+ if (aSessionType == SessionType::kPersistentLicense) {
+ mPersistentSessionIds.insert(sNextSessionId);
+
+ // Save the updated index file.
+ WriteIndex();
+ }
+
+ sNextSessionId++;
+
+ return sessionId;
+}
+
+bool ClearKeyPersistence::IsPersistentSessionId(const string& aSessionId) {
+ return Contains(mPersistentSessionIds, atoi(aSessionId.c_str()));
+}
+
+void ClearKeyPersistence::PersistentSessionRemoved(string& aSessionId) {
+ mPersistentSessionIds.erase(atoi(aSessionId.c_str()));
+
+ // Update the index file.
+ WriteIndex();
+}
diff --git a/dom/media/eme/clearkey/ClearKeyPersistence.h b/dom/media/eme/clearkey/ClearKeyPersistence.h
new file mode 100644
index 0000000000..b832db8c11
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeyPersistence.h
@@ -0,0 +1,64 @@
+/*
+ * 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 __ClearKeyPersistence_h__
+#define __ClearKeyPersistence_h__
+
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <functional>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "content_decryption_module.h"
+
+#include "RefCounted.h"
+
+class ClearKeySessionManager;
+
+// Whether we've loaded the persistent session ids yet.
+enum PersistentKeyState { UNINITIALIZED, LOADING, LOADED };
+
+class ClearKeyPersistence : public RefCounted {
+ public:
+ explicit ClearKeyPersistence(cdm::Host_10* aHost);
+
+ void EnsureInitialized(bool aPersistentStateAllowed,
+ std::function<void()>&& aOnInitialized);
+
+ bool IsLoaded() const;
+
+ std::string GetNewSessionId(cdm::SessionType aSessionType);
+
+ bool IsPersistentSessionId(const std::string& aSid);
+
+ void PersistentSessionRemoved(std::string& aSid);
+
+ private:
+ cdm::Host_10* mHost = nullptr;
+
+ PersistentKeyState mPersistentKeyState = PersistentKeyState::UNINITIALIZED;
+
+ std::set<uint32_t> mPersistentSessionIds;
+
+ void ReadAllRecordsFromIndex(std::function<void()>&& aOnComplete);
+ void WriteIndex();
+};
+
+#endif // __ClearKeyPersistence_h__
diff --git a/dom/media/eme/clearkey/ClearKeySession.cpp b/dom/media/eme/clearkey/ClearKeySession.cpp
new file mode 100644
index 0000000000..59e104fc54
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeySession.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 <assert.h>
+#include <string.h>
+
+#include "BigEndian.h"
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeySession.h"
+#include "ClearKeyStorage.h"
+#include "ClearKeyUtils.h"
+#include "psshparser/PsshParser.h"
+
+using namespace mozilla;
+using namespace cdm;
+
+ClearKeySession::ClearKeySession(const std::string& aSessionId,
+ SessionType aSessionType)
+ : mSessionId(aSessionId), mSessionType(aSessionType) {
+ CK_LOGD("ClearKeySession ctor %p", this);
+}
+
+ClearKeySession::~ClearKeySession() {
+ CK_LOGD("ClearKeySession dtor %p", this);
+}
+
+bool ClearKeySession::Init(InitDataType aInitDataType, const uint8_t* aInitData,
+ uint32_t aInitDataSize) {
+ CK_LOGD("ClearKeySession::Init");
+
+ if (aInitDataType == InitDataType::kCenc) {
+ ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
+ } else if (aInitDataType == InitDataType::kKeyIds) {
+ ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds);
+ } else if (aInitDataType == InitDataType::kWebM &&
+ aInitDataSize <= kMaxWebmInitDataSize) {
+ // "webm" initData format is simply the raw bytes of the keyId.
+ std::vector<uint8_t> keyId;
+ keyId.assign(aInitData, aInitData + aInitDataSize);
+ mKeyIds.push_back(keyId);
+ }
+
+ if (mKeyIds.empty()) {
+ CK_LOGD("ClearKeySession::Init, failed to get keyId");
+ return false;
+ }
+#ifdef WMF_CLEARKEY_DEBUG
+ for (const auto& keyId : mKeyIds) {
+ CK_LOGARRAY("ClearKeySession::Init, KeyId : ", keyId.data(), keyId.size());
+ }
+#endif
+ return true;
+}
+
+SessionType ClearKeySession::Type() const { return mSessionType; }
+
+void ClearKeySession::AddKeyId(const KeyId& aKeyId) {
+ mKeyIds.push_back(aKeyId);
+}
diff --git a/dom/media/eme/clearkey/ClearKeySession.h b/dom/media/eme/clearkey/ClearKeySession.h
new file mode 100644
index 0000000000..3a130aa45a
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeySession.h
@@ -0,0 +1,56 @@
+/*
+ * 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 __ClearKeySession_h__
+#define __ClearKeySession_h__
+
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "content_decryption_module.h"
+
+#include "ClearKeyUtils.h"
+
+class ClearKeySession {
+ public:
+ explicit ClearKeySession(const std::string& aSessionId,
+ cdm::SessionType aSessionType);
+
+ ~ClearKeySession();
+
+ const std::vector<KeyId>& GetKeyIds() const { return mKeyIds; }
+
+ bool Init(cdm::InitDataType aInitDataType, const uint8_t* aInitData,
+ uint32_t aInitDataSize);
+
+ cdm::SessionType Type() const;
+
+ void AddKeyId(const KeyId& aKeyId);
+
+ const std::string& Id() const { return mSessionId; }
+
+ private:
+ const std::string mSessionId;
+ std::vector<KeyId> mKeyIds;
+
+ const cdm::SessionType mSessionType;
+};
+
+#endif // __ClearKeySession_h__
diff --git a/dom/media/eme/clearkey/ClearKeySessionManager.cpp b/dom/media/eme/clearkey/ClearKeySessionManager.cpp
new file mode 100644
index 0000000000..54a15f4fcf
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeySessionManager.cpp
@@ -0,0 +1,713 @@
+/*
+ * 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 "ClearKeySessionManager.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "content_decryption_module.h"
+
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeyPersistence.h"
+#include "ClearKeyStorage.h"
+#include "ClearKeyUtils.h"
+#include "psshparser/PsshParser.h"
+
+using namespace cdm;
+
+using std::function;
+using std::string;
+using std::vector;
+
+ClearKeySessionManager::ClearKeySessionManager(Host_10* aHost)
+ : mDecryptionManager(ClearKeyDecryptionManager::Get()) {
+ CK_LOGD("ClearKeySessionManager ctor %p", this);
+ AddRef();
+
+ mHost = aHost;
+ mPersistence = new ClearKeyPersistence(mHost);
+}
+
+ClearKeySessionManager::~ClearKeySessionManager() {
+ CK_LOGD("ClearKeySessionManager dtor %p", this);
+}
+
+void ClearKeySessionManager::Init(bool aDistinctiveIdentifierAllowed,
+ bool aPersistentStateAllowed) {
+ CK_LOGD("ClearKeySessionManager::Init");
+
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> onPersistentStateLoaded = [self]() {
+ while (!self->mDeferredInitialize.empty()) {
+ function<void()> func = self->mDeferredInitialize.front();
+ self->mDeferredInitialize.pop();
+
+ func();
+ }
+ if (self->mHost) {
+ // The session manager should be the last thing the ClearKey CDM is
+ // waiting on to be initialized.
+ self->mHost->OnInitialized(true);
+ }
+ };
+
+ mPersistence->EnsureInitialized(aPersistentStateAllowed,
+ std::move(onPersistentStateLoaded));
+}
+
+void ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
+ InitDataType aInitDataType,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ SessionType aSessionType) {
+ CK_LOGD("ClearKeySessionManager::CreateSession type:%u", aInitDataType);
+
+ // Copy the init data so it is correctly captured by the lambda
+ vector<uint8_t> initData(aInitData, aInitData + aInitDataSize);
+
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> deferrer = [self, aPromiseId, aInitDataType, initData,
+ aSessionType]() {
+ self->CreateSession(aPromiseId, aInitDataType, initData.data(),
+ initData.size(), aSessionType);
+ };
+
+ // If we haven't loaded, don't do this yet
+ if (MaybeDeferTillInitialized(std::move(deferrer))) {
+ CK_LOGD("Deferring CreateSession");
+ return;
+ }
+
+ CK_LOGARRAY("ClearKeySessionManager::CreateSession initdata: ", aInitData,
+ aInitDataSize);
+
+ // If 'DecryptingComplete' has been called mHost will be null so we can't
+ // won't be able to resolve our promise
+ if (!mHost) {
+ CK_LOGD("ClearKeySessionManager::CreateSession: mHost is nullptr");
+ return;
+ }
+
+ // initDataType must be "cenc", "keyids", or "webm".
+ if (aInitDataType != InitDataType::kCenc &&
+ aInitDataType != InitDataType::kKeyIds &&
+ aInitDataType != InitDataType::kWebM) {
+ string message = "initDataType is not supported by ClearKey";
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionNotSupportedError,
+ 0, message.c_str(), message.size());
+
+ return;
+ }
+
+ string sessionId = mPersistence->GetNewSessionId(aSessionType);
+ assert(mSessions.find(sessionId) == mSessions.end());
+
+ ClearKeySession* session = new ClearKeySession(sessionId, aSessionType);
+
+ if (!session->Init(aInitDataType, aInitData, aInitDataSize)) {
+ CK_LOGD("Failed to initialize session: %s", sessionId.c_str());
+
+ const static char* message = "Failed to initialize session";
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionInvalidStateError,
+ 0, message, strlen(message));
+ delete session;
+
+ return;
+ }
+
+ mSessions[sessionId] = session;
+ mLastSessionId = sessionId;
+
+ const vector<KeyId>& sessionKeys = session->GetKeyIds();
+ vector<KeyId> neededKeys;
+
+ for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) {
+ // Need to request this key ID from the client. We always send a key
+ // request, whether or not another session has sent a request with the same
+ // key ID. Otherwise a script can end up waiting for another script to
+ // respond to the request (which may not necessarily happen).
+ neededKeys.push_back(*it);
+ mDecryptionManager->ExpectKeyId(*it);
+ }
+
+ if (neededKeys.empty()) {
+ CK_LOGD("No keys needed from client.");
+ return;
+ }
+
+ // Send a request for needed key data.
+ string request;
+ ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
+
+ // Resolve the promise with the new session information.
+ mHost->OnResolveNewSessionPromise(aPromiseId, sessionId.c_str(),
+ sessionId.size());
+
+ mHost->OnSessionMessage(sessionId.c_str(), sessionId.size(),
+ MessageType::kLicenseRequest, request.c_str(),
+ request.size());
+}
+
+void ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) {
+ CK_LOGD("ClearKeySessionManager::LoadSession");
+
+ // Copy the sessionId into a string so the lambda captures it properly.
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+
+ // Hold a reference to the SessionManager so that it isn't released before
+ // we try to use it.
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> deferrer = [self, aPromiseId, sessionId]() {
+ self->LoadSession(aPromiseId, sessionId.data(), sessionId.size());
+ };
+
+ if (MaybeDeferTillInitialized(std::move(deferrer))) {
+ CK_LOGD("Deferring LoadSession");
+ return;
+ }
+
+ // If the SessionManager has been shutdown mHost will be null and we won't
+ // be able to resolve the promise.
+ if (!mHost) {
+ return;
+ }
+
+ if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
+ mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ return;
+ }
+
+ if (!mPersistence->IsPersistentSessionId(sessionId)) {
+ mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ return;
+ }
+
+ function<void(const uint8_t*, uint32_t)> success =
+ [self, sessionId, aPromiseId](const uint8_t* data, uint32_t size) {
+ self->PersistentSessionDataLoaded(aPromiseId, sessionId, data, size);
+ };
+
+ function<void()> failure = [self, aPromiseId] {
+ if (!self->mHost) {
+ return;
+ }
+ // As per the API described in ContentDecryptionModule_8
+ self->mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ };
+
+ ReadData(mHost, sessionId, std::move(success), std::move(failure));
+}
+
+void ClearKeySessionManager::PersistentSessionDataLoaded(
+ uint32_t aPromiseId, const string& aSessionId, const uint8_t* aKeyData,
+ uint32_t aKeyDataSize) {
+ CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded");
+
+ // Check that the SessionManager has not been shut down before we try and
+ // resolve any promises.
+ if (!mHost) {
+ return;
+ }
+
+ if (Contains(mSessions, aSessionId) ||
+ (aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) {
+ // As per the instructions in ContentDecryptionModule_8
+ mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ return;
+ }
+
+ ClearKeySession* session =
+ new ClearKeySession(aSessionId, SessionType::kPersistentLicense);
+
+ mSessions[aSessionId] = session;
+ mLastSessionId = aSessionId;
+
+ uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
+
+ vector<KeyInformation> keyInfos;
+ vector<KeyIdPair> keyPairs;
+ for (uint32_t i = 0; i < numKeys; i++) {
+ const uint8_t* base = aKeyData + 2 * CENC_KEY_LEN * i;
+
+ KeyIdPair keyPair;
+
+ keyPair.mKeyId = KeyId(base, base + CENC_KEY_LEN);
+ assert(keyPair.mKeyId.size() == CENC_KEY_LEN);
+
+ keyPair.mKey = Key(base + CENC_KEY_LEN, base + 2 * CENC_KEY_LEN);
+ assert(keyPair.mKey.size() == CENC_KEY_LEN);
+
+ session->AddKeyId(keyPair.mKeyId);
+
+ mDecryptionManager->ExpectKeyId(keyPair.mKeyId);
+ mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
+ mKeyIds.insert(keyPair.mKey);
+ keyPairs.push_back(keyPair);
+
+ KeyInformation keyInfo = {};
+ keyInfo.key_id = &keyPairs.back().mKeyId[0];
+ keyInfo.key_id_size = keyPair.mKeyId.size();
+ keyInfo.status = KeyStatus::kUsable;
+
+ keyInfos.push_back(keyInfo);
+ }
+
+ mHost->OnSessionKeysChange(&aSessionId[0], aSessionId.size(), true,
+ keyInfos.data(), keyInfos.size());
+
+ mHost->OnResolveNewSessionPromise(aPromiseId, aSessionId.c_str(),
+ aSessionId.size());
+}
+
+void ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) {
+ CK_LOGD("ClearKeySessionManager::UpdateSession");
+
+ // Copy the method arguments so we can capture them in the lambda
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+ vector<uint8_t> response(aResponse, aResponse + aResponseSize);
+
+ // Hold a reference to the SessionManager so it isn't released before we
+ // callback.
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> deferrer = [self, aPromiseId, sessionId, response]() {
+ self->UpdateSession(aPromiseId, sessionId.data(), sessionId.size(),
+ response.data(), response.size());
+ };
+
+ // If we haven't fully loaded, defer calling this method
+ if (MaybeDeferTillInitialized(std::move(deferrer))) {
+ CK_LOGD("Deferring LoadSession");
+ return;
+ }
+
+ // Make sure the SessionManager has not been shutdown before we try and
+ // resolve any promises.
+ if (!mHost) {
+ return;
+ }
+
+ CK_LOGD("Updating session: %s", sessionId.c_str());
+
+ auto itr = mSessions.find(sessionId);
+ if (itr == mSessions.end() || !(itr->second)) {
+ CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
+ CK_LOGD("Unable to find session: %s", sessionId.c_str());
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+ ClearKeySession* session = itr->second;
+
+ // Verify the size of session response.
+ if (aResponseSize >= kMaxSessionResponseLength) {
+ CK_LOGW("Session response size is not within a reasonable size.");
+ CK_LOGD("Failed to parse response for session %s", sessionId.c_str());
+
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ // Parse the response for any (key ID, key) pairs.
+ vector<KeyIdPair> keyPairs;
+ if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs,
+ session->Type())) {
+ CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
+
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ vector<KeyInformation> keyInfos;
+ for (size_t i = 0; i < keyPairs.size(); i++) {
+ KeyIdPair& keyPair = keyPairs[i];
+ mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
+ mKeyIds.insert(keyPair.mKeyId);
+
+ KeyInformation keyInfo = {};
+ keyInfo.key_id = &keyPair.mKeyId[0];
+ keyInfo.key_id_size = keyPair.mKeyId.size();
+ keyInfo.status = KeyStatus::kUsable;
+
+ keyInfos.push_back(keyInfo);
+ }
+
+ mHost->OnSessionKeysChange(aSessionId, aSessionIdLength, true,
+ keyInfos.data(), keyInfos.size());
+
+ if (session->Type() != SessionType::kPersistentLicense) {
+ mHost->OnResolvePromise(aPromiseId);
+ return;
+ }
+
+ // Store the keys on disk. We store a record whose name is the sessionId,
+ // and simply append each keyId followed by its key.
+ vector<uint8_t> keydata;
+ Serialize(session, keydata);
+
+ function<void()> resolve = [self, aPromiseId]() {
+ if (!self->mHost) {
+ return;
+ }
+ self->mHost->OnResolvePromise(aPromiseId);
+ };
+
+ function<void()> reject = [self, aPromiseId]() {
+ if (!self->mHost) {
+ return;
+ }
+
+ static const char* message = "Couldn't store cenc key init data";
+ self->mHost->OnRejectPromise(aPromiseId,
+ Exception::kExceptionInvalidStateError, 0,
+ message, strlen(message));
+ };
+
+ WriteData(mHost, sessionId, keydata, std::move(resolve), std::move(reject));
+}
+
+void ClearKeySessionManager::Serialize(const ClearKeySession* aSession,
+ std::vector<uint8_t>& aOutKeyData) {
+ const std::vector<KeyId>& keyIds = aSession->GetKeyIds();
+ for (size_t i = 0; i < keyIds.size(); i++) {
+ const KeyId& keyId = keyIds[i];
+ if (!mDecryptionManager->HasKeyForKeyId(keyId)) {
+ continue;
+ }
+ assert(keyId.size() == CENC_KEY_LEN);
+ aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end());
+ const Key& key = mDecryptionManager->GetDecryptionKey(keyId);
+ assert(key.size() == CENC_KEY_LEN);
+ aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end());
+ }
+}
+
+void ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) {
+ CK_LOGD("ClearKeySessionManager::CloseSession");
+
+ // Copy the sessionId into a string so we capture it properly.
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+ // Hold a reference to the session manager, so it doesn't get deleted
+ // before we need to use it.
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> deferrer = [self, aPromiseId, sessionId]() {
+ self->CloseSession(aPromiseId, sessionId.data(), sessionId.size());
+ };
+
+ // If we haven't loaded, call this method later.
+ if (MaybeDeferTillInitialized(std::move(deferrer))) {
+ CK_LOGD("Deferring CloseSession");
+ return;
+ }
+
+ // If DecryptingComplete has been called mHost will be null and we won't
+ // be able to resolve our promise.
+ if (!mHost) {
+ return;
+ }
+
+ auto itr = mSessions.find(sessionId);
+ if (itr == mSessions.end()) {
+ CK_LOGW("ClearKey CDM couldn't close non-existent session.");
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ ClearKeySession* session = itr->second;
+ assert(session);
+
+ ClearInMemorySessionData(session);
+
+ mHost->OnSessionClosed(aSessionId, aSessionIdLength);
+ mHost->OnResolvePromise(aPromiseId);
+}
+
+void ClearKeySessionManager::ClearInMemorySessionData(
+ ClearKeySession* aSession) {
+ mSessions.erase(aSession->Id());
+ delete aSession;
+}
+
+void ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) {
+ CK_LOGD("ClearKeySessionManager::RemoveSession");
+
+ // Copy the sessionId into a string so it can be captured for the lambda.
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+
+ // Hold a reference to the SessionManager, so it isn't released before we
+ // try and use it.
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> deferrer = [self, aPromiseId, sessionId]() {
+ self->RemoveSession(aPromiseId, sessionId.data(), sessionId.size());
+ };
+
+ // If we haven't fully loaded, defer calling this method.
+ if (MaybeDeferTillInitialized(std::move(deferrer))) {
+ CK_LOGD("Deferring RemoveSession");
+ return;
+ }
+
+ // Check that the SessionManager has not been shutdown before we try and
+ // resolve any promises.
+ if (!mHost) {
+ return;
+ }
+
+ auto itr = mSessions.find(sessionId);
+ if (itr == mSessions.end()) {
+ CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
+
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ ClearKeySession* session = itr->second;
+ assert(session);
+ string sid = session->Id();
+ bool isPersistent = session->Type() == SessionType::kPersistentLicense;
+ ClearInMemorySessionData(session);
+
+ if (!isPersistent) {
+ mHost->OnResolvePromise(aPromiseId);
+ return;
+ }
+
+ mPersistence->PersistentSessionRemoved(sid);
+
+ vector<uint8_t> emptyKeydata;
+
+ function<void()> resolve = [self, aPromiseId]() {
+ if (!self->mHost) {
+ return;
+ }
+ self->mHost->OnResolvePromise(aPromiseId);
+ };
+
+ function<void()> reject = [self, aPromiseId]() {
+ if (!self->mHost) {
+ return;
+ }
+ static const char* message = "Could not remove session";
+ self->mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ message, strlen(message));
+ };
+
+ WriteData(mHost, sessionId, emptyKeydata, std::move(resolve),
+ std::move(reject));
+}
+
+void ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize) {
+ // ClearKey CDM doesn't support this method by spec.
+ CK_LOGD("ClearKeySessionManager::SetServerCertificate");
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionNotSupportedError, 0,
+ nullptr /* message */, 0 /* messageLen */);
+}
+
+Status ClearKeySessionManager::Decrypt(const InputBuffer_2& aBuffer,
+ DecryptedBlock* aDecryptedBlock) {
+ CK_LOGD("ClearKeySessionManager::Decrypt");
+
+ CK_LOGARRAY("Key: ", aBuffer.key_id, aBuffer.key_id_size);
+
+ Buffer* buffer = mHost->Allocate(aBuffer.data_size);
+ assert(buffer != nullptr);
+ assert(buffer->Data() != nullptr);
+ assert(buffer->Capacity() >= aBuffer.data_size);
+
+ memcpy(buffer->Data(), aBuffer.data, aBuffer.data_size);
+
+ Status status = Status::kSuccess;
+ // According to the comment `If |iv_size| = 0, the data is unencrypted.`
+ // Use iv_size to determine if the sample is encrypted.
+ if (aBuffer.iv_size != 0) {
+ status = mDecryptionManager->Decrypt(buffer->Data(), buffer->Size(),
+ CryptoMetaData(&aBuffer));
+ }
+
+ aDecryptedBlock->SetDecryptedBuffer(buffer);
+ aDecryptedBlock->SetTimestamp(aBuffer.timestamp);
+
+ return status;
+}
+
+void ClearKeySessionManager::DecryptingComplete() {
+ CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this);
+
+ for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
+ delete it->second;
+ }
+ mSessions.clear();
+ mLastSessionId = std::nullopt;
+
+ mDecryptionManager = nullptr;
+ mHost = nullptr;
+
+ Release();
+}
+
+bool ClearKeySessionManager::MaybeDeferTillInitialized(
+ function<void()>&& aMaybeDefer) {
+ if (mPersistence->IsLoaded()) {
+ return false;
+ }
+
+ mDeferredInitialize.emplace(std::move(aMaybeDefer));
+ return true;
+}
+
+void ClearKeySessionManager::OnQueryOutputProtectionStatus(
+ QueryResult aResult, uint32_t aLinkMask, uint32_t aOutputProtectionMask) {
+ MOZ_ASSERT(mHasOutstandingOutputProtectionQuery,
+ "Should only be called if a query is outstanding");
+ CK_LOGD("ClearKeySessionManager::OnQueryOutputProtectionStatus");
+ mHasOutstandingOutputProtectionQuery = false;
+
+ if (aResult == QueryResult::kQueryFailed) {
+ // Indicate the query failed. This can happen if we're in shutdown.
+ NotifyOutputProtectionStatus(KeyStatus::kInternalError);
+ return;
+ }
+
+ if (aLinkMask & OutputLinkTypes::kLinkTypeNetwork) {
+ NotifyOutputProtectionStatus(KeyStatus::kOutputRestricted);
+ return;
+ }
+
+ NotifyOutputProtectionStatus(KeyStatus::kUsable);
+}
+
+void ClearKeySessionManager::QueryOutputProtectionStatusIfNeeded() {
+ MOZ_ASSERT(
+ mHost,
+ "Should not query protection status if we're shutdown (mHost == null)!");
+ CK_LOGD(
+ "ClearKeySessionManager::UpdateOutputProtectionStatusAndQueryIfNeeded");
+ if (mLastOutputProtectionQueryTime.IsNull()) {
+ // We haven't perfomed a check yet, get a query going.
+ MOZ_ASSERT(
+ !mHasOutstandingOutputProtectionQuery,
+ "Shouldn't have an outstanding query if we haven't recorded a time");
+ QueryOutputProtectionStatusFromHost();
+ return;
+ }
+
+ MOZ_ASSERT(!mLastOutputProtectionQueryTime.IsNull(),
+ "Should have already handled the case where we don't yet have a "
+ "previous check time");
+ const mozilla::TimeStamp now = mozilla::TimeStamp::NowLoRes();
+ const mozilla::TimeDuration timeSinceQuery =
+ now - mLastOutputProtectionQueryTime;
+
+ // The time between output protection checks to the host. I.e. if this amount
+ // of time has passed since the last check with the host, another should be
+ // performed (provided the first check has been handled).
+ static const mozilla::TimeDuration kOutputProtectionQueryInterval =
+ mozilla::TimeDuration::FromSeconds(0.2);
+ // The number of kOutputProtectionQueryInterval intervals we can miss before
+ // we decide a check has failed. I.e. if this value is 2, if we have not
+ // received a reply to a check after kOutputProtectionQueryInterval * 2
+ // time, we consider the check failed.
+ constexpr uint32_t kMissedIntervalsBeforeFailure = 2;
+ // The length of time after which we will restrict output until we get a
+ // query response.
+ static const mozilla::TimeDuration kTimeToWaitBeforeFailure =
+ kOutputProtectionQueryInterval * kMissedIntervalsBeforeFailure;
+
+ if ((timeSinceQuery > kOutputProtectionQueryInterval) &&
+ !mHasOutstandingOutputProtectionQuery) {
+ // We don't have an outstanding query and enough time has passed we should
+ // query again.
+ QueryOutputProtectionStatusFromHost();
+ return;
+ }
+
+ if ((timeSinceQuery > kTimeToWaitBeforeFailure) &&
+ mHasOutstandingOutputProtectionQuery) {
+ // A reponse was not received fast enough, notify.
+ NotifyOutputProtectionStatus(KeyStatus::kInternalError);
+ }
+}
+
+void ClearKeySessionManager::QueryOutputProtectionStatusFromHost() {
+ MOZ_ASSERT(
+ mHost,
+ "Should not query protection status if we're shutdown (mHost == null)!");
+ CK_LOGD("ClearKeySessionManager::QueryOutputProtectionStatusFromHost");
+ if (mHost) {
+ mLastOutputProtectionQueryTime = mozilla::TimeStamp::NowLoRes();
+ mHost->QueryOutputProtectionStatus();
+ mHasOutstandingOutputProtectionQuery = true;
+ }
+}
+
+void ClearKeySessionManager::NotifyOutputProtectionStatus(KeyStatus aStatus) {
+ MOZ_ASSERT(aStatus == KeyStatus::kUsable ||
+ aStatus == KeyStatus::kOutputRestricted ||
+ aStatus == KeyStatus::kInternalError,
+ "aStatus should have an expected value");
+ CK_LOGD("ClearKeySessionManager::NotifyOutputProtectionStatus");
+ if (!mLastSessionId.has_value()) {
+ // If we don't have a session id, either because we're too early, or are
+ // shutting down, don't notify.
+ return;
+ }
+
+ string& lastSessionId = mLastSessionId.value();
+
+ // Use 'output-protection' as the key ID. This helps tests disambiguate key
+ // status updates related to this.
+ const uint8_t kKeyId[] = {'o', 'u', 't', 'p', 'u', 't', '-', 'p', 'r',
+ 'o', 't', 'e', 'c', 't', 'i', 'o', 'n'};
+ KeyInformation keyInfo = {};
+ keyInfo.key_id = kKeyId;
+ keyInfo.key_id_size = std::size(kKeyId);
+ keyInfo.status = aStatus;
+
+ vector<KeyInformation> keyInfos;
+ keyInfos.push_back(keyInfo);
+
+ // At time of writing, Gecko's higher level handling doesn't use this arg.
+ // However, we set it to false to mimic Chromium's similar case. Since
+ // Clearkey is used to test the Chromium CDM path, it doesn't hurt to try
+ // and mimic their behaviour.
+ bool hasAdditionalUseableKey = false;
+ mHost->OnSessionKeysChange(lastSessionId.c_str(), lastSessionId.size(),
+ hasAdditionalUseableKey, keyInfos.data(),
+ keyInfos.size());
+}
diff --git a/dom/media/eme/clearkey/ClearKeySessionManager.h b/dom/media/eme/clearkey/ClearKeySessionManager.h
new file mode 100644
index 0000000000..3a930f666d
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeySessionManager.h
@@ -0,0 +1,138 @@
+/*
+ * 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 __ClearKeyDecryptor_h__
+#define __ClearKeyDecryptor_h__
+
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <functional>
+#include <map>
+#include <optional>
+#include <queue>
+#include <set>
+#include <string>
+
+#include "content_decryption_module.h"
+
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeyPersistence.h"
+#include "ClearKeySession.h"
+#include "ClearKeyUtils.h"
+#include "RefCounted.h"
+#include "mozilla/TimeStamp.h"
+
+class ClearKeySessionManager final : public RefCounted {
+ public:
+ explicit ClearKeySessionManager(cdm::Host_10* aHost);
+
+ void Init(bool aDistinctiveIdentifierAllowed, bool aPersistentStateAllowed);
+
+ void CreateSession(uint32_t aPromiseId, cdm::InitDataType aInitDataType,
+ const uint8_t* aInitData, uint32_t aInitDataSize,
+ cdm::SessionType aSessionType);
+
+ void LoadSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdLength);
+
+ void UpdateSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdLength, const uint8_t* aResponse,
+ uint32_t aResponseSize);
+
+ void CloseSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdLength);
+
+ void RemoveSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdLength);
+
+ void SetServerCertificate(uint32_t aPromiseId, const uint8_t* aServerCert,
+ uint32_t aServerCertSize);
+
+ cdm::Status Decrypt(const cdm::InputBuffer_2& aBuffer,
+ cdm::DecryptedBlock* aDecryptedBlock);
+
+ void DecryptingComplete();
+
+ void PersistentSessionDataLoaded(uint32_t aPromiseId,
+ const std::string& aSessionId,
+ const uint8_t* aKeyData,
+ uint32_t aKeyDataSize);
+
+ // Receives the result of an output protection query from the user agent.
+ // This may trigger a key status change.
+ // @param aResult indicates if the query succeeded or not. If a query did
+ // not succeed then that other arguments are ignored.
+ // @param aLinkMask is used to indicate if output could be captured by the
+ // user agent. It should be set to `kLinkTypeNetwork` if capture is possible,
+ // otherwise it should be zero.
+ // @param aOutputProtectionMask this argument is unused.
+ void OnQueryOutputProtectionStatus(cdm::QueryResult aResult,
+ uint32_t aLinkMask,
+ uint32_t aOutputProtectionMask);
+
+ // Prompts the session manager to query the output protection status if we
+ // haven't yet, or if enough time has passed since the last check. Will also
+ // notify if a check has not been responded to on time.
+ void QueryOutputProtectionStatusIfNeeded();
+
+ private:
+ ~ClearKeySessionManager();
+
+ void ClearInMemorySessionData(ClearKeySession* aSession);
+ bool MaybeDeferTillInitialized(std::function<void()>&& aMaybeDefer);
+ void Serialize(const ClearKeySession* aSession,
+ std::vector<uint8_t>& aOutKeyData);
+
+ // Signals the host to perform an output protection check.
+ void QueryOutputProtectionStatusFromHost();
+
+ // Called to notify the result of an output protection status call. The
+ // following arguments are expected, along with their intended use:
+ // - KeyStatus::kUsable indicates that the query was responded to and the
+ // response showed output is protected.
+ // - KeyStatus::kOutputRestricted indicates that the query was responded to
+ // and the response showed output is not protected.
+ // - KeyStatus::kInternalError indicates a query was not repsonded to on
+ // time, or that a query was responded to with a failed cdm::QueryResult.
+ // The status passed to this function will be used to update the status of
+ // the keyId "output-protection", which tests an observe.
+ void NotifyOutputProtectionStatus(cdm::KeyStatus aStatus);
+
+ RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
+ RefPtr<ClearKeyPersistence> mPersistence;
+
+ cdm::Host_10* mHost = nullptr;
+
+ std::set<KeyId> mKeyIds;
+ std::map<std::string, ClearKeySession*> mSessions;
+
+ // The session id of the last session created or loaded from persistent
+ // storage. Used to fire test messages at that session.
+ std::optional<std::string> mLastSessionId;
+
+ std::queue<std::function<void()>> mDeferredInitialize;
+
+ // If there is an inflight query to the host to check the output protection
+ // status. Multiple in flight queries should not be allowed, avoid firing
+ // more if this is true.
+ bool mHasOutstandingOutputProtectionQuery = false;
+ // The last time the manager called QueryOutputProtectionStatus on the host.
+ mozilla::TimeStamp mLastOutputProtectionQueryTime;
+};
+
+#endif // __ClearKeyDecryptor_h__
diff --git a/dom/media/eme/clearkey/ClearKeyStorage.cpp b/dom/media/eme/clearkey/ClearKeyStorage.cpp
new file mode 100644
index 0000000000..1375d33c57
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeyStorage.cpp
@@ -0,0 +1,194 @@
+/*
+ * 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 "ClearKeyStorage.h"
+
+#include <assert.h>
+// This include is required in order for content_decryption_module to work
+// on Unix systems.
+#include <stddef.h>
+
+#include <vector>
+
+#include "content_decryption_module.h"
+
+#include "ArrayUtils.h"
+#include "ClearKeyUtils.h"
+
+using namespace cdm;
+
+using std::function;
+using std::string;
+using std::vector;
+
+class WriteRecordClient : public FileIOClient {
+ public:
+ /*
+ * This function will take the memory ownership of the parameters and
+ * delete them when done.
+ */
+ static void Write(Host_10* aHost, string& aRecordName,
+ const vector<uint8_t>& aData, function<void()>&& aOnSuccess,
+ function<void()>&& aOnFailure) {
+ WriteRecordClient* client = new WriteRecordClient(
+ aData, std::move(aOnSuccess), std::move(aOnFailure));
+ client->Do(aRecordName, aHost);
+ }
+
+ void OnOpenComplete(Status aStatus) override {
+ // If we hit an error, fail.
+ if (aStatus != Status::kSuccess) {
+ Done(aStatus);
+ } else if (mFileIO) { // Otherwise, write our data to the file.
+ mFileIO->Write(&mData[0], mData.size());
+ }
+ }
+
+ void OnReadComplete(Status aStatus, const uint8_t* aData,
+ uint32_t aDataSize) override {
+ // This function should never be called, we only ever write data with this
+ // client.
+ assert(false);
+ }
+
+ void OnWriteComplete(Status aStatus) override { Done(aStatus); }
+
+ private:
+ explicit WriteRecordClient(const vector<uint8_t>& aData,
+ function<void()>&& aOnSuccess,
+ function<void()>&& aOnFailure)
+ : mFileIO(nullptr),
+ mOnSuccess(std::move(aOnSuccess)),
+ mOnFailure(std::move(aOnFailure)),
+ mData(aData) {}
+
+ void Do(const string& aName, Host_10* aHost) {
+ // Initialize the FileIO.
+ mFileIO = aHost->CreateFileIO(this);
+ mFileIO->Open(aName.c_str(), aName.size());
+ }
+
+ void Done(cdm::FileIOClient::Status aStatus) {
+ // Note: Call Close() before running continuation, in case the
+ // continuation tries to open the same record; if we call Close()
+ // after running the continuation, the Close() call will arrive
+ // just after the Open() call succeeds, immediately closing the
+ // record we just opened.
+ if (mFileIO) {
+ mFileIO->Close();
+ }
+
+ if (IO_SUCCEEDED(aStatus)) {
+ mOnSuccess();
+ } else {
+ mOnFailure();
+ }
+
+ delete this;
+ }
+
+ FileIO* mFileIO = nullptr;
+
+ function<void()> mOnSuccess;
+ function<void()> mOnFailure;
+
+ const vector<uint8_t> mData;
+};
+
+void WriteData(Host_10* aHost, string& aRecordName,
+ const vector<uint8_t>& aData, function<void()>&& aOnSuccess,
+ function<void()>&& aOnFailure) {
+ WriteRecordClient::Write(aHost, aRecordName, aData, std::move(aOnSuccess),
+ std::move(aOnFailure));
+}
+
+class ReadRecordClient : public FileIOClient {
+ public:
+ /*
+ * This function will take the memory ownership of the parameters and
+ * delete them when done.
+ */
+ static void Read(Host_10* aHost, string& aRecordName,
+ function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
+ function<void()>&& aOnFailure) {
+ (new ReadRecordClient(std::move(aOnSuccess), std::move(aOnFailure)))
+ ->Do(aRecordName, aHost);
+ }
+
+ void OnOpenComplete(Status aStatus) override {
+ auto err = aStatus;
+ if (aStatus != Status::kSuccess) {
+ Done(err, nullptr, 0);
+ } else {
+ mFileIO->Read();
+ }
+ }
+
+ void OnReadComplete(Status aStatus, const uint8_t* aData,
+ uint32_t aDataSize) override {
+ Done(aStatus, aData, aDataSize);
+ }
+
+ void OnWriteComplete(Status aStatus) override {
+ // We should never reach here, this client only ever reads data.
+ assert(false);
+ }
+
+ private:
+ explicit ReadRecordClient(
+ function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
+ function<void()>&& aOnFailure)
+ : mFileIO(nullptr),
+ mOnSuccess(std::move(aOnSuccess)),
+ mOnFailure(std::move(aOnFailure)) {}
+
+ void Do(const string& aName, Host_10* aHost) {
+ mFileIO = aHost->CreateFileIO(this);
+ mFileIO->Open(aName.c_str(), aName.size());
+ }
+
+ void Done(cdm::FileIOClient::Status aStatus, const uint8_t* aData,
+ uint32_t aDataSize) {
+ // Note: Call Close() before running continuation, in case the
+ // continuation tries to open the same record; if we call Close()
+ // after running the continuation, the Close() call will arrive
+ // just after the Open() call succeeds, immediately closing the
+ // record we just opened.
+ if (mFileIO) {
+ mFileIO->Close();
+ }
+
+ if (IO_SUCCEEDED(aStatus)) {
+ mOnSuccess(aData, aDataSize);
+ } else {
+ mOnFailure();
+ }
+
+ delete this;
+ }
+
+ FileIO* mFileIO = nullptr;
+
+ function<void(const uint8_t*, uint32_t)> mOnSuccess;
+ function<void()> mOnFailure;
+};
+
+void ReadData(Host_10* aHost, string& aRecordName,
+ function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
+ function<void()>&& aOnFailure) {
+ ReadRecordClient::Read(aHost, aRecordName, std::move(aOnSuccess),
+ std::move(aOnFailure));
+}
diff --git a/dom/media/eme/clearkey/ClearKeyStorage.h b/dom/media/eme/clearkey/ClearKeyStorage.h
new file mode 100644
index 0000000000..bd8e99b901
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeyStorage.h
@@ -0,0 +1,42 @@
+/*
+ * 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 __ClearKeyStorage_h__
+#define __ClearKeyStorage_h__
+
+#include <stdint.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "ClearKeySessionManager.h"
+
+#define IO_SUCCEEDED(x) ((x) == cdm::FileIOClient::Status::kSuccess)
+#define IO_FAILED(x) ((x) != cdm::FileIOClient::Status::kSuccess)
+
+// Writes data to a file and fires the appropriate callback when complete.
+void WriteData(cdm::Host_10* aHost, std::string& aRecordName,
+ const std::vector<uint8_t>& aData,
+ std::function<void()>&& aOnSuccess,
+ std::function<void()>&& aOnFailure);
+
+// Reads data from a file and fires the appropriate callback when complete.
+void ReadData(cdm::Host_10* aHost, std::string& aRecordName,
+ std::function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
+ std::function<void()>&& aOnFailure);
+
+#endif // __ClearKeyStorage_h__
diff --git a/dom/media/eme/clearkey/ClearKeyUtils.cpp b/dom/media/eme/clearkey/ClearKeyUtils.cpp
new file mode 100644
index 0000000000..3f9588bdf9
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeyUtils.cpp
@@ -0,0 +1,661 @@
+/*
+ * 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 "ClearKeyUtils.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <memory.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <cctype>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "pk11pub.h"
+#include "prerror.h"
+#include "secmodt.h"
+
+#include "ArrayUtils.h"
+#include "BigEndian.h"
+#include "ClearKeyBase64.h"
+#include "mozilla/Sprintf.h"
+#include "psshparser/PsshParser.h"
+
+using namespace cdm;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+struct DeleteHelper {
+ void operator()(PK11Context* value) { PK11_DestroyContext(value, true); }
+ void operator()(PK11SlotInfo* value) { PK11_FreeSlot(value); }
+ void operator()(PK11SymKey* value) { PK11_FreeSymKey(value); }
+};
+
+template <class T>
+struct MaybeDeleteHelper {
+ void operator()(T* ptr) {
+ if (ptr) {
+ DeleteHelper del;
+ del(ptr);
+ }
+ }
+};
+
+void CK_Log(const char* aFmt, ...) {
+ FILE* out = stdout;
+
+ if (getenv("CLEARKEY_LOG_FILE")) {
+ out = fopen(getenv("CLEARKEY_LOG_FILE"), "a");
+ }
+
+ va_list ap;
+
+ va_start(ap, aFmt);
+ const size_t len = 1024;
+ char buf[len];
+ VsprintfLiteral(buf, aFmt, ap);
+ va_end(ap);
+
+ fprintf(out, "%s\n", buf);
+ fflush(out);
+
+ if (out != stdout) {
+ fclose(out);
+ }
+}
+
+static bool PrintableAsString(const uint8_t* aBytes, uint32_t aLength) {
+ return std::all_of(aBytes, aBytes + aLength,
+ [](uint8_t c) { return isprint(c) == 1; });
+}
+
+void CK_LogArray(const char* prepend, const uint8_t* aData,
+ const uint32_t aDataSize) {
+ // If the data is valid ascii, use that. Otherwise print the hex
+ string data = PrintableAsString(aData, aDataSize)
+ ? string(aData, aData + aDataSize)
+ : ClearKeyUtils::ToHexString(aData, aDataSize);
+
+ CK_LOGD("%s%s", prepend, data.c_str());
+}
+
+/* static */
+bool ClearKeyUtils::DecryptCbcs(const vector<uint8_t>& aKey,
+ const vector<uint8_t>& aIV,
+ mozilla::Span<uint8_t> aSubsample,
+ uint32_t aCryptByteBlock,
+ uint32_t aSkipByteBlock) {
+ if (aKey.size() != CENC_KEY_LEN || aIV.size() != CENC_KEY_LEN) {
+ CK_LOGE("Key and IV size should be 16!");
+ return false;
+ }
+
+ std::unique_ptr<PK11SlotInfo, MaybeDeleteHelper<PK11SlotInfo>> slot(
+ PK11_GetInternalKeySlot());
+
+ if (!slot.get()) {
+ CK_LOGE("Failed to get internal PK11 slot");
+ return false;
+ }
+
+ SECItem keyItem = {siBuffer, (unsigned char*)&aKey[0], CENC_KEY_LEN};
+ SECItem ivItem = {siBuffer, (unsigned char*)&aIV[0], CENC_KEY_LEN};
+
+ std::unique_ptr<PK11SymKey, MaybeDeleteHelper<PK11SymKey>> key(
+ PK11_ImportSymKey(slot.get(), CKM_AES_CBC, PK11_OriginUnwrap, CKA_DECRYPT,
+ &keyItem, nullptr));
+
+ if (!key.get()) {
+ CK_LOGE("Failed to import sym key");
+ return false;
+ }
+
+ std::unique_ptr<PK11Context, MaybeDeleteHelper<PK11Context>> ctx(
+ PK11_CreateContextBySymKey(CKM_AES_CBC, CKA_DECRYPT, key.get(), &ivItem));
+
+ if (!ctx) {
+ CK_LOGE("Failed to get PK11Context!");
+ return false;
+ }
+
+ assert(aCryptByteBlock <= 0xFF);
+ assert(aSkipByteBlock <= 0xFF);
+
+ uint8_t* encryptedSubsample = &aSubsample[0];
+ const uint32_t BLOCK_SIZE = 16;
+ const uint32_t skipBytes = aSkipByteBlock * BLOCK_SIZE;
+ const uint32_t totalBlocks = aSubsample.Length() / BLOCK_SIZE;
+ uint32_t blocksProcessed = 0;
+
+ if (aSkipByteBlock == 0) {
+ // ISO/IEC 23001 - 7 Section 9.6.1
+ // 'When the fields default_crypt_byte_block and default_skip_byte_block in
+ // a version 1 Track Encryption Box('tenc') are non - zero numbers, pattern
+ // encryption SHALL be applied.'
+ // So if both are 0, then everything is encrypted. Similarly, if skip is 0
+ // and crypt is non-0, everything is encrypted.
+ // In this case we can just decrypt all the blocks in one call. This is the
+ // same outcome as decrypting them using smaller steps, as either way the
+ // CBC result should be the same.
+ MOZ_ASSERT(skipBytes == 0);
+ aCryptByteBlock = totalBlocks;
+ }
+
+ while (blocksProcessed < totalBlocks) {
+ uint32_t blocksToDecrypt = aCryptByteBlock <= totalBlocks - blocksProcessed
+ ? aCryptByteBlock
+ : totalBlocks - blocksProcessed;
+ uint32_t bytesToDecrypt = blocksToDecrypt * BLOCK_SIZE;
+ int outLen;
+ SECStatus rv;
+ rv = PK11_CipherOp(ctx.get(), encryptedSubsample, &outLen, bytesToDecrypt,
+ encryptedSubsample, bytesToDecrypt);
+ if (rv != SECSuccess) {
+ CK_LOGE("PK11_CipherOp() failed");
+ return false;
+ }
+
+ encryptedSubsample += skipBytes + bytesToDecrypt;
+ blocksProcessed += aSkipByteBlock + blocksToDecrypt;
+ }
+
+ return true;
+}
+
+/* static */
+bool ClearKeyUtils::DecryptAES(const vector<uint8_t>& aKey,
+ vector<uint8_t>& aData, vector<uint8_t>& aIV) {
+ assert(aIV.size() == CENC_KEY_LEN);
+ assert(aKey.size() == CENC_KEY_LEN);
+
+ PK11SlotInfo* slot = PK11_GetInternalKeySlot();
+ if (!slot) {
+ CK_LOGE("Failed to get internal PK11 slot");
+ return false;
+ }
+
+ SECItem keyItem = {siBuffer, (unsigned char*)&aKey[0], CENC_KEY_LEN};
+ PK11SymKey* key = PK11_ImportSymKey(slot, CKM_AES_CTR, PK11_OriginUnwrap,
+ CKA_ENCRYPT, &keyItem, nullptr);
+ PK11_FreeSlot(slot);
+ if (!key) {
+ CK_LOGE("Failed to import sym key");
+ return false;
+ }
+
+ CK_AES_CTR_PARAMS params;
+ params.ulCounterBits = 32;
+ memcpy(&params.cb, &aIV[0], CENC_KEY_LEN);
+ SECItem paramItem = {siBuffer, (unsigned char*)&params,
+ sizeof(CK_AES_CTR_PARAMS)};
+
+ unsigned int outLen = 0;
+ auto rv = PK11_Decrypt(key, CKM_AES_CTR, &paramItem, &aData[0], &outLen,
+ aData.size(), &aData[0], aData.size());
+
+ aData.resize(outLen);
+ PK11_FreeSymKey(key);
+
+ if (rv != SECSuccess) {
+ CK_LOGE("PK11_Decrypt() failed");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * ClearKey expects all Key IDs to be base64 encoded with non-standard alphabet
+ * and padding.
+ */
+static bool EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded) {
+ const char sAlphabet[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+ const uint8_t sMask = 0x3f;
+
+ aEncoded.resize((aBinary.size() * 8 + 5) / 6);
+
+ // Pad binary data in case there's rubbish past the last byte.
+ aBinary.push_back(0);
+
+ // Number of bytes not consumed in the previous character
+ uint32_t shift = 0;
+
+ auto out = aEncoded.begin();
+ auto data = aBinary.begin();
+ for (string::size_type i = 0; i < aEncoded.length(); i++) {
+ if (shift) {
+ out[i] = (*data << (6 - shift)) & sMask;
+ data++;
+ } else {
+ out[i] = 0;
+ }
+
+ out[i] += (*data >> (shift + 2)) & sMask;
+ shift = (shift + 2) % 8;
+
+ // Cast idx to size_t before using it as an array-index,
+ // to pacify clang 'Wchar-subscripts' warning:
+ size_t idx = static_cast<size_t>(out[i]);
+
+ // out of bounds index for 'sAlphabet'
+ assert(idx < MOZ_ARRAY_LENGTH(sAlphabet));
+ out[i] = sAlphabet[idx];
+ }
+
+ return true;
+}
+
+/* static */
+void ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
+ string& aOutRequest,
+ SessionType aSessionType) {
+ assert(!aKeyIDs.empty() && aOutRequest.empty());
+
+ aOutRequest.append("{\"kids\":[");
+ for (size_t i = 0; i < aKeyIDs.size(); i++) {
+ if (i) {
+ aOutRequest.append(",");
+ }
+ aOutRequest.append("\"");
+
+ string base64key;
+ EncodeBase64Web(aKeyIDs[i], base64key);
+ aOutRequest.append(base64key);
+
+ aOutRequest.append("\"");
+ }
+ aOutRequest.append("],\"type\":");
+
+ aOutRequest.append("\"");
+ aOutRequest.append(SessionTypeToString(aSessionType));
+ aOutRequest.append("\"}");
+}
+
+#define EXPECT_SYMBOL(CTX, X) \
+ do { \
+ if (GetNextSymbol(CTX) != (X)) { \
+ CK_LOGE("Unexpected symbol in JWK parser"); \
+ return false; \
+ } \
+ } while (false)
+
+struct ParserContext {
+ const uint8_t* mIter;
+ const uint8_t* mEnd;
+};
+
+static uint8_t PeekSymbol(ParserContext& aCtx) {
+ for (; aCtx.mIter < aCtx.mEnd; (aCtx.mIter)++) {
+ if (!isspace(*aCtx.mIter)) {
+ return *aCtx.mIter;
+ }
+ }
+
+ return 0;
+}
+
+static uint8_t GetNextSymbol(ParserContext& aCtx) {
+ uint8_t sym = PeekSymbol(aCtx);
+ aCtx.mIter++;
+ return sym;
+}
+
+static bool SkipToken(ParserContext& aCtx);
+
+static bool SkipString(ParserContext& aCtx) {
+ EXPECT_SYMBOL(aCtx, '"');
+ for (uint8_t sym = GetNextSymbol(aCtx); sym; sym = GetNextSymbol(aCtx)) {
+ if (sym == '\\') {
+ sym = GetNextSymbol(aCtx);
+ if (!sym) {
+ return false;
+ }
+ } else if (sym == '"') {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Skip whole object and values it contains.
+ */
+static bool SkipObject(ParserContext& aCtx) {
+ EXPECT_SYMBOL(aCtx, '{');
+
+ if (PeekSymbol(aCtx) == '}') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+
+ while (true) {
+ if (!SkipString(aCtx)) return false;
+ EXPECT_SYMBOL(aCtx, ':');
+ if (!SkipToken(aCtx)) return false;
+
+ if (PeekSymbol(aCtx) == '}') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+}
+
+/**
+ * Skip array value and the values it contains.
+ */
+static bool SkipArray(ParserContext& aCtx) {
+ EXPECT_SYMBOL(aCtx, '[');
+
+ if (PeekSymbol(aCtx) == ']') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+
+ while (SkipToken(aCtx)) {
+ if (PeekSymbol(aCtx) == ']') {
+ GetNextSymbol(aCtx);
+ return true;
+ }
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return false;
+}
+
+/**
+ * Skip unquoted literals like numbers, |true|, and |null|.
+ * (XXX and anything else that matches /([:alnum:]|[+-.])+/)
+ */
+static bool SkipLiteral(ParserContext& aCtx) {
+ for (; aCtx.mIter < aCtx.mEnd; aCtx.mIter++) {
+ if (!isalnum(*aCtx.mIter) && *aCtx.mIter != '.' && *aCtx.mIter != '-' &&
+ *aCtx.mIter != '+') {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool SkipToken(ParserContext& aCtx) {
+ uint8_t startSym = PeekSymbol(aCtx);
+ if (startSym == '"') {
+ CK_LOGD("JWK parser skipping string");
+ return SkipString(aCtx);
+ } else if (startSym == '{') {
+ CK_LOGD("JWK parser skipping object");
+ return SkipObject(aCtx);
+ } else if (startSym == '[') {
+ CK_LOGD("JWK parser skipping array");
+ return SkipArray(aCtx);
+ } else {
+ CK_LOGD("JWK parser skipping literal");
+ return SkipLiteral(aCtx);
+ }
+}
+
+static bool GetNextLabel(ParserContext& aCtx, string& aOutLabel) {
+ EXPECT_SYMBOL(aCtx, '"');
+
+ const uint8_t* start = aCtx.mIter;
+ for (uint8_t sym = GetNextSymbol(aCtx); sym; sym = GetNextSymbol(aCtx)) {
+ if (sym == '\\') {
+ GetNextSymbol(aCtx);
+ continue;
+ }
+
+ if (sym == '"') {
+ aOutLabel.assign(start, aCtx.mIter - 1);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool ParseKeyObject(ParserContext& aCtx, KeyIdPair& aOutKey) {
+ EXPECT_SYMBOL(aCtx, '{');
+
+ // Reject empty objects as invalid licenses.
+ if (PeekSymbol(aCtx) == '}') {
+ GetNextSymbol(aCtx);
+ return false;
+ }
+
+ string keyId;
+ string key;
+
+ while (true) {
+ string label;
+ string value;
+
+ if (!GetNextLabel(aCtx, label)) {
+ return false;
+ }
+
+ EXPECT_SYMBOL(aCtx, ':');
+ if (label == "kty") {
+ if (!GetNextLabel(aCtx, value)) return false;
+ // By spec, type must be "oct".
+ if (value != "oct") return false;
+ } else if (label == "k" && PeekSymbol(aCtx) == '"') {
+ // if this isn't a string we will fall through to the SkipToken() path.
+ if (!GetNextLabel(aCtx, key)) return false;
+ } else if (label == "kid" && PeekSymbol(aCtx) == '"') {
+ if (!GetNextLabel(aCtx, keyId)) return false;
+ } else {
+ if (!SkipToken(aCtx)) return false;
+ }
+
+ uint8_t sym = PeekSymbol(aCtx);
+ if (!sym || sym == '}') {
+ break;
+ }
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return !key.empty() && !keyId.empty() &&
+ 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, '[');
+
+ while (true) {
+ KeyIdPair key;
+ if (!ParseKeyObject(aCtx, key)) {
+ CK_LOGE("Failed to parse key object");
+ return false;
+ }
+
+ assert(!key.mKey.empty() && !key.mKeyId.empty());
+ aOutKeys.push_back(key);
+
+ uint8_t sym = PeekSymbol(aCtx);
+ if (!sym || sym == ']') {
+ break;
+ }
+
+ EXPECT_SYMBOL(aCtx, ',');
+ }
+
+ return GetNextSymbol(aCtx) == ']';
+}
+
+/* static */
+bool ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
+ vector<KeyIdPair>& aOutKeys,
+ SessionType aSessionType) {
+ ParserContext ctx;
+ ctx.mIter = aKeyData;
+ ctx.mEnd = aKeyData + aKeyDataSize;
+
+ // Consume '{' from start of object.
+ EXPECT_SYMBOL(ctx, '{');
+
+ while (true) {
+ string label;
+ // Consume member key.
+ if (!GetNextLabel(ctx, label)) return false;
+ EXPECT_SYMBOL(ctx, ':');
+
+ if (label == "keys") {
+ // Parse "keys" array.
+ if (!ParseKeys(ctx, aOutKeys)) return false;
+ } else if (label == "type") {
+ // Consume type string.
+ string type;
+ if (!GetNextLabel(ctx, type)) return false;
+ if (type != SessionTypeToString(aSessionType)) {
+ 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 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) || !DecodeBase64(label, keyId)) {
+ return false;
+ }
+ if (!keyId.empty() && keyId.size() <= kMaxKeyIdsLength) {
+ 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) {
+ 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) || aOutKeyIds.empty()) {
+ 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(
+ SessionType aSessionType) {
+ switch (aSessionType) {
+ case SessionType::kTemporary:
+ return "temporary";
+ case SessionType::kPersistentLicense:
+ return "persistent-license";
+ default: {
+ // We don't support any other license types.
+ assert(false);
+ return "invalid";
+ }
+ }
+}
+
+/* static */
+bool ClearKeyUtils::IsValidSessionId(const char* aBuff, uint32_t aLength) {
+ if (aLength > 10) {
+ // 10 is the max number of characters in UINT32_MAX when
+ // represented as a string; ClearKey session ids are integers.
+ return false;
+ }
+ for (uint32_t i = 0; i < aLength; i++) {
+ if (!isdigit(aBuff[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+string ClearKeyUtils::ToHexString(const uint8_t* aBytes, uint32_t aLength) {
+ stringstream ss;
+ ss << std::showbase << std::uppercase << std::hex;
+ for (uint32_t i = 0; i < aLength; ++i) {
+ ss << std::hex << static_cast<uint32_t>(aBytes[i]);
+ ss << " ";
+ }
+
+ return ss.str();
+}
diff --git a/dom/media/eme/clearkey/ClearKeyUtils.h b/dom/media/eme/clearkey/ClearKeyUtils.h
new file mode 100644
index 0000000000..04c227c71d
--- /dev/null
+++ b/dom/media/eme/clearkey/ClearKeyUtils.h
@@ -0,0 +1,107 @@
+/*
+ * 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 __ClearKeyUtils_h__
+#define __ClearKeyUtils_h__
+
+#include <assert.h>
+// stdef.h is required for content_decryption_module to work on Unix systems.
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "content_decryption_module.h"
+#include "pk11pub.h"
+
+#include "mozilla/Span.h"
+
+#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__)
+# define CK_LOGARRAY(APREPEND, ADATA, ADATA_SIZE) \
+ CK_LogArray(APREPEND, ADATA, ADATA_SIZE)
+#else
+// Note: Enabling logging slows things down a LOT, especially when logging to
+// a file.
+# define CK_LOGE(...)
+# define CK_LOGD(...)
+# define CK_LOGW(...)
+# define CK_LOGARRAY(APREPEND, ADATA, ADATA_SIZE)
+#endif
+
+typedef std::vector<uint8_t> KeyId;
+typedef std::vector<uint8_t> Key;
+
+// The session response size should be within a reasonable limit.
+// The size 64 KB is referenced from web-platform-test.
+static const uint32_t kMaxSessionResponseLength = 65536;
+
+// Provide limitation for KeyIds length and webm initData size.
+static const uint32_t kMaxWebmInitDataSize = 65536;
+static const uint32_t kMaxKeyIdsLength = 512;
+
+void CK_LogArray(const char* aPrepend, const uint8_t* aData,
+ const uint32_t aDataSize);
+
+struct KeyIdPair {
+ KeyId mKeyId;
+ Key mKey;
+};
+
+class ClearKeyUtils {
+ public:
+ static bool DecryptCbcs(const std::vector<uint8_t>& aKey,
+ const std::vector<uint8_t>& aIV,
+ mozilla::Span<uint8_t> aSubsampleEncryptedRange,
+ uint32_t aCryptByteBlocks, uint32_t aSkipByteBlocks);
+
+ static bool DecryptAES(const std::vector<uint8_t>& aKey,
+ std::vector<uint8_t>& aData,
+ std::vector<uint8_t>& aIV);
+
+ static bool ParseKeyIdsInitData(const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ std::vector<KeyId>& aOutKeyIds);
+
+ static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
+ std::string& aOutRequest,
+ cdm::SessionType aSessionType);
+
+ static bool ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
+ std::vector<KeyIdPair>& aOutKeys,
+ cdm::SessionType aSessionType);
+ static const char* SessionTypeToString(cdm::SessionType aSessionType);
+
+ static bool IsValidSessionId(const char* aBuff, uint32_t aLength);
+
+ static std::string ToHexString(const uint8_t* aBytes, uint32_t aLength);
+};
+
+template <class Container, class Element>
+inline bool Contains(const Container& aContainer, const Element& aElement) {
+ return aContainer.find(aElement) != aContainer.end();
+}
+
+template <typename T>
+inline void Assign(std::vector<T>& aVec, const T* aData, size_t aLength) {
+ aVec.assign(aData, aData + aLength);
+}
+
+#endif // __ClearKeyUtils_h__
diff --git a/dom/media/eme/clearkey/RefCounted.h b/dom/media/eme/clearkey/RefCounted.h
new file mode 100644
index 0000000000..25f308164c
--- /dev/null
+++ b/dom/media/eme/clearkey/RefCounted.h
@@ -0,0 +1,85 @@
+/*
+ * 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 __RefCount_h__
+#define __RefCount_h__
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <atomic>
+
+#include "ClearKeyUtils.h"
+
+// Note: Thread safe.
+class RefCounted {
+ public:
+ void AddRef() { ++mRefCount; }
+
+ uint32_t Release() {
+ uint32_t newCount = --mRefCount;
+ if (!newCount) {
+ delete this;
+ }
+ return newCount;
+ }
+
+ protected:
+ RefCounted() : mRefCount(0) {}
+ virtual ~RefCounted() { assert(!mRefCount); }
+ std::atomic<uint32_t> mRefCount;
+};
+
+template <class T>
+class RefPtr {
+ public:
+ RefPtr(const RefPtr& src) { Set(src.mPtr); }
+
+ explicit RefPtr(T* aPtr) { Set(aPtr); }
+ RefPtr() { Set(nullptr); }
+
+ ~RefPtr() { Set(nullptr); }
+ T* operator->() const { return mPtr; }
+ T** operator&() { return &mPtr; }
+ T* operator->() { return mPtr; }
+ operator T*() { return mPtr; }
+
+ T* Get() const { return mPtr; }
+
+ RefPtr& operator=(T* aVal) {
+ Set(aVal);
+ return *this;
+ }
+
+ private:
+ T* Set(T* aPtr) {
+ if (mPtr == aPtr) {
+ return aPtr;
+ }
+ if (mPtr) {
+ mPtr->Release();
+ }
+ mPtr = aPtr;
+ if (mPtr) {
+ aPtr->AddRef();
+ }
+ return mPtr;
+ }
+
+ T* mPtr = nullptr;
+};
+
+#endif // __RefCount_h__
diff --git a/dom/media/eme/clearkey/gtest/TestClearKeyUtils.cpp b/dom/media/eme/clearkey/gtest/TestClearKeyUtils.cpp
new file mode 100644
index 0000000000..bff6d0f356
--- /dev/null
+++ b/dom/media/eme/clearkey/gtest/TestClearKeyUtils.cpp
@@ -0,0 +1,81 @@
+/* -*- 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 "../ClearKeyBase64.cpp"
+
+using namespace std;
+
+struct B64Test {
+ string b64;
+ vector<uint8_t> raw;
+ bool shouldPass;
+};
+
+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},
+ true},
+ {"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,
+ },
+ {"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
+ {"A", {}, false}, // 1 character is too few.
+ {"_", {}, false}, // 1 character is too few.
+};
+
+TEST(ClearKey, DecodeBase64)
+{
+ for (const B64Test& test : tests) {
+ vector<uint8_t> v;
+ bool rv = DecodeBase64(string(test.b64), v);
+ EXPECT_EQ(test.shouldPass, rv);
+ if (test.shouldPass) {
+ EXPECT_EQ(test.raw.size(), v.size());
+ for (size_t k = 0; k < test.raw.size(); k++) {
+ EXPECT_EQ(test.raw[k], v[k]);
+ }
+ }
+ }
+}
diff --git a/dom/media/eme/clearkey/gtest/moz.build b/dom/media/eme/clearkey/gtest/moz.build
new file mode 100644
index 0000000000..5771c4724c
--- /dev/null
+++ b/dom/media/eme/clearkey/gtest/moz.build
@@ -0,0 +1,15 @@
+# -*- 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 += [
+ "TestClearKeyUtils.cpp",
+]
+
+FINAL_LIBRARY = "xul-gtest"
+
+LOCAL_INCLUDES += [
+ "..",
+]
diff --git a/dom/media/eme/clearkey/moz.build b/dom/media/eme/clearkey/moz.build
new file mode 100644
index 0000000000..57160624ea
--- /dev/null
+++ b/dom/media/eme/clearkey/moz.build
@@ -0,0 +1,37 @@
+# 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/.
+
+EXPORTS += [
+ "ArrayUtils.h",
+ "BigEndian.h",
+ "ClearKeyBase64.h",
+ "ClearKeyDecryptionManager.h",
+ "ClearKeyPersistence.h",
+ "ClearKeySession.h",
+ "ClearKeySessionManager.h",
+ "ClearKeyStorage.h",
+ "ClearKeyUtils.h",
+ "RefCounted.h",
+]
+
+UNIFIED_SOURCES += [
+ "ClearKeyBase64.cpp",
+ "ClearKeyDecryptionManager.cpp",
+ "ClearKeyPersistence.cpp",
+ "ClearKeySession.cpp",
+ "ClearKeySessionManager.cpp",
+ "ClearKeyStorage.cpp",
+ "ClearKeyUtils.cpp",
+]
+
+TEST_DIRS += [
+ "gtest",
+]
+
+USE_LIBS += [
+ "nss",
+ "psshparser",
+]
+
+Library("gecko-clearkey")