Files
android/libwvdrmengine/level3/arm/l3crypto_engine_mock.cpp
Jeff Tinker 1a8aa0dd05 Initial import of Widevine Common Encryption DRM engine
Builds libwvmdrmengine.so, which is loaded by the new
MediaDrm APIs to support playback of Widevine/CENC
protected content.

Change-Id: I6f57dd37083dfd96c402cb9dd137c7d74edc8f1c
2013-03-22 11:14:17 -07:00

525 lines
14 KiB
C++

/*******************************************************************************
*
* Copyright 2013 Google Inc. All Rights Reserved.
*
* mock implementation of OEMCrypto APIs
*
******************************************************************************/
#include "l3crypto_engine_mock.h"
#include <iostream>
#include <vector>
#include <string.h>
#include "log.h"
#include "l3crypto_key_mock.h"
#include "openssl/aes.h"
#include "openssl/cmac.h"
#include "openssl/hmac.h"
#include "openssl/rand.h"
#include "openssl/sha.h"
#include "wv_cdm_constants.h"
namespace {
// Increment counter for AES-CTR
void ctr128_inc(uint8_t* counter) {
uint32_t n = 16;
do {
if (++counter[--n] != 0) return;
} while (n);
}
}
namespace wvoec_obfs {
SessionKeyTable::~SessionKeyTable() {
for (KeyMap::iterator i = keys_.begin(); i != keys_.end(); ++i) {
if (NULL != i->second) {
delete i->second;
}
}
}
bool SessionKeyTable::Insert(const KeyId key_id, const Key& key_data) {
if (keys_.find(key_id) != keys_.end()) return false;
keys_[key_id] = new Key(key_data);
return true;
}
Key* SessionKeyTable::Find(const KeyId key_id) {
if (keys_.find(key_id) == keys_.end()) {
return NULL;
}
return keys_[key_id];
}
void SessionKeyTable::Remove(const KeyId key_id) {
if (keys_.find(key_id) != keys_.end()) {
delete keys_[key_id];
keys_.erase(key_id);
}
}
void SessionContext::Open() {
}
void SessionContext::Close() {
}
// Internal utility function to derive key using CMAC-128
bool SessionContext::DeriveKey(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& context,
int counter,
std::vector<uint8_t>* out) {
if (key.empty() || counter > 2 || context.empty() || out == NULL) {
LOGE("[DeriveKey(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return false;
}
const EVP_CIPHER* cipher = EVP_aes_128_cbc();
CMAC_CTX* cmac_ctx = CMAC_CTX_new();
if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, 0)) {
LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]");
return false;
}
std::vector<uint8_t> message;
message.push_back(counter);
message.insert(message.end(), context.begin(), context.end());
if (!CMAC_Update(cmac_ctx, &message[0], message.size())) {
LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]");
return false;
}
size_t reslen;
uint8_t res[128];
if (!CMAC_Final(cmac_ctx, res, &reslen)) {
LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]");
return false;
}
out->assign(res, res+reslen);
CMAC_CTX_free(cmac_ctx);
return true;
}
bool SessionContext::DeriveKeys(const std::vector<uint8_t>& mac_key_context,
const std::vector<uint8_t>& enc_key_context) {
// Generate derived key for mac key
std::vector<uint8_t> device_key = ce_->keybox().device_key().value();
std::vector<uint8_t> mac_key;
std::vector<uint8_t> result;
if (!DeriveKey(device_key, mac_key_context, 1, &mac_key)) {
return false;
}
if (!DeriveKey(device_key, mac_key_context, 2, &result)) {
return false;
}
mac_key.insert( mac_key.end(), result.begin(), result.end());
// Generate derived key for encryption key
std::vector<uint8_t> enc_key;
if (!DeriveKey(device_key, enc_key_context, 1, &enc_key)) {
return false;
}
set_mac_key(mac_key);
set_encryption_key(enc_key);
return true;
}
// Utility function to generate a message signature
bool SessionContext::GenerateSignature(const uint8_t* message,
size_t message_length,
uint8_t* signature,
size_t* signature_length) {
if (message == NULL || message_length == 0 ||
signature == NULL || signature_length == 0) {
LOGE("[OEMCrypto_GenerateSignature(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return false;
}
if (mac_key_.empty() || mac_key_.size() != wvcdm::MAC_KEY_SIZE) {
LOGE("[GenerateSignature(): No MAC Key]");
return false;
}
if (*signature_length < SHA256_DIGEST_LENGTH) {
*signature_length = SHA256_DIGEST_LENGTH;
return false;
}
unsigned int md_len = *signature_length;
if (HMAC(EVP_sha256(), &mac_key_[0], SHA256_DIGEST_LENGTH,
message, message_length, signature, &md_len)) {
*signature_length = md_len;
return true;
}
return false;
}
// Validate message signature
bool SessionContext::ValidateMessage(const uint8_t* given_message,
size_t message_length,
const uint8_t* given_signature,
size_t signature_length) {
if (signature_length != SHA256_DIGEST_LENGTH) {
return false;
}
uint8_t computed_signature[signature_length];
if (! GenerateSignature(given_message, message_length,
computed_signature, &signature_length)) {
return false;
}
if (memcmp(given_signature, computed_signature, signature_length)) {
return false;
}
return true;
}
bool SessionContext::ParseKeyControl(
const std::vector<uint8_t>& key_control_string,
KeyControlBlock& key_control_block) {
key_control_block.Invalidate();
if (key_control_string.size() < wvcdm::KEY_CONTROL_SIZE) {
return false;
}
if (!key_control_block.SetFromString(key_control_string)) {
LOGE("KCB: BAD Size or Structure");
return false;
}
if (!key_control_block.Validate()) {
LOGE("KCB: BAD Signature");
return false;
}
// TODO(fredgc): This checks each key against a nonce and then throws it out.
// Instead, it should use the same nonce for the whole message.
// if (!CheckNonce(key_control_block.nonce())) {
// LOGE("KCB: BAD Nonce");
// return false;
// }
LOGD("KCB:");
LOGD(" valid: %d", key_control_block.valid());
LOGD(" duration: %d", key_control_block.duration());
LOGD(" nonce: %08X", key_control_block.nonce());
LOGD(" bits: %08X", key_control_block.control_bits());
return true;
}
bool SessionContext::InstallKey(const KeyId& key_id,
const std::vector<uint8_t>& key_data,
const std::vector<uint8_t>& key_data_iv,
const std::vector<uint8_t>& key_control,
const std::vector<uint8_t>& key_control_iv) {
// Decrypt encrypted key_data using derived encryption key and offered iv
std::vector<uint8_t> content_key;
std::vector<uint8_t> key_control_str;
KeyControlBlock key_control_block;
if (!ce_->DecryptMessage(this, encryption_key_, key_data_iv,
key_data, &content_key)) {
return false;
}
// Key control must be supplied by license server
if (key_control.empty()) {
LOGE("[Installkey(): WARNING: No Key Control]");
key_control_block.Invalidate();
return false;
} else {
if (key_control_iv.empty()) {
LOGE("[Installkey(): ERROR: No Key Control IV]");
return false;
}
if (!ce_->DecryptMessage(this, content_key, key_control_iv,
key_control, &key_control_str)) {
return false;
}
if (!ParseKeyControl(key_control_str, key_control_block)) {
return false;
}
}
Key key(KEYTYPE_CONTENT, content_key, key_control_block);
session_keys_.Insert(key_id, key);
return true;
}
bool SessionContext::RefreshKey(const KeyId& key_id,
const std::vector<uint8_t>& key_control,
const std::vector<uint8_t>& key_control_iv) {
if (key_id.empty()) {
return false;
}
Key* content_key = session_keys_.Find(key_id);
if (NULL == content_key) {
return false;
}
if (!key_control.empty()) {
const std::vector<uint8_t> content_key_value = content_key->value();
// Decrypt encrypted key control block
// We don't actually make use of it in Oemcrypto mock, just to verify its
// validity
std::vector<uint8_t> control;
if (key_control_iv.empty()) {
control = key_control;
} else if (!ce_->DecryptMessage(this, content_key_value, key_control_iv,
key_control, &control)) {
return false;
}
KeyControlBlock key_control_block;
if (!ParseKeyControl(control, key_control_block)) {
return false;
}
if (!content_key->UpdateControl(key_control_block)) {
return false;
}
}
return true;
}
bool SessionContext::UpdateMacKey(const std::vector<uint8_t>& enc_mac_key,
const std::vector<uint8_t>& iv) {
// Decrypt mac key from enc_mac_key using device_key
std::vector<uint8_t> mac_key;
if (!ce_->DecryptMessage(this, encryption_key_, iv,
enc_mac_key, &mac_key)) {
return false;
}
mac_key_ = mac_key;
return true;
}
bool SessionContext::SelectContentKey(const KeyId& key_id) {
const Key* content_key = session_keys_.Find(key_id);
if (NULL == content_key) {
LOGE("[SelectContentKey(): No key matches key id]");
return false;
}
current_content_key_ = content_key;
return true;
}
void SessionContext::AddNonce(uint32_t nonce) {
nonce_table_.AddNonce(nonce);
}
bool SessionContext::CheckNonce(uint32_t nonce) {
return nonce_table_.CheckNonce(nonce);
}
CryptoEngine::CryptoEngine() :
ce_state_(CE_INITIALIZED), current_session_(NULL) {
valid_ = true;
}
CryptoEngine::~CryptoEngine() {
current_session_ = NULL;
sessions_.clear();
}
void CryptoEngine::Terminate() {
}
KeyboxError CryptoEngine::ValidateKeybox() { return keybox_.Validate(); }
SessionId CryptoEngine::CreateSession() {
wvcdm::AutoLock lock(session_table_lock_);
static int unique_id = 1;
SessionId sid = (SessionId)++unique_id;
SessionContext* sctx = new SessionContext(this, sid);
sessions_[sid] = sctx;
return sid;
}
bool CryptoEngine::DestroySession(SessionId sid) {
SessionContext* sctx = FindSession(sid);
wvcdm::AutoLock lock(session_table_lock_);
if (sctx) {
sessions_.erase(sid);
delete sctx;
return true;
} else {
return false;
}
}
SessionContext* CryptoEngine::FindSession(SessionId sid) {
wvcdm::AutoLock lock(session_table_lock_);
ActiveSessions::iterator it = sessions_.find(sid);
if (it != sessions_.end()) {
return it->second;
}
return NULL;
}
// Internal utility function to decrypt the message
bool CryptoEngine::DecryptMessage(SessionContext* session,
const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv,
const std::vector<uint8_t>& message,
std::vector<uint8_t>* decrypted) {
if (key.empty() || iv.empty() || message.empty() || !decrypted) {
LOGE("[DecryptMessage(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return false;
}
decrypted->resize(message.size());
uint8_t iv_buffer[16];
memcpy(iv_buffer, &iv[0], 16);
AES_KEY aes_key;
AES_set_decrypt_key(&key[0], 128, &aes_key);
AES_cbc_encrypt(&message[0], &(decrypted->front()), message.size(),
&aes_key, iv_buffer, AES_DECRYPT);
return true;
}
bool CryptoEngine::DecryptCTR(SessionContext* session,
const std::vector<uint8_t>& iv,
size_t byte_offset,
const std::vector<uint8_t>& cipher_data,
bool is_encrypted,
void* clear_data,
BufferType buffer_type) {
// Check there is a content key
if (session->current_content_key() == NULL) {
LOGE("[DecryptCTR(): OEMCrypto_ERROR_NO_CONTENT_KEY]");
return false;
}
const KeyControlBlock& control = session->current_content_key()->control();
if (control.control_bits() & kControlDataPathSecure) {
if (buffer_type == BUFFER_TYPE_CLEAR) {
LOGE("[DecryptCTR(): Secure key with insecure buffer]");
return false;
}
}
// TODO(fredgc): Check duration of key.
const std::vector<uint8_t>& content_key = session->current_content_key()->value();
// Set the AES key.
if (static_cast<int>(content_key.size()) != AES_BLOCK_SIZE) {
LOGE("[DecryptCTR(): CONTENT_KEY has wrong size.");
return false;
}
const uint8_t* key_u8 = &content_key[0];
AES_KEY aes_key;
if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) {
LOGE("[DecryptCTR(): FAILURE]");
return false;
}
if (buffer_type == BUFFER_TYPE_DIRECT) {
// For reference implementation, we quietly drop direct video.
return true;
}
if (buffer_type == BUFFER_TYPE_SECURE) {
// For reference implementation, we also quietly drop secure data.
return true;
}
if (! is_encrypted) {
memcpy(reinterpret_cast<uint8_t*>(clear_data),
&cipher_data[0], cipher_data.size());
return true;
}
// Local copy (will be modified).
uint8_t aes_iv[AES_BLOCK_SIZE];
if (static_cast<int>(iv.size()) != AES_BLOCK_SIZE) {
LOGE("[DecryptCTR(): FAILURE: iv has wrong length]");
return false;
}
memcpy(aes_iv, &iv[0], AES_BLOCK_SIZE);
// Encrypt the IV.
uint8_t ecount_buf[AES_BLOCK_SIZE];
if (byte_offset != 0) {
// The context is needed only when not starting a new block.
AES_encrypt(aes_iv, ecount_buf, &aes_key);
ctr128_inc(aes_iv);
}
// Decryption.
unsigned int byte_offset_cur = byte_offset;
AES_ctr128_encrypt(
&cipher_data[0], reinterpret_cast<uint8_t*>(clear_data), cipher_data.size(),
&aes_key, aes_iv, ecount_buf, &byte_offset_cur);
if (byte_offset_cur != ((byte_offset + cipher_data.size()) % AES_BLOCK_SIZE)) {
LOGE("[DecryptCTR(): FAILURE: byte offset wrong.]");
return false;
}
return true;
}
void NonceTable::AddNonce(uint32_t nonce) {
int new_slot = -1;
int oldest_slot = -1;
for (int i = 0; i < kTableSize; ++i) {
if (valid_[i]) {
++age_[i];
if (-1 == oldest_slot) {
oldest_slot = i;
} else {
if (age_[i] > age_[oldest_slot]) {
oldest_slot = i;
}
}
} else {
if (-1 == new_slot) {
age_[i] = 0;
nonces_[i] = nonce;
valid_[i] = true;
new_slot = i;
}
}
}
if (-1 == new_slot) {
// reuse oldest
// assert (oldest_slot != -1)
int i = oldest_slot;
age_[i] = 0;
nonces_[i] = nonce;
valid_[i] = true;
}
}
bool NonceTable::CheckNonce(uint32_t nonce) {
for (int i = 0; i < kTableSize; ++i) {
if (valid_[i]) {
if (nonce == nonces_[i]) {
valid_[i] = false;
return true;
}
}
}
return false;
}
}; // namespace wvoec_obfs