Bug 1516673 - Adds CBCS encryption scheme functionality. r=bryce
authorJason <burtonljas@gmail.com>
Thu, 26 Mar 2020 20:53:59 +0000
changeset 520640 2e1f08362eb0a47c3f6ffc90d73e0edcba91cfa0
parent 520639 de94a3c40ad2e87d947aff7ef836f7f8ea444492
child 520641 ac87de0a73e9f3395e50a8d5d18780178debb1c9
push id37254
push usernerli@mozilla.com
push dateFri, 27 Mar 2020 04:48:07 +0000
treeherdermozilla-central@2d758b42bd73 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbryce
bugs1516673
milestone76.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1516673 - Adds CBCS encryption scheme functionality. r=bryce Differential Revision: https://phabricator.services.mozilla.com/D60053
media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
media/gmp-clearkey/0.1/ClearKeyDecryptionManager.h
media/gmp-clearkey/0.1/ClearKeyUtils.cpp
media/gmp-clearkey/0.1/ClearKeyUtils.h
--- a/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
@@ -19,16 +19,17 @@
 #include "psshparser/PsshParser.h"
 
 #include <assert.h>
 #include <string.h>
 #include <vector>
 #include <algorithm>
 
 #include "mozilla/CheckedInt.h"
+#include "mozilla/Span.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; });
 }
 
@@ -164,22 +165,72 @@ ClearKeyDecryptor::~ClearKeyDecryptor() 
 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::MakeSpan(
+          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::MakeSpan(
+          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.
-    static_assert(sizeof(uintptr_t) == sizeof(uint8_t*),
-                  "We need uintptr_t to be exactly the same size as a pointer");
     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!
--- a/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.h
+++ b/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.h
@@ -37,39 +37,44 @@ class CryptoMetaData {
   }
 
   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);
-      mClearBytes.push_back(subsample.clear_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();
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
@@ -11,46 +11,62 @@
  * 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 <algorithm>
 #include <assert.h>
-#include <stdlib.h>
-#include <cctype>
 #include <ctype.h>
 #include <memory.h>
-#include <sstream>
 #include <stdarg.h>
+// This include is required in order for content_decryption_module to work // on
+// Unix systems.
+#include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <cctype>
+#include <memory>
+#include <sstream>
 #include <vector>
 
 #include "ArrayUtils.h"
 #include "BigEndian.h"
 #include "ClearKeyBase64.h"
-// This include is required in order for content_decryption_module to work
-// on Unix systems.
-#include "stddef.h"
-#include "content_decryption_module.h"
 #include "pk11pub.h"
 #include "prerror.h"
 #include "psshparser/PsshParser.h"
 #include "secmodt.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;
@@ -80,16 +96,77 @@ void CK_LogArray(const char* prepend, co
   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) {
+  assert(aKey.size() == CENC_KEY_LEN);
+  assert(aIV.size() == CENC_KEY_LEN);
+  assert(aCryptByteBlock <= 0xFF);
+  assert(aSkipByteBlock <= 0xFF);
+
+  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));
+
+  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;
+
+  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");
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.h
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.h
@@ -12,25 +12,27 @@
  * 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 <assert.h>
 
-// This include is required in order for content_decryption_module to work
-// on Unix systems.
-#include "stddef.h"
 #include "content_decryption_module.h"
+#include "mozilla/Span.h"
+#include "pk11pub.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)
@@ -59,16 +61,21 @@ void CK_LogArray(const char* aPrepend, c
 
 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);