These are a set of CLs merged from the wv cdm repo to the android repo. * Enable Cast for Android Things build. Author: Thoren Paulson <thoren@google.com> [ Merge of http://go/wvgerrit/29941 ] Added a path to make_cast_libwvlevel3 for Android Things. Added the new system id to the preprocessor guards in android_keybox.cpp. Guarded the references to stderr in page_allocator.cpp because for some reason they don't get resolved when we link against the resulting library. BUG: 63443584 * Resolve memory leaks in use of OpenSSL. Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/32700 ] Use of EVP_CIPHER_CTX requires a call to EVP_CIPHER_CTX_cleanup(). * Memory leak in OpenSSL RSA key handling. Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/32621 ] This fixes a range of tests. --gtest_filter="CdmDecrypt*" runs five tests and still loses 5 objects totalling 1320 bytes (down from 6200 bytes). * Unit test and mock OEMCrypto memory leaks. Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/32640 ] More memory leak cleanup. All remaining leaks are due to calls to CRYPTO_malloc() without the matching free (i.e., calls into openssl). * Clean up memory leaks in tests. Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/32600 ] This is the first pass at cleaning up memory leaks. These leaks were affecting a lot of tests, making it hard to identify more serious leaks. Switch to unique_ptr<> pointers for CdmEngine in generic_crypto_unittest tests for FileSystem object in mock OEMCrypto's CryptoEngine object. * Fix broken tests - linux-only & address sanitizer failures. Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/32460 ] Fix broken test: WvCdmEnginePreProvTestStaging.ServiceCertificateInitialNoneTest Fix failures found by address sanitizer: DeviceFilesUsageInfoTest.RetrieveByProviderSessionToken DeviceFilesUsageInfoTest.UpdateUsageInfo NOTE: address sanitizer cannot handle EXPECT_CALL macros containing a call with a Contains matcher as an argument, e.g.: EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0), Gt(certificate.size() + wrapped_private_key.size()))) The address sanitizer reports a crash, issues a report, and stops. A temporary fix is to replace the "Contains()" argument with "_". * Usage license handling corrections Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/28540 ] Validate that offline licenses that do not contain a provider session token are not handled by the TEE. BUG: 38490468 Test: WV Unit/integration tests, GtsMediaTestCases, WvCdmRequestLicenseTest.ReleaseRetryL3OfflineKeySessionUsageDisabledTest * UsageTableEntry::CopyOldUsageEntry memcpy read out of range. Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/32220 ] The function copies the pst from a variable length input vector into a 256 byte character array. But the length argument was a fixed value - MAC_KEY_SIZE. Depending on the actual PST length this can lead to memcpy reading out of bounds or the PST getting truncated. BUG: 71650075 Test: Not currently passing. Will be addressed in a subsequent commit in the chain. Change-Id: I81a4593d7d04d0ef6069ce48d0601b6fbdd85de9
241 lines
8.4 KiB
C++
241 lines
8.4 KiB
C++
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
//
|
|
// Mock implementation of OEMCrypto APIs
|
|
//
|
|
// This is from the v12 version of oemcrypto usage tables. It is used for
|
|
// devices that upgrade from v12 to v13 in the field, and need to convert from
|
|
// the old type of usage table to the new.
|
|
#include "oemcrypto_old_usage_table_mock.h"
|
|
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <openssl/aes.h>
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/sha.h>
|
|
|
|
#include "file_store.h"
|
|
#include "log.h"
|
|
#include "oemcrypto_engine_mock.h"
|
|
#include "oemcrypto_logging.h"
|
|
#include "properties.h"
|
|
#include "pst_report.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
|
|
namespace wvoec_mock {
|
|
|
|
OldUsageTableEntry::OldUsageTableEntry(const std::vector<uint8_t> &pst_hash)
|
|
: pst_hash_(pst_hash),
|
|
time_of_license_received_(time(NULL)),
|
|
time_of_first_decrypt_(0),
|
|
time_of_last_decrypt_(0),
|
|
status_(kUnused) {}
|
|
|
|
OldUsageTableEntry::~OldUsageTableEntry() {}
|
|
|
|
OldUsageTableEntry::OldUsageTableEntry(const OldStoredUsageEntry *buffer) {
|
|
pst_hash_.assign(buffer->pst_hash, buffer->pst_hash + SHA256_DIGEST_LENGTH);
|
|
time_of_license_received_ = buffer->time_of_license_received;
|
|
time_of_first_decrypt_ = buffer->time_of_first_decrypt;
|
|
time_of_last_decrypt_ = buffer->time_of_last_decrypt;
|
|
status_ = buffer->status;
|
|
mac_key_server_.assign(buffer->mac_key_server,
|
|
buffer->mac_key_server + wvcdm::MAC_KEY_SIZE);
|
|
mac_key_client_.assign(buffer->mac_key_client,
|
|
buffer->mac_key_client + wvcdm::MAC_KEY_SIZE);
|
|
}
|
|
|
|
OldUsageTable::OldUsageTable(CryptoEngine *ce) {
|
|
ce_ = ce;
|
|
generation_ = 0;
|
|
table_.clear();
|
|
|
|
// Load saved table.
|
|
wvcdm::FileSystem* file_system = ce->file_system();
|
|
wvcdm::File *file;
|
|
std::string path;
|
|
// Note: this path is OK for a real implementation, but using security level 1
|
|
// would be better.
|
|
if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3,
|
|
&path)) {
|
|
LOGE("OldUsageTable: Unable to get base path");
|
|
return;
|
|
}
|
|
std::string filename = path + "UsageTable.dat";
|
|
if (!file_system->Exists(filename)) {
|
|
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
|
|
LOGI("OldUsageTable: No saved usage table. Creating new table.");
|
|
}
|
|
return;
|
|
}
|
|
|
|
size_t file_size = file_system->FileSize(filename);
|
|
std::vector<uint8_t> encrypted_buffer(file_size);
|
|
std::vector<uint8_t> buffer(file_size);
|
|
OldStoredUsageTable *stored_table =
|
|
reinterpret_cast<OldStoredUsageTable *>(&buffer[0]);
|
|
OldStoredUsageTable *encrypted_table =
|
|
reinterpret_cast<OldStoredUsageTable *>(&encrypted_buffer[0]);
|
|
|
|
file = file_system->Open(filename, wvcdm::FileSystem::kReadOnly);
|
|
if (!file) {
|
|
LOGE("OldUsageTable: File open failed: %s", path.c_str());
|
|
return;
|
|
}
|
|
file->Read(reinterpret_cast<char *>(&encrypted_buffer[0]), file_size);
|
|
file->Close();
|
|
|
|
// Verify the signature of the usage table file.
|
|
|
|
// 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 bool override_to_real = true;
|
|
const std::vector<uint8_t> &key = ce_->DeviceRootKey(override_to_real);
|
|
|
|
uint8_t computed_signature[SHA256_DIGEST_LENGTH];
|
|
unsigned int sig_length = sizeof(computed_signature);
|
|
if (!HMAC(EVP_sha256(), &key[0], key.size(),
|
|
&encrypted_buffer[SHA256_DIGEST_LENGTH],
|
|
file_size - SHA256_DIGEST_LENGTH, computed_signature,
|
|
&sig_length)) {
|
|
LOGE("OldUsageTable: Could not recreate signature.");
|
|
table_.clear();
|
|
return;
|
|
}
|
|
if (memcmp(encrypted_table->signature, computed_signature, sig_length)) {
|
|
LOGE("OldUsageTable: Invalid signature given: %s",
|
|
wvcdm::HexEncode(&encrypted_buffer[0], sig_length).c_str());
|
|
LOGE("OldUsageTable: Invalid signature computed: %s",
|
|
wvcdm::HexEncode(computed_signature, sig_length).c_str());
|
|
table_.clear();
|
|
return;
|
|
}
|
|
|
|
// Next, decrypt the table.
|
|
uint8_t iv_buffer[wvcdm::KEY_IV_SIZE];
|
|
memcpy(iv_buffer, encrypted_table->iv, wvcdm::KEY_IV_SIZE);
|
|
AES_KEY aes_key;
|
|
AES_set_decrypt_key(&key[0], 128, &aes_key);
|
|
AES_cbc_encrypt(&encrypted_buffer[SHA256_DIGEST_LENGTH + wvcdm::KEY_IV_SIZE],
|
|
&buffer[SHA256_DIGEST_LENGTH + wvcdm::KEY_IV_SIZE],
|
|
file_size - SHA256_DIGEST_LENGTH - wvcdm::KEY_IV_SIZE,
|
|
&aes_key, iv_buffer, AES_DECRYPT);
|
|
|
|
// Next, read the generation number from a different location.
|
|
// On a real implementation, you should NOT put the generation number in
|
|
// a file in user space. It should be stored in secure memory. For the
|
|
// reference implementation, we'll just pretend this is secure.
|
|
std::string filename2 = path + "GenerationNumber.dat";
|
|
file = file_system->Open(filename2, wvcdm::FileSystem::kReadOnly);
|
|
if (!file) {
|
|
LOGE("OldUsageTable: File open failed: %s (clearing table)", path.c_str());
|
|
generation_ = 0;
|
|
table_.clear();
|
|
return;
|
|
}
|
|
file->Read(reinterpret_cast<char *>(&generation_), sizeof(int64_t));
|
|
file->Close();
|
|
if (stored_table->generation == generation_ + 1) {
|
|
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
|
|
LOGW("OldUsageTable: File is one generation old. Acceptable rollback.");
|
|
}
|
|
} else if (stored_table->generation == generation_ - 1) {
|
|
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
|
|
LOGW("OldUsageTable: File is one generation new. Acceptable rollback.");
|
|
}
|
|
// This might happen if the generation number was rolled back?
|
|
} else if (stored_table->generation != generation_) {
|
|
LOGE("OldUsageTable: Rollback detected. Clearing Usage Table. %lx -> %lx",
|
|
generation_, stored_table->generation);
|
|
table_.clear();
|
|
generation_ = 0;
|
|
return;
|
|
}
|
|
|
|
// At this point, the stored table looks valid. We can load in all the
|
|
// entries.
|
|
for (uint64_t i = 0; i < stored_table->count; i++) {
|
|
OldUsageTableEntry *entry =
|
|
new OldUsageTableEntry(&stored_table->entries[i].entry);
|
|
table_[entry->pst_hash()] = entry;
|
|
}
|
|
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
|
|
LOGI("OldUsageTable: loaded %d entries.", stored_table->count);
|
|
}
|
|
}
|
|
|
|
OldUsageTableEntry *OldUsageTable::FindEntry(const std::vector<uint8_t> &pst) {
|
|
wvcdm::AutoLock lock(lock_);
|
|
return FindEntryLocked(pst);
|
|
}
|
|
|
|
OldUsageTableEntry *OldUsageTable::FindEntryLocked(const std::vector<uint8_t> &pst) {
|
|
std::vector<uint8_t> pst_hash;
|
|
if (!ComputeHash(pst, pst_hash)) {
|
|
LOGE("OldUsageTable: Could not compute hash of pst.");
|
|
return NULL;
|
|
}
|
|
EntryMap::iterator it = table_.find(pst_hash);
|
|
if (it == table_.end()) {
|
|
return NULL;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
OldUsageTableEntry *OldUsageTable::CreateEntry(const std::vector<uint8_t> &pst) {
|
|
std::vector<uint8_t> pst_hash;
|
|
if (!ComputeHash(pst, pst_hash)) {
|
|
LOGE("OldUsageTable: Could not compute hash of pst.");
|
|
return NULL;
|
|
}
|
|
OldUsageTableEntry *entry = new OldUsageTableEntry(pst_hash);
|
|
wvcdm::AutoLock lock(lock_);
|
|
table_[pst_hash] = entry;
|
|
return entry;
|
|
}
|
|
|
|
void OldUsageTable::Clear() {
|
|
wvcdm::AutoLock lock(lock_);
|
|
for (EntryMap::iterator i = table_.begin(); i != table_.end(); ++i) {
|
|
if (i->second) delete i->second;
|
|
}
|
|
table_.clear();
|
|
}
|
|
|
|
void OldUsageTable::DeleteFile(CryptoEngine *ce) {
|
|
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.
|
|
if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3,
|
|
&path)) {
|
|
LOGE("OldUsageTable: Unable to get base path");
|
|
return;
|
|
}
|
|
std::string filename = path + "UsageTable.dat";
|
|
if (file_system->Exists(filename)) {
|
|
if (!file_system->Remove(filename)) {
|
|
LOGE("DeleteOldUsageTable: error removing file.");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool OldUsageTable::ComputeHash(const std::vector<uint8_t> &pst,
|
|
std::vector<uint8_t> &pst_hash) {
|
|
// The PST is not fixed size, and we have no promises that it is reasonbly
|
|
// sized, so we compute a hash of it, and store that instead.
|
|
pst_hash.resize(SHA256_DIGEST_LENGTH);
|
|
SHA256_CTX context;
|
|
if (!SHA256_Init(&context)) return false;
|
|
if (!SHA256_Update(&context, &pst[0], pst.size())) return false;
|
|
if (!SHA256_Final(&pst_hash[0], &context)) return false;
|
|
return true;
|
|
}
|
|
|
|
} // namespace wvoec_mock
|