In order to match the other implementations of CDM, we are going to replace the android OEMCrypto mockup with the one in the cdm repository. This would be disruptive to the clear key library because it relies on the current implementation of the mockup. In order to prevent that, I am moving the current mockup into the same directory as the clear key library. Then, we can put the new mockup under the directory libwvdrmengine. This mockup will then be deleted when the clear key library is deleted. Change-Id: I89ee23f249dacd18241ae5ca499329e620bf5a2c
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
|