This is a merge from the Widevine repo of http://go/wvgerrit/117311 Update backwards compatibility builds http://go/wvgerrit/117423 Restrict maximum size of key id To protect from out-of-memory found by fuzz testing. http://go/wvgerrit/117683 Generation number should wrap The master generation number should wrap around on overflow. This means that we cannot use less than to check for a skew of 1. http://go/wvgerrit/119232 Replace 0 with nullptr Bug: 176234903 Bug: 184866351 Bug: 161243686 Test: ran unit tests (CL affects test code only) Merged-In: Ie787bcf9c66a7605700c3dc29a8aa16406926ce3 Change-Id: I2b02a36a70a0920f31ffc00de102a23516d4b20e
713 lines
28 KiB
C++
713 lines
28 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine
|
|
// License Agreement.
|
|
//
|
|
// Reference implementation of OEMCrypto APIs
|
|
//
|
|
#include "oemcrypto_usage_table_ref.h"
|
|
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <openssl/aes.h>
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/sha.h>
|
|
|
|
#include "file_store.h"
|
|
#include "log.h"
|
|
#include "odk.h"
|
|
#include "oemcrypto_engine_ref.h"
|
|
// TODO(fredgc): Setting the device files base bath is currently broken as
|
|
// wvcdm::Properties is no longer used by the reference code.
|
|
//#include "properties.h"
|
|
#include "pst_report.h"
|
|
#include "string_conversions.h"
|
|
|
|
namespace wvoec_ref {
|
|
namespace {
|
|
const size_t kMagicLength = 8;
|
|
const char* kEntryVerification = "USEENTRY";
|
|
const char* kHeaderVerification = "USEHEADR";
|
|
// Offset into a signed block where we start encrypting. We need to
|
|
// skip the signature and the iv.
|
|
const size_t kEncryptionOffset = SHA256_DIGEST_LENGTH + SHA256_DIGEST_LENGTH;
|
|
|
|
// A structure that holds an usage entry and its signature.
|
|
struct SignedEntryBlock {
|
|
uint8_t signature[SHA256_DIGEST_LENGTH];
|
|
uint8_t iv[SHA256_DIGEST_LENGTH];
|
|
uint8_t verification[kMagicLength];
|
|
StoredUsageEntry data;
|
|
};
|
|
|
|
// This has the data in the header of constant size. There is also an array
|
|
// of generation numbers.
|
|
struct SignedHeaderBlock {
|
|
uint8_t signature[SHA256_DIGEST_LENGTH];
|
|
uint8_t iv[SHA256_DIGEST_LENGTH];
|
|
uint8_t verification[kMagicLength];
|
|
int64_t master_generation;
|
|
uint64_t count;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
UsageTableEntry::UsageTableEntry(UsageTable* table, uint32_t index,
|
|
int64_t generation)
|
|
: usage_table_(table), recent_decrypt_(false), forbid_report_(true) {
|
|
memset(&data_, 0, sizeof(data_));
|
|
data_.generation_number = generation;
|
|
data_.index = index;
|
|
}
|
|
|
|
UsageTableEntry::~UsageTableEntry() { usage_table_->ReleaseEntry(data_.index); }
|
|
|
|
OEMCryptoResult UsageTableEntry::SetPST(const uint8_t* pst, size_t pst_length) {
|
|
if (pst_length > kMaxPSTLength) return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
|
data_.pst_length = pst_length;
|
|
if (!pst || !pst_length) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
memcpy(data_.pst, pst, pst_length);
|
|
data_.time_of_license_received = usage_table_->ce_->SystemTime();
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
bool UsageTableEntry::VerifyPST(const uint8_t* pst, size_t pst_length) {
|
|
if (pst_length > kMaxPSTLength) return false;
|
|
if (data_.pst_length != pst_length) return false;
|
|
if (!pst || !pst_length) return false;
|
|
return 0 == CRYPTO_memcmp(pst, data_.pst, pst_length);
|
|
}
|
|
|
|
bool UsageTableEntry::VerifyMacKeys(const std::vector<uint8_t>& server,
|
|
const std::vector<uint8_t>& client) {
|
|
return (server.size() == wvoec::MAC_KEY_SIZE) &&
|
|
(client.size() == wvoec::MAC_KEY_SIZE) &&
|
|
(0 == CRYPTO_memcmp(&server[0], data_.mac_key_server,
|
|
wvoec::MAC_KEY_SIZE)) &&
|
|
(0 ==
|
|
CRYPTO_memcmp(&client[0], data_.mac_key_client, wvoec::MAC_KEY_SIZE));
|
|
}
|
|
|
|
bool UsageTableEntry::SetMacKeys(const std::vector<uint8_t>& server,
|
|
const std::vector<uint8_t>& client) {
|
|
if ((server.size() != wvoec::MAC_KEY_SIZE) ||
|
|
(client.size() != wvoec::MAC_KEY_SIZE))
|
|
return false;
|
|
memcpy(data_.mac_key_server, &server[0], wvoec::MAC_KEY_SIZE);
|
|
memcpy(data_.mac_key_client, &client[0], wvoec::MAC_KEY_SIZE);
|
|
return true;
|
|
}
|
|
|
|
void UsageTableEntry::ForbidReport() {
|
|
forbid_report_ = true;
|
|
data_.generation_number++;
|
|
usage_table_->IncrementGeneration();
|
|
}
|
|
|
|
OEMCryptoResult UsageTableEntry::ReportUsage(const std::vector<uint8_t>& pst,
|
|
uint8_t* buffer,
|
|
size_t* buffer_length) {
|
|
if (forbid_report_) return OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE;
|
|
if (recent_decrypt_) return OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE;
|
|
if (pst.size() == 0 || pst.size() > kMaxPSTLength ||
|
|
pst.size() != data_.pst_length) {
|
|
LOGE("ReportUsage: bad pst length = %zu, should be %u.", pst.size(),
|
|
data_.pst_length);
|
|
return OEMCrypto_ERROR_WRONG_PST;
|
|
}
|
|
if (CRYPTO_memcmp(&pst[0], data_.pst, data_.pst_length)) {
|
|
LOGE("ReportUsage: wrong pst %s, should be %s.",
|
|
wvcdm::b2a_hex(pst).c_str(),
|
|
wvcdm::HexEncode(data_.pst, data_.pst_length).c_str());
|
|
return OEMCrypto_ERROR_WRONG_PST;
|
|
}
|
|
const size_t length_needed =
|
|
wvcdm::Unpacked_PST_Report::report_size(pst.size());
|
|
if (*buffer_length < length_needed) {
|
|
*buffer_length = length_needed;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (!buffer) {
|
|
LOGE("ReportUsage: buffer was null pointer.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
wvcdm::Unpacked_PST_Report pst_report(buffer);
|
|
const int64_t now = usage_table_->ce_->SystemTime();
|
|
pst_report.set_seconds_since_license_received(now -
|
|
data_.time_of_license_received);
|
|
pst_report.set_seconds_since_first_decrypt(now - data_.time_of_first_decrypt);
|
|
pst_report.set_seconds_since_last_decrypt(now - data_.time_of_last_decrypt);
|
|
pst_report.set_status(data_.status);
|
|
pst_report.set_clock_security_level(kSecureTimer);
|
|
pst_report.set_pst_length(data_.pst_length);
|
|
memcpy(pst_report.pst(), data_.pst, data_.pst_length);
|
|
unsigned int md_len = SHA_DIGEST_LENGTH;
|
|
if (!HMAC(EVP_sha1(), data_.mac_key_client, wvoec::MAC_KEY_SIZE,
|
|
buffer + SHA_DIGEST_LENGTH, length_needed - SHA_DIGEST_LENGTH,
|
|
pst_report.signature(), &md_len)) {
|
|
LOGE("ReportUsage: could not compute signature.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
void UsageTableEntry::UpdateAndIncrement(ODK_ClockValues* clock_values) {
|
|
if (recent_decrypt_) {
|
|
data_.time_of_last_decrypt = usage_table_->ce_->SystemTime();
|
|
recent_decrypt_ = false;
|
|
}
|
|
data_.time_of_license_received = clock_values->time_of_license_signed;
|
|
data_.time_of_first_decrypt = clock_values->time_of_first_decrypt;
|
|
// Use the most recent time_of_last_decrypt.
|
|
if (static_cast<uint64_t>(data_.time_of_last_decrypt) <
|
|
clock_values->time_of_last_decrypt) {
|
|
// For the reference implementation, we update the clock_values on every
|
|
// decrypt.
|
|
data_.time_of_last_decrypt = clock_values->time_of_last_decrypt;
|
|
} else {
|
|
// For this reference implementation of OEMCrypto, we regularly update
|
|
// clock_values->time_of_last_decrypt and we could just update
|
|
// data_.time_of_last_decrypt here. However, I'm including the line below to
|
|
// make it clear that you could do it the other way around. When this
|
|
// function is called, the two values should be synced so that the usage
|
|
// entry can be saved with the correct value.
|
|
clock_values->time_of_last_decrypt = data_.time_of_last_decrypt;
|
|
}
|
|
data_.status = clock_values->status;
|
|
data_.generation_number++;
|
|
usage_table_->IncrementGeneration();
|
|
forbid_report_ = false;
|
|
}
|
|
|
|
OEMCryptoResult UsageTableEntry::SaveData(CryptoEngine* ce,
|
|
SessionContext* session,
|
|
uint8_t* signed_buffer,
|
|
size_t buffer_size) {
|
|
// buffer_size was determined by calling function.
|
|
if (buffer_size != SignedEntrySize()) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
std::vector<uint8_t> clear_buffer(buffer_size);
|
|
memset(&clear_buffer[0], 0, buffer_size);
|
|
memset(signed_buffer, 0, buffer_size);
|
|
SignedEntryBlock* clear =
|
|
reinterpret_cast<SignedEntryBlock*>(&clear_buffer[0]);
|
|
SignedEntryBlock* encrypted =
|
|
reinterpret_cast<SignedEntryBlock*>(signed_buffer);
|
|
clear->data = data_; // Copy the current data.
|
|
memcpy(clear->verification, kEntryVerification, kMagicLength);
|
|
|
|
// This should be encrypted and signed with a device specific key.
|
|
// For the reference implementation, I'm just going to use the keybox key.
|
|
const std::vector<uint8_t>& key = ce->DeviceRootKey();
|
|
if (key.empty()) {
|
|
LOGE("SaveUsageEntry: DeviceRootKey is unexpectedly empty.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
// Encrypt the entry.
|
|
if (RAND_bytes(encrypted->iv, wvoec::KEY_IV_SIZE) != 1) {
|
|
LOGE("SaveUsageEntry: Could not generate iv.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; // working iv buffer.
|
|
memcpy(iv_buffer, encrypted->iv, wvoec::KEY_IV_SIZE);
|
|
AES_KEY aes_key;
|
|
AES_set_encrypt_key(&key[0], 128, &aes_key);
|
|
AES_cbc_encrypt(
|
|
&clear_buffer[kEncryptionOffset], &signed_buffer[kEncryptionOffset],
|
|
buffer_size - kEncryptionOffset, &aes_key, iv_buffer, AES_ENCRYPT);
|
|
|
|
// Sign the entry.
|
|
unsigned int sig_length = SHA256_DIGEST_LENGTH;
|
|
if (!HMAC(EVP_sha256(), &key[0], key.size(),
|
|
&signed_buffer[SHA256_DIGEST_LENGTH],
|
|
buffer_size - SHA256_DIGEST_LENGTH, encrypted->signature,
|
|
&sig_length)) {
|
|
LOGE("SaveUsageEntry: Could not sign entry.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult UsageTableEntry::LoadData(CryptoEngine* ce, uint32_t index,
|
|
const std::vector<uint8_t>& buffer,
|
|
ODK_ClockValues* clock_values) {
|
|
if (buffer.size() < SignedEntrySize()) return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
if (buffer.size() > SignedEntrySize())
|
|
LOGW("LoadUsageTableEntry: buffer is large. %zu > %zu", buffer.size(),
|
|
SignedEntrySize());
|
|
std::vector<uint8_t> clear_buffer(buffer.size());
|
|
SignedEntryBlock* clear =
|
|
reinterpret_cast<SignedEntryBlock*>(&clear_buffer[0]);
|
|
const SignedEntryBlock* encrypted =
|
|
reinterpret_cast<const SignedEntryBlock*>(&buffer[0]);
|
|
|
|
// This should be encrypted and signed with a device specific key.
|
|
// For the reference implementation, I'm just going to use the keybox key.
|
|
const std::vector<uint8_t>& key = ce->DeviceRootKey();
|
|
if (key.empty()) {
|
|
LOGE("LoadUsageEntry: DeviceRootKey is unexpectedly empty.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
// Verify the signature of the usage entry. Sign encrypted into clear buffer.
|
|
unsigned int sig_length = SHA256_DIGEST_LENGTH;
|
|
if (!HMAC(EVP_sha256(), &key[0], key.size(), &buffer[SHA256_DIGEST_LENGTH],
|
|
buffer.size() - SHA256_DIGEST_LENGTH, clear->signature,
|
|
&sig_length)) {
|
|
LOGE("LoadUsageEntry: Could not sign entry.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (CRYPTO_memcmp(clear->signature, encrypted->signature,
|
|
SHA256_DIGEST_LENGTH)) {
|
|
LOGE("LoadUsageEntry: Signature did not match.");
|
|
LOGE("LoadUsageEntry: Invalid signature given: %s",
|
|
wvcdm::HexEncode(encrypted->signature, sig_length).c_str());
|
|
LOGE("LoadUsageEntry: Invalid signature computed: %s",
|
|
wvcdm::HexEncode(clear->signature, sig_length).c_str());
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
|
|
// Next, decrypt the entry.
|
|
uint8_t iv_buffer[wvoec::KEY_IV_SIZE];
|
|
memcpy(iv_buffer, encrypted->iv, wvoec::KEY_IV_SIZE);
|
|
AES_KEY aes_key;
|
|
AES_set_decrypt_key(&key[0], 128, &aes_key);
|
|
AES_cbc_encrypt(&buffer[kEncryptionOffset], &clear_buffer[kEncryptionOffset],
|
|
buffer.size() - kEncryptionOffset, &aes_key, iv_buffer,
|
|
AES_DECRYPT);
|
|
|
|
// Check the verification string is correct.
|
|
if (memcmp(kEntryVerification, clear->verification, kMagicLength)) {
|
|
LOGE("LoadUsageEntry: Invalid magic: %s=%8.8s expected: %s=%8.8s",
|
|
wvcdm::HexEncode(clear->verification, kMagicLength).c_str(),
|
|
clear->verification,
|
|
wvcdm::HexEncode(reinterpret_cast<const uint8_t*>(kEntryVerification),
|
|
kMagicLength)
|
|
.c_str(),
|
|
reinterpret_cast<const uint8_t*>(kEntryVerification));
|
|
return OEMCrypto_ERROR_BAD_MAGIC;
|
|
}
|
|
|
|
// Check that the index is correct.
|
|
if (index != clear->data.index) {
|
|
LOGE("LoadUsageEntry: entry says index is %u, not %u", clear->data.index,
|
|
index);
|
|
return OEMCrypto_ERROR_INVALID_SESSION;
|
|
}
|
|
if (clear->data.status > kInactiveUnused) {
|
|
LOGE("LoadUsageEntry: entry has bad status %d", clear->data.status);
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
data_ = clear->data;
|
|
return ODK_ReloadClockValues(
|
|
clock_values, data_.time_of_license_received, data_.time_of_first_decrypt,
|
|
data_.time_of_last_decrypt, data_.status, ce->SystemTime());
|
|
}
|
|
|
|
size_t UsageTableEntry::SignedEntrySize() {
|
|
size_t base = sizeof(SignedEntryBlock);
|
|
// round up to make even number of blocks:
|
|
size_t blocks = (base - 1) / wvoec::KEY_IV_SIZE + 1;
|
|
return blocks * wvoec::KEY_IV_SIZE;
|
|
}
|
|
|
|
UsageTable::~UsageTable() {}
|
|
|
|
size_t UsageTable::SignedHeaderSize(size_t count) {
|
|
size_t base = sizeof(SignedHeaderBlock) + count * sizeof(int64_t);
|
|
// round up to make even number of blocks:
|
|
size_t blocks = (base - 1) / wvoec::KEY_IV_SIZE + 1;
|
|
return blocks * wvoec::KEY_IV_SIZE;
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::UpdateUsageEntry(
|
|
SessionContext* session, UsageTableEntry* entry, uint8_t* header_buffer,
|
|
size_t* header_buffer_length, uint8_t* entry_buffer,
|
|
size_t* entry_buffer_length, ODK_ClockValues* clock_values) {
|
|
size_t signed_header_size = SignedHeaderSize(generation_numbers_.size());
|
|
if (*entry_buffer_length < UsageTableEntry::SignedEntrySize() ||
|
|
*header_buffer_length < signed_header_size) {
|
|
*entry_buffer_length = UsageTableEntry::SignedEntrySize();
|
|
*header_buffer_length = signed_header_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
*entry_buffer_length = UsageTableEntry::SignedEntrySize();
|
|
*header_buffer_length = signed_header_size;
|
|
if ((!header_buffer) || (!entry_buffer))
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
entry->UpdateAndIncrement(clock_values);
|
|
generation_numbers_[entry->index()] = entry->generation_number();
|
|
OEMCryptoResult result =
|
|
entry->SaveData(ce_, session, entry_buffer, *entry_buffer_length);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = SaveUsageTableHeader(header_buffer, *header_buffer_length);
|
|
return result;
|
|
}
|
|
|
|
UsageTableEntry* UsageTable::MakeEntry(uint32_t index) {
|
|
return new UsageTableEntry(this, index, master_generation_number_);
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::CreateNewUsageEntry(
|
|
SessionContext* session, std::unique_ptr<UsageTableEntry>* entry,
|
|
uint32_t* usage_entry_number) {
|
|
if (!header_loaded_) {
|
|
LOGE("CreateNewUsageEntry: Header not loaded.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!entry) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
if (!usage_entry_number) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
const size_t index = generation_numbers_.size();
|
|
const size_t max = ce_->max_usage_table_size();
|
|
if (max > 0 && index >= max) {
|
|
LOGE("Too many usage entries: %zu/%zu", index, max);
|
|
return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES;
|
|
}
|
|
UsageTableEntry* new_entry = MakeEntry(index);
|
|
generation_numbers_.push_back(master_generation_number_);
|
|
sessions_.push_back(session);
|
|
master_generation_number_++;
|
|
entry->reset(new_entry);
|
|
*usage_entry_number = index;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::LoadUsageEntry(
|
|
SessionContext* session, std::unique_ptr<UsageTableEntry>* entry,
|
|
uint32_t index, const std::vector<uint8_t>& buffer,
|
|
ODK_ClockValues* clock_values) {
|
|
if (!header_loaded_) {
|
|
LOGE("LoadUsageEntry: Header not loaded.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!entry) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
if (index >= generation_numbers_.size())
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
if (sessions_[index]) {
|
|
LOGE("LoadUsageEntry: index %u used by other session.", index);
|
|
return OEMCrypto_ERROR_INVALID_SESSION;
|
|
}
|
|
const size_t max = ce_->max_usage_table_size();
|
|
if (max > 0 && index >= max) {
|
|
LOGE("Too many usage entries: %u/%zu", index, max);
|
|
return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES;
|
|
}
|
|
std::unique_ptr<UsageTableEntry> new_entry(MakeEntry(index));
|
|
|
|
OEMCryptoResult status =
|
|
new_entry->LoadData(ce_, index, buffer, clock_values);
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
return status;
|
|
}
|
|
if (new_entry->generation_number() != generation_numbers_[index]) {
|
|
LOGE("Generation SKEW: %ld -> %ld", new_entry->generation_number(),
|
|
generation_numbers_[index]);
|
|
if ((new_entry->generation_number() + 1 == generation_numbers_[index]) ||
|
|
(new_entry->generation_number() - 1 == generation_numbers_[index])) {
|
|
status = OEMCrypto_WARNING_GENERATION_SKEW;
|
|
} else {
|
|
return OEMCrypto_ERROR_GENERATION_SKEW;
|
|
}
|
|
}
|
|
sessions_[index] = session;
|
|
*entry = std::move(new_entry);
|
|
return status;
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::ShrinkUsageTableHeader(
|
|
uint32_t new_table_size, uint8_t* header_buffer,
|
|
size_t* header_buffer_length) {
|
|
if (new_table_size > generation_numbers_.size()) {
|
|
LOGE("OEMCrypto_ShrinkUsageTableHeader: %u > %zu", new_table_size,
|
|
generation_numbers_.size());
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
size_t signed_header_size = SignedHeaderSize(new_table_size);
|
|
if (*header_buffer_length < signed_header_size) {
|
|
*header_buffer_length = signed_header_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
*header_buffer_length = signed_header_size;
|
|
if (!header_buffer) {
|
|
LOGE("OEMCrypto_ShrinkUsageTableHeader: buffer null.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
for (size_t i = new_table_size; i < sessions_.size(); ++i) {
|
|
if (sessions_[i]) {
|
|
LOGE("ShrinkUsageTableHeader: session open for %zu", i);
|
|
return OEMCrypto_ERROR_ENTRY_IN_USE;
|
|
}
|
|
}
|
|
generation_numbers_.resize(new_table_size);
|
|
sessions_.resize(new_table_size);
|
|
master_generation_number_++;
|
|
return SaveUsageTableHeader(header_buffer, *header_buffer_length);
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::SaveUsageTableHeader(uint8_t* signed_buffer,
|
|
size_t buffer_size) {
|
|
if (!SaveGenerationNumber()) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
size_t count = generation_numbers_.size();
|
|
// buffer_size was determined by calling function.
|
|
if (buffer_size != SignedHeaderSize(count))
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
std::vector<uint8_t> clear_buffer(buffer_size);
|
|
memset(&clear_buffer[0], 0, buffer_size);
|
|
memset(signed_buffer, 0, buffer_size);
|
|
SignedHeaderBlock* clear =
|
|
reinterpret_cast<SignedHeaderBlock*>(&clear_buffer[0]);
|
|
SignedHeaderBlock* encrypted =
|
|
reinterpret_cast<SignedHeaderBlock*>(signed_buffer);
|
|
|
|
// Pack the clear data into the clear buffer.
|
|
memcpy(clear->verification, kHeaderVerification, kMagicLength);
|
|
clear->master_generation = master_generation_number_;
|
|
clear->count = count;
|
|
// This points to the variable size part of the buffer.
|
|
int64_t* stored_generations =
|
|
reinterpret_cast<int64_t*>(&clear_buffer[sizeof(SignedHeaderBlock)]);
|
|
std::copy(generation_numbers_.begin(), generation_numbers_.begin() + count,
|
|
stored_generations);
|
|
|
|
// This should be encrypted and signed with a device specific key.
|
|
// For the reference implementation, I'm just going to use the keybox key.
|
|
const std::vector<uint8_t>& key = ce_->DeviceRootKey();
|
|
if (key.empty()) {
|
|
LOGE("SaveUsageTableHeader: DeviceRootKey is unexpectedly empty.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
// Encrypt the entry.
|
|
if (RAND_bytes(encrypted->iv, wvoec::KEY_IV_SIZE) != 1) {
|
|
LOGE("SaveUsageHeader: Could not generate iv entry.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; // working iv buffer.
|
|
memcpy(iv_buffer, encrypted->iv, wvoec::KEY_IV_SIZE);
|
|
AES_KEY aes_key;
|
|
AES_set_encrypt_key(&key[0], 128, &aes_key);
|
|
AES_cbc_encrypt(
|
|
&clear_buffer[kEncryptionOffset], &signed_buffer[kEncryptionOffset],
|
|
buffer_size - kEncryptionOffset, &aes_key, iv_buffer, AES_ENCRYPT);
|
|
|
|
// Sign the entry.
|
|
unsigned int sig_length = SHA256_DIGEST_LENGTH;
|
|
if (!HMAC(EVP_sha256(), &key[0], key.size(),
|
|
&signed_buffer[SHA256_DIGEST_LENGTH],
|
|
buffer_size - SHA256_DIGEST_LENGTH, encrypted->signature,
|
|
&sig_length)) {
|
|
LOGE("SaveUsageHeader: Could not sign entry.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::LoadUsageTableHeader(
|
|
const std::vector<uint8_t>& buffer) {
|
|
if (!LoadGenerationNumber(false)) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
|
|
if (buffer.size() < SignedHeaderSize(0)) return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
size_t max = ce_->max_usage_table_size();
|
|
if (max > 0 && buffer.size() > SignedHeaderSize(max)) {
|
|
LOGE("Header too big: %zu bytes/%zu bytes", buffer.size(),
|
|
SignedHeaderSize(max));
|
|
return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES;
|
|
}
|
|
std::vector<uint8_t> clear_buffer(buffer.size());
|
|
SignedHeaderBlock* clear =
|
|
reinterpret_cast<SignedHeaderBlock*>(&clear_buffer[0]);
|
|
const SignedHeaderBlock* encrypted =
|
|
reinterpret_cast<const SignedHeaderBlock*>(&buffer[0]);
|
|
|
|
// This should be encrypted and signed with a device specific key.
|
|
// For the reference implementation, I'm just going to use the keybox key.
|
|
const std::vector<uint8_t>& key = ce_->DeviceRootKey();
|
|
if (key.empty()) {
|
|
LOGE("LoadUsageTableHeader: DeviceRootKey is unexpectedly empty.");
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
// Verify the signature of the usage entry. Sign encrypted into clear buffer.
|
|
unsigned int sig_length = SHA256_DIGEST_LENGTH;
|
|
if (!HMAC(EVP_sha256(), &key[0], key.size(), &buffer[SHA256_DIGEST_LENGTH],
|
|
buffer.size() - SHA256_DIGEST_LENGTH, clear->signature,
|
|
&sig_length)) {
|
|
LOGE("LoadUsageTableHeader: Could not sign entry.");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (CRYPTO_memcmp(clear->signature, encrypted->signature,
|
|
SHA256_DIGEST_LENGTH)) {
|
|
LOGE("LoadUsageTableHeader: Signature did not match.");
|
|
LOGE("LoadUsageTableHeader: Invalid signature given: %s",
|
|
wvcdm::HexEncode(encrypted->signature, sig_length).c_str());
|
|
LOGE("LoadUsageTableHeader: Invalid signature computed: %s",
|
|
wvcdm::HexEncode(clear->signature, sig_length).c_str());
|
|
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
|
}
|
|
|
|
// Next, decrypt the entry.
|
|
uint8_t iv_buffer[wvoec::KEY_IV_SIZE];
|
|
memcpy(iv_buffer, encrypted->iv, wvoec::KEY_IV_SIZE);
|
|
AES_KEY aes_key;
|
|
AES_set_decrypt_key(&key[0], 128, &aes_key);
|
|
AES_cbc_encrypt(&buffer[kEncryptionOffset], &clear_buffer[kEncryptionOffset],
|
|
buffer.size() - kEncryptionOffset, &aes_key, iv_buffer,
|
|
AES_DECRYPT);
|
|
|
|
// Check the verification string is correct.
|
|
if (memcmp(kHeaderVerification, clear->verification, kMagicLength)) {
|
|
LOGE("LoadUsageTableHeader: Invalid magic: %s=%8.8s expected: %s=%8.8s",
|
|
wvcdm::HexEncode(clear->verification, kMagicLength).c_str(),
|
|
clear->verification,
|
|
wvcdm::HexEncode(reinterpret_cast<const uint8_t*>(kHeaderVerification),
|
|
kMagicLength)
|
|
.c_str(),
|
|
reinterpret_cast<const uint8_t*>(kHeaderVerification));
|
|
return OEMCrypto_ERROR_BAD_MAGIC;
|
|
}
|
|
|
|
// Check that size is correct, now that we know what it should be.
|
|
if (buffer.size() < SignedHeaderSize(clear->count)) {
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
if (buffer.size() > SignedHeaderSize(clear->count)) {
|
|
LOGW("LoadUsageTableHeader: buffer is large. %zu > %zu", buffer.size(),
|
|
SignedHeaderSize(clear->count));
|
|
}
|
|
|
|
OEMCryptoResult status = OEMCrypto_SUCCESS;
|
|
if (clear->master_generation != master_generation_number_) {
|
|
LOGE("Generation SKEW: %ld -> %ld", clear->master_generation,
|
|
master_generation_number_);
|
|
if ((clear->master_generation + 1 == master_generation_number_) ||
|
|
(clear->master_generation - 1 == master_generation_number_)) {
|
|
status = OEMCrypto_WARNING_GENERATION_SKEW;
|
|
} else {
|
|
return OEMCrypto_ERROR_GENERATION_SKEW;
|
|
}
|
|
}
|
|
int64_t* stored_generations =
|
|
reinterpret_cast<int64_t*>(&clear_buffer[0] + sizeof(SignedHeaderBlock));
|
|
generation_numbers_.assign(stored_generations,
|
|
stored_generations + clear->count);
|
|
sessions_.clear();
|
|
sessions_.resize(clear->count);
|
|
header_loaded_ = true;
|
|
return status;
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::MoveEntry(UsageTableEntry* entry,
|
|
uint32_t new_index) {
|
|
if (new_index >= generation_numbers_.size()) {
|
|
LOGE("MoveEntry: index beyond end of usage table %u >= %zu", new_index,
|
|
generation_numbers_.size());
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (sessions_[new_index]) {
|
|
LOGE("MoveEntry: session open for %u", new_index);
|
|
return OEMCrypto_ERROR_ENTRY_IN_USE;
|
|
}
|
|
if (!entry) {
|
|
LOGE("MoveEntry: null entry");
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
sessions_[new_index] = sessions_[entry->index()];
|
|
sessions_[entry->index()] = nullptr;
|
|
|
|
entry->set_index(new_index);
|
|
generation_numbers_[new_index] = master_generation_number_;
|
|
entry->set_generation_number(master_generation_number_);
|
|
master_generation_number_++;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
void UsageTable::IncrementGeneration() {
|
|
master_generation_number_++;
|
|
SaveGenerationNumber();
|
|
}
|
|
|
|
bool UsageTable::SaveGenerationNumber() {
|
|
wvcdm::FileSystem* file_system = ce_->file_system();
|
|
std::string path;
|
|
// Note: this path is OK for a real implementation, but using security level 1
|
|
// would be better.
|
|
// TODO(jfore, rfrias): Address how this property is presented to the ref.
|
|
// For now, the path is empty.
|
|
/*if (!Properties::GetDeviceFilesBasePath(kSecurityLevelL3,
|
|
&path)) {
|
|
LOGE("UsageTable: Unable to get base path");
|
|
return false;
|
|
}*/
|
|
// On a real implementation, you should NOT put the generation number in
|
|
// a file in user space. It should be stored in secure memory.
|
|
std::string filename = path + "GenerationNumber.dat";
|
|
auto file = file_system->Open(
|
|
filename, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate);
|
|
if (!file) {
|
|
LOGE("UsageTable: File open failed: %s", path.c_str());
|
|
return false;
|
|
}
|
|
file->Write(reinterpret_cast<char*>(&master_generation_number_),
|
|
sizeof(int64_t));
|
|
return true;
|
|
}
|
|
|
|
bool UsageTable::LoadGenerationNumber(bool or_make_new_one) {
|
|
wvcdm::FileSystem* file_system = ce_->file_system();
|
|
std::string path;
|
|
// Note: this path is OK for a real implementation, but using security level 1
|
|
// would be better.
|
|
// TODO(jfore, rfrias): Address how this property is presented to the ref.
|
|
// For now, the path is empty.
|
|
/*if (!Properties::GetDeviceFilesBasePath(kSecurityLevelL3,
|
|
&path)) {
|
|
LOGE("UsageTable: Unable to get base path");
|
|
return false;
|
|
}*/
|
|
// On a real implementation, you should NOT put the generation number in
|
|
// a file in user space. It should be stored in secure memory.
|
|
std::string filename = path + "GenerationNumber.dat";
|
|
auto file = file_system->Open(filename, wvcdm::FileSystem::kReadOnly);
|
|
if (!file) {
|
|
if (or_make_new_one) {
|
|
return RAND_bytes(reinterpret_cast<uint8_t*>(&master_generation_number_),
|
|
sizeof(int64_t)) == 1;
|
|
}
|
|
LOGE("UsageTable: File open failed: %s (clearing table)", path.c_str());
|
|
master_generation_number_ = 0;
|
|
return false;
|
|
}
|
|
file->Read(reinterpret_cast<char*>(&master_generation_number_),
|
|
sizeof(int64_t));
|
|
return true;
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::CreateUsageTableHeader(
|
|
uint8_t* header_buffer, size_t* header_buffer_length) {
|
|
size_t signed_header_size = SignedHeaderSize(0);
|
|
if (*header_buffer_length < signed_header_size) {
|
|
*header_buffer_length = signed_header_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
*header_buffer_length = signed_header_size;
|
|
if (!LoadGenerationNumber(true)) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
// Make sure there are no entries that are currently tied to an open session.
|
|
for (size_t i = 0; i < sessions_.size(); ++i) {
|
|
if (sessions_[i] != nullptr) {
|
|
LOGE("CreateUsageTableHeader: index %zu used by session.", i);
|
|
return OEMCrypto_ERROR_INVALID_SESSION;
|
|
}
|
|
}
|
|
sessions_.clear();
|
|
generation_numbers_.clear();
|
|
header_loaded_ = true;
|
|
return SaveUsageTableHeader(header_buffer, *header_buffer_length);
|
|
}
|
|
|
|
} // namespace wvoec_ref
|