This is a software only implementation of the OEMCrypto library for testing the rest of the DRM code. It currently implements the OEMCrypto_DecrtyptCTR function using a clear key. I've included the license request code so the rest of the group can play with it, but I have only tested part of it. This patch also has some makefiles and an integration testing. You should be able to generate the shared library libclearkeydrmengine.so with cd vendor/widevine/libclearkeydrmengine; mm You can create some unit test and integration test programs from the directories: vendor/widevine/libwvdrmengine/oemcrypto/test vendor/widevine/libclearkeydrmengine/test vendor/widevine/libclearkeydrmengine/inttest vendor/widevine/libclearkeydrmengine/crypto/test This change also addresses some comments about comments in OEMCryptoDASH.h which were made in https://googleplex-android-review.googlesource.com/257323 Change-Id: Id6899b9f8d2f09e09be2ea493baa83a6b929073b
420 lines
16 KiB
C++
420 lines
16 KiB
C++
/*********************************************************************
|
|
* MockOEMCrypto.cpp
|
|
*
|
|
* (c) Copyright 2011-2012 Google, Inc.
|
|
*
|
|
* Mock implementation of OEMCryptoDASH.h used for testing.
|
|
*********************************************************************/
|
|
|
|
#define LOG_TAG "WV.MockOEMCrypto"
|
|
#include <utils/Log.h>
|
|
#include <utils/String8.h>
|
|
|
|
#include "OEMCryptoDASH.h"
|
|
#include "MockOEMCrypto.h"
|
|
|
|
#include "openssl/rand.h"
|
|
#include "openssl/aes.h"
|
|
#include "openssl/cmac.h"
|
|
#include "openssl/hmac.h"
|
|
|
|
#include "wvcrc32.h"
|
|
|
|
#include "arpa/inet.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
using namespace android;
|
|
|
|
namespace wvdrm {
|
|
|
|
MockOEMCrypto* MockOEMCrypto::sSingleton = NULL;
|
|
|
|
static struct BinaryKeybox kDefaultKeybox = {
|
|
// Sample keybox used for test vectors
|
|
{
|
|
// deviceID
|
|
0x74, 0x65, 0x73, 0x74, 0x69, 0x64, 0x00, 0x00, // testid..
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
|
|
}, {
|
|
// key
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}, {
|
|
// data
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}, {
|
|
// magic
|
|
0x6b, 0x62, 0x6f, 0x78
|
|
}, {
|
|
// Crc
|
|
0x00, 0x00, 0x00, 0x00
|
|
}
|
|
};
|
|
|
|
MockOEMCrypto::MockOEMCrypto(BinaryKeybox *keybox) {
|
|
if (keybox == NULL) keybox = &kDefaultKeybox;
|
|
memcpy(&mKeybox, keybox, sizeof(BinaryKeybox));
|
|
mInitialized = false;
|
|
mClearKeys = true; // XXX -- default to clear keys for initial demo.
|
|
mMaxId = 0;
|
|
}
|
|
|
|
MockOEMCrypto::~MockOEMCrypto() {
|
|
}
|
|
|
|
OEMCryptoResult MockOEMCrypto::initialize(void) {
|
|
if (mInitialized) {
|
|
ALOGE( "[OEMCrypto_Initialize(): failed -- already initialized]\n" );
|
|
return OEMCrypto_ERROR_INIT_FAILED;
|
|
}
|
|
mInitialized = true;
|
|
ALOGV( "[OEMCrypto_Initialize(): success]\n" );
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult MockOEMCrypto::terminate(void) {
|
|
if (!mInitialized) {
|
|
ALOGE( "[OEMCrypto_Terminate(): failed - not initialized]\n" );
|
|
return OEMCrypto_ERROR_TERMINATE_FAILED;
|
|
}
|
|
mInitialized = false;
|
|
ALOGV( "[OEMCrypto_Terminate(): success]\n" );
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult MockOEMCrypto::openSession(OEMCrypto_SESSION *session) {
|
|
mMaxId++;
|
|
MockSession *s = new MockSession(mMaxId);
|
|
Mutex::Autolock lock(mSessionListMutex);
|
|
mSessions.add(mMaxId, s);
|
|
*session = mMaxId;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult MockOEMCrypto::closeSession(OEMCrypto_SESSION session) {
|
|
Mutex::Autolock lock(mSessionListMutex);
|
|
ssize_t index = mSessions.indexOfKey(session);
|
|
if (index < 0) return OEMCrypto_ERROR_INVALID_SESSION;
|
|
mSessions.removeItem(session);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
MockSession *MockOEMCrypto::findSession(OEMCrypto_SESSION session) {
|
|
Mutex::Autolock lock(mSessionListMutex);
|
|
ssize_t index = mSessions.indexOfKey(session);
|
|
if (index < 0) return NULL;
|
|
return mSessions.valueAt(index).get();
|
|
}
|
|
|
|
MockSession::MockSession(OEMCrypto_SESSION id) {
|
|
mId = id;
|
|
mCurrentKey = NULL;
|
|
mNoKeyLoaded = true;
|
|
}
|
|
|
|
MockSession::~MockSession() {
|
|
}
|
|
|
|
static void aes_128_cmac(const uint8_t key[16], const uint8_t *data, size_t len, uint8_t *dest) {
|
|
// Quote from openssl tutorial:
|
|
// "Check out the function print_cmac_gen in the file fips/cmac/fips_cmactest.c"
|
|
CMAC_CTX *cmac_ctx = CMAC_CTX_new();
|
|
|
|
CMAC_Init(cmac_ctx, key, 16, EVP_aes_128_cbc(), 0);
|
|
CMAC_Update(cmac_ctx, data, len);
|
|
size_t reslen;
|
|
unsigned char res[128];
|
|
if (!CMAC_Final(cmac_ctx, res, &reslen)) {
|
|
ALOGE("Error calculating CMAC\n");
|
|
} else if (16 != reslen) {
|
|
ALOGE("Parameter error, CMAC length = %d\n", reslen);
|
|
} else {
|
|
memcpy(dest, res, 16);
|
|
}
|
|
CMAC_CTX_free(cmac_ctx);
|
|
}
|
|
|
|
OEMCryptoResult MockSession::generateDerivedKeys(const uint8_t *mac_key_context,
|
|
uint32_t mac_key_context_length,
|
|
const uint8_t *enc_key_context,
|
|
uint32_t enc_key_context_length) {
|
|
uint8_t enc_buffer[enc_key_context_length + 1];
|
|
memcpy( enc_buffer+1, enc_key_context, enc_key_context_length);
|
|
enc_buffer[0] = 0x01;
|
|
aes_128_cmac(MockOEMCrypto::sSingleton->mKeybox.mKey,
|
|
enc_buffer, enc_key_context_length+1, mEncryptKey);
|
|
|
|
uint8_t mac_buffer[mac_key_context_length + 1];
|
|
memcpy( mac_buffer+1, mac_key_context, mac_key_context_length);
|
|
mac_buffer[0] = 0x01;
|
|
aes_128_cmac(MockOEMCrypto::sSingleton->mKeybox.mKey,
|
|
mac_buffer, mac_key_context_length+1, mMacKey);
|
|
mac_buffer[0] = 0x02;
|
|
aes_128_cmac(MockOEMCrypto::sSingleton->mKeybox.mKey,
|
|
mac_buffer, mac_key_context_length+1, mMacKey+16);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult MockSession::generateNonce(uint32_t* nonce) {
|
|
RAND_bytes((uint8_t *)nonce, 4);
|
|
mNonce = *nonce;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult MockSession::generateSignature(const uint8_t* message,
|
|
size_t message_length,
|
|
uint8_t* signature,
|
|
size_t* signature_length) {
|
|
if (*signature_length < 32) {
|
|
*signature_length = 32;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if ( HMAC( EVP_sha256(), mMacKey, 32, message, message_length,
|
|
signature, signature_length) ) {
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
|
|
OEMCryptoResult MockSession::loadKeys(const uint8_t* message,
|
|
size_t message_length,
|
|
const uint8_t* signature,
|
|
size_t signature_length,
|
|
const uint8_t* enc_mac_key_iv,
|
|
const uint8_t* enc_mac_key,
|
|
size_t num_keys,
|
|
const OEMCrypto_KeyObject* key_array) {
|
|
|
|
uint8_t computed_signature[32];
|
|
if ( ! HMAC( EVP_sha256(), mMacKey, 32, message, message_length,
|
|
computed_signature, &signature_length) ) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (signature_length != 32 || memcmp(signature, computed_signature, 32)) {
|
|
ALOGE("Computed signature does not match.");
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
|
|
mKeys.clear();
|
|
mKeys.setCapacity(num_keys);
|
|
AES_KEY aes_encrypt_key;
|
|
AES_set_decrypt_key( mEncryptKey, 128, &aes_encrypt_key);
|
|
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
|
uint8_t iv[16]; // read/write temporary buffer.
|
|
for(size_t i=0; i < num_keys; i++) {
|
|
mKeys.add();
|
|
mKeys.editItemAt(i).mKeyId.insertArrayAt( key_array[i].key_id, 0, key_array[i].key_id_length);
|
|
memcpy( iv, key_array[i].key_data_iv, 16);
|
|
AES_cbc_encrypt(key_array[i].key_data, mKeys.editItemAt(i).mKeyData, 16,
|
|
&aes_encrypt_key, iv, AES_DECRYPT);
|
|
|
|
AES_KEY aes_content_key;
|
|
AES_set_decrypt_key( mKeys.editItemAt(i).mKeyData, 128, &aes_content_key);
|
|
memcpy( iv, key_array[i].key_control_iv, 16);
|
|
AES_cbc_encrypt(key_array[i].key_control, (uint8_t *) &(mKeys.editItemAt(i).mControl), 16,
|
|
&aes_content_key, iv, AES_DECRYPT);
|
|
mKeys.editItemAt(i).mControl.mDuration = ntohl(mKeys[i].mControl.mDuration);
|
|
mKeys.editItemAt(i).mControl.mControl = ntohl(mKeys[i].mControl.mControl);
|
|
|
|
ALOGV("Control Flags are 0x%08x", mKeys[i].mControl.mControl);
|
|
ALOGV("Duration is %d", mKeys[i].mControl.mDuration);
|
|
|
|
if (memcmp( mKeys[i].mControl.mVerification, "kctl", 4)) {
|
|
ALOGE("Key Control block %d verification = 0x%08X, expected kctl=0x%08X", i,
|
|
*((uint32_t *)(mKeys[i].mControl.mVerification)),
|
|
*((uint32_t *)("kctl")));
|
|
result = OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (mKeys[i].mControl.mControl & (1<<3)) { // Nonce enabled.
|
|
if (mKeys[i].mControl.mNonce != mNonce) {
|
|
ALOGE("NONCE ERROR for key %d: got: %08X, expected: %08X", i,
|
|
mKeys[i].mControl.mNonce, mNonce);
|
|
result = OEMCrypto_ERROR_INVALID_NONCE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
mKeys.clear();
|
|
return result;
|
|
}
|
|
|
|
memcpy( iv, enc_mac_key_iv, 16);
|
|
AES_cbc_encrypt(enc_mac_key, mMacKey, 32,
|
|
&aes_encrypt_key, iv, AES_DECRYPT);
|
|
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult MockSession::refreshKeys(const uint8_t* message,
|
|
size_t message_length,
|
|
const uint8_t* signature,
|
|
size_t signature_length,
|
|
size_t num_keys,
|
|
const OEMCrypto_KeyRefreshObject* key_array) {
|
|
// XXX
|
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
OEMCryptoResult MockSession::selectKey(const uint8_t* key_id,
|
|
size_t key_id_length) {
|
|
|
|
// TODO: handle key_id=0 case. Does that mean following data is not encrypted?
|
|
// This is the demo version:
|
|
if (MockOEMCrypto::sSingleton->mClearKeys) {
|
|
mCurrentKey = &mClearKey;
|
|
if (mNoKeyLoaded
|
|
|| mClearKey.mKeyId.size() != 16
|
|
|| (memcmp(mClearKey.mKeyId.array(), key_id, 16) != 0)) { // Copy it once.
|
|
|
|
mClearKey.mKeyId.clear();
|
|
mClearKey.mKeyId.insertArrayAt( key_id, 0, key_id_length);
|
|
memcpy(mClearKey.mKeyData, key_id, 16);
|
|
memcpy(mClearKey.mControl.mVerification, "kctl",4);
|
|
mClearKey.mControl.mDuration = 0;
|
|
mClearKey.mControl.mNonce = 0XABACAB00;
|
|
mClearKey.mControl.mDuration = 0;
|
|
if (AES_set_encrypt_key(mClearKey.mKeyData, 128, &mClearKey.mKey) < 0) {
|
|
ALOGE("Could not set encryption key.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
mNoKeyLoaded = false;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
} else {
|
|
for(size_t i=0; i< mKeys.size(); i++) {
|
|
if (key_id_length == mKeys[i].mKeyId.size()
|
|
&& memcmp(key_id, mKeys[i].mKeyId.array(), key_id_length)) {
|
|
mCurrentKey = &mKeys.editItemAt(i);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
}
|
|
return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
|
}
|
|
}
|
|
|
|
OEMCryptoResult MockSession::decryptCTR(const uint8_t *data_addr,
|
|
size_t data_length,
|
|
bool is_encrypted,
|
|
const uint8_t *iv,
|
|
size_t offset,
|
|
const OEMCrypto_DestBufferDesc* out_buffer) {
|
|
if (mCurrentKey == NULL) {
|
|
ALOGE("SelectKey was not called.");
|
|
return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
|
}
|
|
if (mCurrentKey->mControl.mControl & (1<<4)) { // secure only
|
|
if (out_buffer->type == OEMCrypto_BufferType_Clear) {
|
|
ALOGE("Key type is secure only, but buffer is not secure.");
|
|
return OEMCrypto_ERROR_CONTROL_INVALID;
|
|
}
|
|
}
|
|
uint8_t *dest;
|
|
if (out_buffer->type == OEMCrypto_BufferType_Direct) {
|
|
ALOGV("Buffer type is direct. No work to do.");
|
|
return OEMCrypto_SUCCESS; // XXX -- send directdly to video.
|
|
} else if (out_buffer->type == OEMCrypto_BufferType_Direct) {
|
|
dest = (uint8_t *)out_buffer->buffer.secure.handle;
|
|
if (data_length > out_buffer->buffer.secure.max_length) {
|
|
ALOGE("Secure Buffer is %d long, but wanted %d.",
|
|
out_buffer->buffer.secure.max_length, data_length);
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
} else {
|
|
dest = out_buffer->buffer.clear.address;
|
|
if (data_length > out_buffer->buffer.clear.max_length) {
|
|
ALOGE("Buffer is %d long, but wanted %d.",
|
|
out_buffer->buffer.clear.max_length, data_length);
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
}
|
|
if (! is_encrypted) {
|
|
memcpy(dest, data_addr, data_length);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
uint8_t buffer[offset + data_length];
|
|
|
|
unsigned int num = 0;
|
|
uint8_t ivec[AES_BLOCK_SIZE];
|
|
uint8_t ecount[AES_BLOCK_SIZE];
|
|
memset(ecount, 0, AES_BLOCK_SIZE);
|
|
memcpy(ivec, iv, AES_BLOCK_SIZE);
|
|
|
|
AES_ctr128_encrypt(data_addr+(-offset), buffer, data_length+offset,
|
|
&(mCurrentKey->mKey), ivec, ecount, &num);
|
|
memcpy(dest, buffer+offset, data_length);
|
|
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult MockOEMCrypto::installKeybox(uint8_t *keybox,
|
|
size_t keyBoxLength) {
|
|
ALOGV("OEMCryptoResult OEMCrypto_InstallKeybox\n");
|
|
if (keyBoxLength != 128) { /* 128 = sizeof(struct Debug_Keybox). */
|
|
ALOGE("The keybox has the wrong length = %d.\n", keyBoxLength);
|
|
return OEMCrypto_ERROR_WRITE_KEYBOX;
|
|
}
|
|
memcpy( & mKeybox, keybox, keyBoxLength);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult MockOEMCrypto::isKeyboxValid(void) {
|
|
ALOGV("OEMCryptoResult OEMCrypto_IsKeyboxValid\n");
|
|
if (strncmp((char*)mKeybox.mMagic, "kbox", 4) != 0) {
|
|
ALOGE("Magic is not 'kbox'=0x%08x, it is '%4.4s'=0x%08x\n",
|
|
*((uint32_t *)("kbox")),
|
|
mKeybox.mMagic, *((uint32_t *)(mKeybox.mMagic)));
|
|
return OEMCrypto_ERROR_BAD_MAGIC;
|
|
}
|
|
uint32_t crc_computed;
|
|
uint32_t *crc_stored = (uint32_t *)mKeybox.mCrc;
|
|
// XXX -------------------------------------------------------------------
|
|
// XXX TODO: verify that CRC in keybox is in network byte order.
|
|
// XXX -------------------------------------------------------------------
|
|
crc_computed = ntohl(wvcrc32( mKeybox.mDeviceId, 124));
|
|
if (crc_computed != *crc_stored) {
|
|
ALOGE("CRC problem: computed = %08x, stored = %08x.\n", crc_computed, *crc_stored);
|
|
return OEMCrypto_ERROR_BAD_CRC;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult MockOEMCrypto::getDeviceID(uint8_t* deviceID,
|
|
size_t *idLength) {
|
|
ALOGV("OEMCryptoResult OEMCrypto_GetDeviceID -> %s\n", mKeybox.mDeviceId);
|
|
size_t size = strnlen( (char *)mKeybox.mDeviceId, 31) + 1;
|
|
if (*idLength < size) {
|
|
*idLength = size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
*idLength = size;
|
|
memcpy( deviceID, mKeybox.mDeviceId, size );
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult MockOEMCrypto::getKeyData(uint8_t* keyData,
|
|
size_t *keyDataLength) {
|
|
ALOGV("OEMCryptoResult OEMCrypto_GetKeyData\n");
|
|
if (*keyDataLength < sizeof(mKeybox.mData)) {
|
|
*keyDataLength = sizeof(mKeybox.mData);
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
*keyDataLength = sizeof(mKeybox.mData);
|
|
memcpy( keyData, mKeybox.mData, sizeof(mKeybox.mData));
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
}; // namespace wvdrm
|