Bug 1516673 - Adds CBCS encryption scheme functionality. r=bryce

Differential Revision: https://phabricator.services.mozilla.com/D60053

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jason 2020-03-26 20:53:59 +00:00
parent b39107855d
commit ea383d394a
4 changed files with 157 additions and 17 deletions

View File

@ -24,6 +24,7 @@
#include <algorithm>
#include "mozilla/CheckedInt.h"
#include "mozilla/Span.h"
using namespace cdm;
@ -169,12 +170,62 @@ Status ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
// 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);

View File

@ -42,14 +42,16 @@ class CryptoMetaData {
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];
mCipherBytes.push_back(subsample.cipher_bytes);
mClearBytes.push_back(subsample.clear_bytes);
mCipherBytes.push_back(subsample.cipher_bytes);
}
}
@ -63,8 +65,11 @@ class CryptoMetaData {
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;
};

View File

@ -16,36 +16,52 @@
#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;
@ -84,6 +100,67 @@ void CK_LogArray(const char* prepend, const uint8_t* aData,
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) {

View File

@ -17,15 +17,17 @@
#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, ...);
@ -64,6 +66,11 @@ struct KeyIdPair {
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);