These are a set of CLs merged from the wv cdm repo to the android repo. * Correct error logging Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/40000 ] In tests, we set the cipher list to avoid using insecure ciphers when connecting to the provisioning/license service. The result of setting the cipher list was being incorrectly validated. Bug: 64847919 * Move mips cache headers to clear_cache_function.h Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39700 ] Since the clear_cache function has been moved away from the dynamic adapter, we need these conditional includes to be migrated as well for MIPS. * Comment out Level 3 debug call until merge Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39761 ] This call was introduced in go/wvgerrit/34260/. Since the haystack tool in google3 still needs this merge, this should be commented out so the tool can still build until the merge has finished. * Add logging for MAC keys to mock Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39740 ] Bug: 70637842 * Move external interfaces into level3.h + refactor Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39673 ] As part of b/70523618, this CL moves interfaces that partners are responsible for in Level 3 to level3.h so they can be visible as part of the CDM release process. It also cleans up some of the names of the files and adds documentation. * Corrected close session logging level Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/39676 ] Bug: 69460963 * Remove Security Level Path Backward Compatibility Support Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/39505 ] From the android K release onwards certificates were stored in security level specific directories. If upgrading from previous releases persistent information needed to be moved to those directories. Since no device is likely to upgrade from J to Pi, comptibility support can be removed. Bug: 70160032 * Rename privacy_crypto_openssl To privacy_crypto_boringssl Author: John W. Bruce <juce@google.com> [ Merge of http://go/wvgerrit/37122 ] Now that we no longer support OpenSSL in the Shared Source CDM, the name of this file can be updated. Bug: 67907873 Test: build.py x86-64 Test: wv_ce_cdm_unittest Test: jenkins/linux_unit_tests * Remove Conditional Compilation from OpenSSL/BoringSSL Author: John W. Bruce <juce@google.com> [ Merge of http://go/wvgerrit/39460 ] This change removes the usages of conditional compilation to support both BoringSSL and OpenSSL, as well as to support multiple versions of the OpenSSL API. All code is now compiled against one of the two versions of BoringSSL in third_party/. Note that in some cases, the kit/ and legacy_kit/ versions of BoringSSL had different APIs, so when removing the OpenSSL version compatibility conditional compilation, sometimes the older branch was kept and sometimes the newer branch was kept. Bug: 67907873 Test: build.py x86-64 Test: wv_ce_cdm_unittest Test: jenkins/linux_unit_tests * Build CE & Jenkins CDMs With BoringSSL from third_party/ Author: John W. Bruce <juce@google.com> [ Merge of http://go/wvgerrit/37120 ] Up until now, integrators have been responsible for providing a compatible crypto library for use by the CE CDM. (either OpenSSL or BoringSSL) After this change, this decision will no longer be in their hands. The CE CDM build will always use the copy of BoringSSL in third_party/, which will be statically linked with our library with hidden visibility. This allows us to better control what crypto library we use and will prevent continuing problems with trying to support both OpenSSL and BoringSSL. Unfortunately, BoringSSL began using C++11 in mid-2017, and we can't support C++11 right now. Until we can, we need to use a C++11-free version of BoringSSL for libssl. The CDM itself will continue to use a recent BoringSSL, as it only needs libcrypto. But the unit tests that need libssl have to use the legacy version. Bug: 67907873 Test: build.py x86-64 Test: wv_ce_cdm_unittest Test: jenkins/linux_unit_tests * Modified RNG for Level3 to use more entropy Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39220 ] Bug: 65165076 Modified seed generation to use an xor of clock_gettime and client-implemented code to supply random seeds to the RNG. Modified the RNG as well to use xoroshiro128+ instead of xorshift, since it uses more than one seed/state (which are 64-bit) and has higher "statistical quality". The default implementations for the seed generation use /dev/urandom. * Configure base path for Level3FileSystem Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/39506 ] This is in response to b/70354006. This change makes the Android Level3FileSystem use the existing properties method GetDevicesFilesBasePath for binderization. The same is done for the Linux implementation. * Add legacy_kit/ to BoringSSL Directory Author: John W. Bruce <juce@google.com> [ Merge of http://go/wvgerrit/38861 ] This adds a second copy of BoringSSL to the third_party/boringssl/ directory. This second copy is pinned to the last revision of BoringSSL not to require C++11 and is not updated by the UPDATE_BORINGSSL.sh script. This second copy will be used to provide libssl to the tests on devices that do not support C++11. Once we support C++11 in the CDM again, this weight should be removed and all targets should use the copy of BoringSSL in the kit/ directory. Bug: 67907873 * Use Shared Libraries for Unit Tests Author: John W. Bruce <juce@google.com> [ Merge of http://go/wvgerrit/38860 ] Some unit tests were using a statically-linked CDM instead of a dynamically-linked one. (Or, in one case, trying to link both ways into the same binary.) For now, we need to only link dynamically, so that the unit tests and the CDM can use different versions of BoringSSL. Long-term, we would like to test both kinds of linkage. (See b/69548115 for that.) Some unit tests were also using a dynamicaly-linked CDM that was named such that it appeared to be statically-linked. This patch renames some targets to make the linkage clearer. Bug: 67907873 * Change CDM_Backwards_Compatiblity_Tests to dedicated brances Author: Fred Gylys-Colwell <fredgc@google.com> [ Merge of http://go/wvgerrit/39003 ] The build scripts used by CDM_Backwards_Compatiblity_Tests now pull old versions of oemcrypto from the dedicated branches oemcrypto-v*, which [will eventually] contain old oemcrypto versions, that build with the current build system with a current boringssl version. bug: 67907873 * Fix spacing on level3 header Author: Srujan Gaddam <srujzs@google.com> [ Merge of http://go/wvgerrit/38760 ] * Correct Query status calls Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/38640 ] Bug: 70160032 * Refactoring to allow encryption of client ID Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/37460 ] The code has been restructured to allow encryption of client identification in provisioning requests. This will be enabled when server side changes have been made (b/69427217). * Additional information is included in the Client Identification portion of the provisioning request. * Client identification will be encrypted with a service certificate provided by the app/client. Platform changes to enable passing this to core are needed. If a service certificate is not provided, a default one associated with the production Keysmith will be used. * Switched APIs in CdmEngine to take a service certificate for provisioning rather than licensing. Service certificates for licensing are session based and passed as properties from platform code. Bug: 30737060 * Allow some CDM errors to be reported from multiple locations Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/38360 ] This creates some CdmResponseType errors which may be reused PARAMETER_NULL, NOT_INITIALIZED_ERROR, REINIT_ERROR. I have made changes to a few classes to report these errors. Will work on additional classes in a separate CL. Bug: 69864404 BUG: 71650075 Test: WV Unit/integration tests Change-Id: Icc048770d424ac537d11ff327cda2cb142da802d
753 lines
29 KiB
C++
753 lines
29 KiB
C++
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
//
|
|
// Mock implementation of OEMCrypto APIs
|
|
//
|
|
#include "oemcrypto_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 "oemcrypto_old_usage_table_mock.h"
|
|
#include "properties.h"
|
|
#include "pst_report.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
|
|
namespace wvoec_mock {
|
|
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) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
memcpy(data_.pst, pst, pst_length);
|
|
data_.time_of_license_received = time(NULL);
|
|
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) return false;
|
|
return 0 == memcmp(pst, data_.pst, pst_length);
|
|
}
|
|
|
|
bool UsageTableEntry::VerifyMacKeys(const std::vector<uint8_t>& server,
|
|
const std::vector<uint8_t>& client) {
|
|
return (server.size() == wvcdm::MAC_KEY_SIZE) &&
|
|
(client.size() == wvcdm::MAC_KEY_SIZE) &&
|
|
(0 == memcmp(&server[0], data_.mac_key_server, wvcdm::MAC_KEY_SIZE)) &&
|
|
(0 == memcmp(&client[0], data_.mac_key_client, wvcdm::MAC_KEY_SIZE));
|
|
}
|
|
|
|
bool UsageTableEntry::SetMacKeys(const std::vector<uint8_t>& server,
|
|
const std::vector<uint8_t>& client) {
|
|
if ((server.size() != wvcdm::MAC_KEY_SIZE) ||
|
|
(client.size() != wvcdm::MAC_KEY_SIZE))
|
|
return false;
|
|
memcpy(data_.mac_key_server, &server[0], wvcdm::MAC_KEY_SIZE);
|
|
memcpy(data_.mac_key_client, &client[0], wvcdm::MAC_KEY_SIZE);
|
|
return true;
|
|
}
|
|
|
|
bool UsageTableEntry::CheckForUse() {
|
|
if (Inactive()) return false;
|
|
recent_decrypt_ = true;
|
|
if (data_.status == kUnused) {
|
|
data_.status = kActive;
|
|
data_.time_of_first_decrypt = time(NULL);
|
|
data_.generation_number++;
|
|
usage_table_->IncrementGeneration();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void UsageTableEntry::Deactivate(const std::vector<uint8_t>& pst) {
|
|
if (data_.status == kUnused) {
|
|
data_.status = kInactiveUnused;
|
|
} else if (data_.status == kActive) {
|
|
data_.status = kInactiveUsed;
|
|
}
|
|
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 = %d, should be %d.",
|
|
pst.size(), data_.pst_length);
|
|
return OEMCrypto_ERROR_WRONG_PST;
|
|
}
|
|
if (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;
|
|
}
|
|
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);
|
|
int64_t now = time(NULL);
|
|
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 (LogCategoryEnabled(kLoggingDumpDerivedKeys)) {
|
|
std::vector<uint8_t> mac_key_client(
|
|
data_.mac_key_client,
|
|
data_.mac_key_client + wvcdm::MAC_KEY_SIZE * sizeof(uint8_t));
|
|
LOGI(("message signed with HMAC and data_.mac_key_client, "
|
|
"mac_key_client = " +
|
|
wvcdm::b2a_hex(mac_key_client)).c_str());
|
|
}
|
|
if (!HMAC(EVP_sha1(), data_.mac_key_client, wvcdm::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() {
|
|
if (recent_decrypt_) {
|
|
data_.time_of_last_decrypt = time(NULL);
|
|
recent_decrypt_ = false;
|
|
}
|
|
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 = this->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 bool override_to_real = true;
|
|
const std::vector<uint8_t>& key = ce->DeviceRootKey(override_to_real);
|
|
|
|
// Encrypt the entry.
|
|
RAND_bytes(encrypted->iv, wvcdm::KEY_IV_SIZE);
|
|
uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; // working iv buffer.
|
|
memcpy(iv_buffer, encrypted->iv, wvcdm::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) {
|
|
if (buffer.size() < SignedEntrySize()) return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
if (buffer.size() > SignedEntrySize())
|
|
LOGW("LoadUsageTableEntry: buffer is large. %d > %d", 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 bool override_to_real = true;
|
|
const std::vector<uint8_t>& key = ce->DeviceRootKey(override_to_real);
|
|
|
|
// 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 (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[wvcdm::KEY_IV_SIZE];
|
|
memcpy(iv_buffer, encrypted->iv, wvcdm::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 %d, not %d", 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;
|
|
}
|
|
this->data_ = clear->data;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult UsageTableEntry::CopyOldUsageEntry(
|
|
const std::vector<uint8_t>& pst) {
|
|
OldUsageTableEntry* old_entry = usage_table_->FindOldUsageEntry(pst);
|
|
if (!old_entry) return OEMCrypto_ERROR_WRONG_PST;
|
|
data_.time_of_license_received = old_entry->time_of_license_received_;
|
|
data_.time_of_first_decrypt = old_entry->time_of_first_decrypt_;
|
|
data_.time_of_last_decrypt = old_entry->time_of_last_decrypt_;
|
|
data_.status = old_entry->status_;
|
|
if (old_entry->mac_key_server_.size() != wvcdm::MAC_KEY_SIZE) {
|
|
LOGE("CopyOldEntry: Old entry has bad server mac key.");
|
|
} else {
|
|
memcpy(data_.mac_key_server, &(old_entry->mac_key_server_[0]),
|
|
wvcdm::MAC_KEY_SIZE);
|
|
}
|
|
if (old_entry->mac_key_client_.size() != wvcdm::MAC_KEY_SIZE) {
|
|
LOGE("CopyOldEntry: Old entry has bad client mac key.");
|
|
} else {
|
|
memcpy(data_.mac_key_client, &(old_entry->mac_key_client_[0]),
|
|
wvcdm::MAC_KEY_SIZE);
|
|
if (LogCategoryEnabled(kLoggingDumpDerivedKeys)) {
|
|
std::vector<uint8_t> mac_key_client(
|
|
data_.mac_key_client,
|
|
data_.mac_key_client + wvcdm::MAC_KEY_SIZE * sizeof(uint8_t));
|
|
LOGI(("data_.mac_key_client has changed to = " +
|
|
wvcdm::b2a_hex(mac_key_client)).c_str());
|
|
}
|
|
}
|
|
if (pst.size() > kMaxPSTLength) {
|
|
LOGE("CopyOldEntry: PST Length was too large. Truncating.");
|
|
data_.pst_length = kMaxPSTLength;
|
|
} else {
|
|
data_.pst_length = pst.size();
|
|
}
|
|
memcpy(data_.pst, &pst[0], data_.pst_length);
|
|
data_.pst[data_.pst_length] = '\0';
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
|
|
size_t UsageTableEntry::SignedEntrySize() {
|
|
size_t base = sizeof(SignedEntryBlock);
|
|
// round up to make even number of blocks:
|
|
size_t blocks = (base - 1) / wvcdm::KEY_IV_SIZE + 1;
|
|
return blocks * wvcdm::KEY_IV_SIZE;
|
|
}
|
|
|
|
UsageTable::~UsageTable() {
|
|
if (old_table_) {
|
|
delete old_table_;
|
|
old_table_ = NULL;
|
|
}
|
|
}
|
|
|
|
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) / wvcdm::KEY_IV_SIZE + 1;
|
|
return blocks * wvcdm::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) {
|
|
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();
|
|
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;
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::CreateNewUsageEntry(SessionContext* session,
|
|
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;
|
|
uint32_t index = generation_numbers_.size();
|
|
UsageTableEntry* new_entry =
|
|
new UsageTableEntry(this, index, master_generation_number_);
|
|
generation_numbers_.push_back(master_generation_number_);
|
|
sessions_.push_back(session);
|
|
master_generation_number_++;
|
|
*entry = new_entry;
|
|
*usage_entry_number = index;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::LoadUsageEntry(SessionContext* session,
|
|
UsageTableEntry** entry,
|
|
uint32_t index,
|
|
const std::vector<uint8_t>& buffer) {
|
|
if (!header_loaded_) {
|
|
LOGE("CreateNewUsageEntry: 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 %d used by other session.", index);
|
|
return OEMCrypto_ERROR_INVALID_SESSION;
|
|
}
|
|
UsageTableEntry* new_entry =
|
|
new UsageTableEntry(this, index, master_generation_number_);
|
|
|
|
OEMCryptoResult status = new_entry->LoadData(ce_, index, buffer);
|
|
if (status != OEMCrypto_SUCCESS) {
|
|
delete new_entry;
|
|
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])) {
|
|
delete new_entry;
|
|
return OEMCrypto_ERROR_GENERATION_SKEW;
|
|
}
|
|
status = OEMCrypto_WARNING_GENERATION_SKEW;
|
|
}
|
|
sessions_[index] = session;
|
|
*entry = 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: %d > %zd.", 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 %d", 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 bool override_to_real = true;
|
|
const std::vector<uint8_t>& key = ce_->DeviceRootKey(override_to_real);
|
|
|
|
// Encrypt the entry.
|
|
RAND_bytes(encrypted->iv, wvcdm::KEY_IV_SIZE);
|
|
uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; // working iv buffer.
|
|
memcpy(iv_buffer, encrypted->iv, wvcdm::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;
|
|
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 bool override_to_real = true;
|
|
const std::vector<uint8_t>& key = ce_->DeviceRootKey(override_to_real);
|
|
|
|
// 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 (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[wvcdm::KEY_IV_SIZE];
|
|
memcpy(iv_buffer, encrypted->iv, wvcdm::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. %d > %d", 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_)) {
|
|
return OEMCrypto_ERROR_GENERATION_SKEW;
|
|
}
|
|
status = OEMCrypto_WARNING_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 %d >= %d", new_index,
|
|
generation_numbers_.size());
|
|
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
|
}
|
|
if (sessions_[new_index]) {
|
|
LOGE("MoveEntry: session open for %d", 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()] = 0;
|
|
|
|
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.
|
|
if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::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";
|
|
wvcdm::File* 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));
|
|
file->Close();
|
|
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.
|
|
if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::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";
|
|
wvcdm::File* file = file_system->Open(filename,
|
|
wvcdm::FileSystem::kReadOnly);
|
|
if (!file) {
|
|
if (or_make_new_one) {
|
|
RAND_bytes(reinterpret_cast<uint8_t*>(&master_generation_number_),
|
|
sizeof(int64_t));
|
|
return true;
|
|
}
|
|
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));
|
|
file->Close();
|
|
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] != NULL) {
|
|
LOGE("CreateUsageTableHeader: index %d used by session.", i);
|
|
return OEMCrypto_ERROR_INVALID_SESSION;
|
|
}
|
|
}
|
|
sessions_.clear();
|
|
generation_numbers_.clear();
|
|
header_loaded_ = true;
|
|
return SaveUsageTableHeader(header_buffer, *header_buffer_length);
|
|
}
|
|
|
|
OldUsageTableEntry* UsageTable::FindOldUsageEntry(
|
|
const std::vector<uint8_t>& pst) {
|
|
if (!old_table_) old_table_ = new OldUsageTable(ce_);
|
|
return old_table_->FindEntry(pst);
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::DeleteOldUsageTable() {
|
|
if (old_table_) {
|
|
old_table_->Clear();
|
|
delete old_table_;
|
|
old_table_ = NULL;
|
|
}
|
|
OldUsageTable::DeleteFile(ce_);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult UsageTable::CreateOldUsageEntry(
|
|
uint64_t time_since_license_received, uint64_t time_since_first_decrypt,
|
|
uint64_t time_since_last_decrypt, OEMCrypto_Usage_Entry_Status status,
|
|
uint8_t* server_mac_key, uint8_t* client_mac_key, const uint8_t* pst,
|
|
size_t pst_length) {
|
|
if (!old_table_) old_table_ = new OldUsageTable(ce_);
|
|
std::vector<uint8_t> pstv(pst, pst+pst_length);
|
|
OldUsageTableEntry *old_entry = old_table_->CreateEntry(pstv);
|
|
|
|
int64_t now = time(NULL);
|
|
old_entry->time_of_license_received_ = now - time_since_license_received;
|
|
old_entry->time_of_first_decrypt_ = now - time_since_first_decrypt;
|
|
old_entry->time_of_last_decrypt_ = now - time_since_last_decrypt;
|
|
old_entry->status_ = status;
|
|
old_entry->mac_key_server_.assign(server_mac_key,
|
|
server_mac_key + wvcdm::MAC_KEY_SIZE);
|
|
old_entry->mac_key_client_.assign(client_mac_key,
|
|
client_mac_key + wvcdm::MAC_KEY_SIZE);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
} // namespace wvoec_mock
|