Reference Code for Big Usage Tables
Merge from widevine of http://go/wvgerrit/23283 This CL adds some big usage table functionality to the oemcrypto mock and unit tests. Still missing are: backwards compatibility, defragging the table, haystack code, and lots of new unit tests. The haystack now reports it doesn't support usage tables, so that the unit tests will pass. This will be fixed in a future CL. b/31458046 b/32554171 b/34173776 b/34174907 Change-Id: I6e08e76f7612ffb77e413151e00f830339298c62
This commit is contained in:
@@ -4,6 +4,7 @@ include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES:= \
|
||||
src/keys.cpp \
|
||||
src/oemcrypto_auth_mock.cpp \
|
||||
src/oemcrypto_engine_device_properties.cpp \
|
||||
src/oemcrypto_engine_mock.cpp \
|
||||
src/oemcrypto_key_mock.cpp \
|
||||
@@ -11,6 +12,7 @@ LOCAL_SRC_FILES:= \
|
||||
src/oemcrypto_keybox_testkey.cpp \
|
||||
src/oemcrypto_logging.cpp \
|
||||
src/oemcrypto_mock.cpp \
|
||||
src/oemcrypto_rsa_key_shared.cpp \
|
||||
src/oemcrypto_usage_table_mock.cpp \
|
||||
src/wvcrc.cpp \
|
||||
|
||||
|
||||
@@ -90,7 +90,10 @@ void SessionKeyTable::UpdateDuration(const KeyControlBlock& control) {
|
||||
}
|
||||
|
||||
SessionContext::~SessionContext() {
|
||||
if (usage_entry_) usage_entry_->set_session(NULL);
|
||||
if (usage_entry_) {
|
||||
delete usage_entry_;
|
||||
usage_entry_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal utility function to derive key using CMAC-128
|
||||
@@ -346,56 +349,68 @@ bool SessionContext::ValidateMessage(const uint8_t* given_message,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionContext::CheckNonceOrEntry(const KeyControlBlock& key_control_block,
|
||||
const std::vector<uint8_t>& pst) {
|
||||
OEMCryptoResult SessionContext::CheckStatusOnline(uint32_t nonce,
|
||||
uint32_t control) {
|
||||
if (!(control & kControlNonceEnabled)) {
|
||||
LOGE("LoadKeys: Server provided Nonce_Required but Nonce_Enabled = 0.");
|
||||
// Server error. Continue, and assume nonce required.
|
||||
}
|
||||
if (!CheckNonce(nonce)) return OEMCrypto_ERROR_INVALID_NONCE;
|
||||
switch (usage_entry_status_) {
|
||||
case kNoUsageEntry:
|
||||
LOGE("LoadKeys: Session did not create usage entry.");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
case kUsageEntryLoaded:
|
||||
LOGE("LoadKeys: Session reloaded existing entry.");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
case kUsageEntryNew:
|
||||
return OEMCrypto_SUCCESS;
|
||||
default: // invalid status.
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::CheckStatusOffline(uint32_t nonce,
|
||||
uint32_t control) {
|
||||
if (control & kControlNonceEnabled) {
|
||||
LOGE("KCB: Server provided NonceOrEntry but Nonce_Enabled = 1.");
|
||||
// Server error. Continue, and assume nonce required.
|
||||
}
|
||||
switch (usage_entry_status_) {
|
||||
case kNoUsageEntry:
|
||||
LOGE("LoadKeys: Session did not create or load usage entry.");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
case kUsageEntryLoaded:
|
||||
// Repeat load. Calling function will verify pst and keys.
|
||||
return OEMCrypto_SUCCESS;
|
||||
case kUsageEntryNew:
|
||||
// First load. Verify nonce.
|
||||
if (!CheckNonce(nonce)) return OEMCrypto_ERROR_INVALID_NONCE;
|
||||
return OEMCrypto_SUCCESS;
|
||||
default: // invalid status.
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::CheckNonceOrEntry(
|
||||
const KeyControlBlock& key_control_block) {
|
||||
switch (key_control_block.control_bits() & kControlReplayMask) {
|
||||
case kControlNonceRequired: // Online license. Nonce always required.
|
||||
if (pst.size() == 0) {
|
||||
LOGE("KCB: PST null for kControlNonceRequired.");
|
||||
return false;
|
||||
}
|
||||
if (!(key_control_block.control_bits() & kControlNonceEnabled)) {
|
||||
LOGE("KCB: Server provided Nonce_Required but Nonce_Enabled = 0.");
|
||||
// Server error. Continue, and assume nonce required.
|
||||
}
|
||||
if (!CheckNonce(key_control_block.nonce())) return false;
|
||||
if (!usage_entry_) {
|
||||
if (ce_->usage_table()->FindEntry(pst)) {
|
||||
LOGE("KCB: Cannot create duplicate entries in usage table.");
|
||||
return false;
|
||||
}
|
||||
usage_entry_ = ce_->usage_table()->CreateEntry(pst, this);
|
||||
}
|
||||
return CheckStatusOnline(key_control_block.nonce(),
|
||||
key_control_block.control_bits());
|
||||
break;
|
||||
case kControlNonceOrEntry: // Offline license. Nonce required on first use.
|
||||
return CheckStatusOffline(key_control_block.nonce(),
|
||||
key_control_block.control_bits());
|
||||
break;
|
||||
case kControlNonceOrEntry: // Offline license. Nonce required on first use.
|
||||
if (key_control_block.control_bits() & kControlNonceEnabled) {
|
||||
LOGE("KCB: Server provided NonceOrEntry but Nonce_Enabled = 1.");
|
||||
// Server error. Continue, and assume nonce required.
|
||||
}
|
||||
if (pst.size() == 0) {
|
||||
LOGE("KCB: PST null for kControlNonceOrEntry.");
|
||||
return false;
|
||||
}
|
||||
if (!usage_entry_) {
|
||||
usage_entry_ = ce_->usage_table()->FindEntry(pst);
|
||||
if (usage_entry_) {
|
||||
if (usage_entry_->inactive()) return false;
|
||||
} else {
|
||||
if (!CheckNonce(key_control_block.nonce())) return false;
|
||||
usage_entry_ = ce_->usage_table()->CreateEntry(pst, this);
|
||||
}
|
||||
} else {
|
||||
if (usage_entry_->inactive()) return false;
|
||||
}
|
||||
break; // Usage table not required. Look at nonce enabled bit.
|
||||
default:
|
||||
if ((key_control_block.control_bits() & kControlNonceEnabled) &&
|
||||
(!CheckNonce(key_control_block.nonce()))) {
|
||||
LOGE("KCB: BAD Nonce");
|
||||
return false;
|
||||
LOGE("LoadKeys: BAD Nonce");
|
||||
return OEMCrypto_ERROR_INVALID_NONCE;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
void SessionContext::StartTimer() { timer_start_ = time(NULL); }
|
||||
@@ -420,14 +435,12 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
|
||||
// Decrypt and install keys in key object
|
||||
// Each key will have a key control block. They will all have the same nonce.
|
||||
bool status = true;
|
||||
OEMCryptoResult status = OEMCrypto_SUCCESS;
|
||||
std::vector<uint8_t> key_id;
|
||||
std::vector<uint8_t> enc_key_data;
|
||||
std::vector<uint8_t> key_data_iv;
|
||||
std::vector<uint8_t> key_control;
|
||||
std::vector<uint8_t> key_control_iv;
|
||||
std::vector<uint8_t> pstv;
|
||||
if (pst_length > 0) pstv.assign(pst, pst + pst_length);
|
||||
for (unsigned int i = 0; i < num_keys; i++) {
|
||||
key_id.assign(key_array[i].key_id,
|
||||
key_array[i].key_id + key_array[i].key_id_length);
|
||||
@@ -436,7 +449,7 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
key_data_iv.assign(key_array[i].key_data_iv,
|
||||
key_array[i].key_data_iv + wvcdm::KEY_IV_SIZE);
|
||||
if (key_array[i].key_control == NULL) {
|
||||
status = false;
|
||||
status = OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
break;
|
||||
}
|
||||
key_control.assign(key_array[i].key_control,
|
||||
@@ -444,15 +457,16 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
key_control_iv.assign(key_array[i].key_control_iv,
|
||||
key_array[i].key_control_iv + wvcdm::KEY_IV_SIZE);
|
||||
|
||||
if (!InstallKey(key_id, enc_key_data, key_data_iv, key_control,
|
||||
key_control_iv, pstv,
|
||||
key_array[i].cipher_mode == OEMCrypto_CipherMode_CTR)) {
|
||||
status = false;
|
||||
OEMCryptoResult result = InstallKey(
|
||||
key_id, enc_key_data, key_data_iv, key_control, key_control_iv,
|
||||
key_array[i].cipher_mode == OEMCrypto_CipherMode_CTR);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
status = result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
FlushNonces();
|
||||
if (!status) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
if (status != OEMCrypto_SUCCESS) return status;
|
||||
|
||||
// enc_mac_key can be NULL if license renewal is not supported
|
||||
if (enc_mac_keys != NULL) {
|
||||
@@ -467,68 +481,88 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
}
|
||||
if (pst_length > 0) {
|
||||
if (!usage_entry_) {
|
||||
LOGE("Usage table entry not found.\n");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!usage_entry_->VerifyOrSetMacKeys(mac_key_server_, mac_key_client_)) {
|
||||
LOGE("Usage table entry does not match.\n");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
if (usage_entry_) {
|
||||
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
||||
switch (usage_entry_status_) {
|
||||
case kNoUsageEntry:
|
||||
if (pst_length > 0) {
|
||||
LOGE("LoadKeys: PST specified but no usage entry loaded.");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
break; // no extra check.
|
||||
case kUsageEntryNew:
|
||||
result = usage_entry_->SetPST(pst, pst_length);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
if (!usage_entry_->SetMacKeys(mac_key_server_, mac_key_client_)) {
|
||||
LOGE("LoadKeys: Usage table can't set keys.\n");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
break;
|
||||
case kUsageEntryLoaded:
|
||||
if (!usage_entry_->VerifyPST(pst, pst_length)) {
|
||||
return OEMCrypto_ERROR_WRONG_PST;
|
||||
}
|
||||
if (!usage_entry_->VerifyMacKeys(mac_key_server_, mac_key_client_)) {
|
||||
LOGE("LoadKeys: Usage table entry does not match.\n");
|
||||
return OEMCrypto_ERROR_WRONG_KEYS;
|
||||
}
|
||||
if (usage_entry_->Inactive()) return OEMCrypto_ERROR_LICENSE_INACTIVE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
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,
|
||||
const std::vector<uint8_t>& pst,
|
||||
bool ctr_mode) {
|
||||
OEMCryptoResult 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, bool ctr_mode) {
|
||||
// Decrypt encrypted key_data using derived encryption key and offered iv
|
||||
std::vector<uint8_t> content_key;
|
||||
std::vector<uint8_t> key_control_str;
|
||||
|
||||
if (!DecryptMessage(encryption_key_, key_data_iv, key_data, &content_key)) {
|
||||
LOGE("[Installkey(): Could not decrypt key data]");
|
||||
return false;
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
if (LogCategoryEnabled(kLoggingDumpContentKeys)) {
|
||||
LOGI((" InstallKey: key_id = " + wvcdm::b2a_hex(key_id)).c_str());
|
||||
LOGI(
|
||||
(" InstallKey: content_key = " + wvcdm::b2a_hex(content_key)).c_str());
|
||||
LOGI((" InstallKey: key_control = " + wvcdm::b2a_hex(key_control_str))
|
||||
.c_str());
|
||||
LOGI((" InstallKey: key_id = " +
|
||||
wvcdm::b2a_hex(key_id)).c_str());
|
||||
LOGI((" InstallKey: content_key = " +
|
||||
wvcdm::b2a_hex(content_key)).c_str());
|
||||
LOGI((" InstallKey: key_control = " +
|
||||
wvcdm::b2a_hex(key_control_str)).c_str());
|
||||
}
|
||||
|
||||
// Key control must be supplied by license server
|
||||
if (key_control.empty()) {
|
||||
LOGE("[Installkey(): WARNING: No Key Control]");
|
||||
return false;
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if (key_control_iv.empty()) {
|
||||
LOGE("[Installkey(): ERROR: No Key Control IV]");
|
||||
return false;
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if (!DecryptMessage(content_key, key_control_iv, key_control,
|
||||
&key_control_str)) {
|
||||
LOGE("[Installkey(): ERROR: Could not decrypt content key]");
|
||||
return false;
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
KeyControlBlock key_control_block(key_control_str);
|
||||
if (!key_control_block.valid()) {
|
||||
LOGE("Error parsing key control.");
|
||||
return false;
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if ((key_control_block.control_bits() &
|
||||
kControlRequireAntiRollbackHardware) &&
|
||||
!ce_->config_is_anti_rollback_hw_present()) {
|
||||
LOGE("Anti-rollback hardware is required but hardware not present.");
|
||||
return false;
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
uint8_t minimum_patch_level =
|
||||
(key_control_block.control_bits() & kControlSecurityPatchLevelMask) >>
|
||||
@@ -536,17 +570,18 @@ bool SessionContext::InstallKey(const KeyId& key_id,
|
||||
if (minimum_patch_level > OEMCrypto_Security_Patch_Level()) {
|
||||
LOGE("[InstallKey(): security patch level: %d. Minimum:%d]",
|
||||
OEMCrypto_Security_Patch_Level(), minimum_patch_level);
|
||||
return false;
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
if (!CheckNonceOrEntry(key_control_block, pst)) {
|
||||
LOGE("Failed Nonce/PST check.");
|
||||
return false;
|
||||
OEMCryptoResult result = CheckNonceOrEntry(key_control_block);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("LoadKeys: Failed Nonce/PST check.");
|
||||
return result;
|
||||
}
|
||||
|
||||
Key key(content_key, key_control_block, ctr_mode);
|
||||
session_keys_.Insert(key_id, key);
|
||||
return true;
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
bool SessionContext::InstallRSAEncryptedKey(
|
||||
@@ -691,7 +726,7 @@ bool SessionContext::LoadRSAKey(const uint8_t* pkcs8_rsa_key,
|
||||
return rsa_key_.LoadPkcs8RsaKey(pkcs8_rsa_key, rsa_key_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::AllowKeyUse(const std::string& log_string,
|
||||
OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string,
|
||||
uint32_t use_type,
|
||||
OEMCryptoBufferType buffer_type) {
|
||||
const KeyControlBlock& control = current_content_key()->control();
|
||||
@@ -707,7 +742,7 @@ OEMCryptoResult SessionContext::AllowKeyUse(const std::string& log_string,
|
||||
}
|
||||
}
|
||||
if (control.control_bits() & kControlReplayMask) {
|
||||
if (!IsUsageEntryValid()) {
|
||||
if (!CheckUsageEntry()) {
|
||||
LOGE("[%s(): usage entry not valid]", log_string.c_str());
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
@@ -762,7 +797,7 @@ OEMCryptoResult SessionContext::Generic_Encrypt(const uint8_t* in_buffer,
|
||||
LOGE("[Generic_Encrypt(): CONTENT_KEY has wrong size: %d", key.size());
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
OEMCryptoResult result = AllowKeyUse("Generic_Encrypt", kControlAllowEncrypt,
|
||||
OEMCryptoResult result = CheckKeyUse("Generic_Encrypt", kControlAllowEncrypt,
|
||||
OEMCrypto_BufferType_Clear);
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) {
|
||||
@@ -802,7 +837,7 @@ OEMCryptoResult SessionContext::Generic_Decrypt(const uint8_t* in_buffer,
|
||||
LOGE("[Generic_Decrypt(): CONTENT_KEY has wrong size.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
OEMCryptoResult result = AllowKeyUse("Generic_Decrypt", kControlAllowDecrypt,
|
||||
OEMCryptoResult result = CheckKeyUse("Generic_Decrypt", kControlAllowDecrypt,
|
||||
OEMCrypto_BufferType_Clear);
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
|
||||
@@ -847,7 +882,7 @@ OEMCryptoResult SessionContext::Generic_Sign(const uint8_t* in_buffer,
|
||||
LOGE("[Generic_Sign(): CONTENT_KEY has wrong size; %d", key.size());
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
OEMCryptoResult result = AllowKeyUse("Generic_Sign", kControlAllowSign,
|
||||
OEMCryptoResult result = CheckKeyUse("Generic_Sign", kControlAllowSign,
|
||||
OEMCrypto_BufferType_Clear);
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
if (algorithm != OEMCrypto_HMAC_SHA256) {
|
||||
@@ -883,7 +918,7 @@ OEMCryptoResult SessionContext::Generic_Verify(const uint8_t* in_buffer,
|
||||
LOGE("[Generic_Verify(): CONTENT_KEY has wrong size: %d", key.size());
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
OEMCryptoResult result = AllowKeyUse("Generic_Verify", kControlAllowVerify,
|
||||
OEMCryptoResult result = CheckKeyUse("Generic_Verify", kControlAllowVerify,
|
||||
OEMCrypto_BufferType_Clear);
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
if (algorithm != OEMCrypto_HMAC_SHA256) {
|
||||
@@ -973,23 +1008,80 @@ bool SessionContext::CheckNonce(uint32_t nonce) {
|
||||
|
||||
void SessionContext::FlushNonces() { nonce_table_.Flush(); }
|
||||
|
||||
bool SessionContext::IsUsageEntryValid() {
|
||||
bool SessionContext::CheckUsageEntry() {
|
||||
if (!usage_entry_) return false;
|
||||
return usage_entry_->UpdateTime();
|
||||
return usage_entry_->CheckForUse();
|
||||
}
|
||||
|
||||
void SessionContext::ReleaseUsageEntry() { usage_entry_ = NULL; }
|
||||
OEMCryptoResult SessionContext::CreateNewUsageEntry(
|
||||
uint32_t* usage_entry_number) {
|
||||
OEMCryptoResult result = ce_->usage_table().CreateNewUsageEntry(
|
||||
this, &usage_entry_, usage_entry_number);
|
||||
if (usage_entry_) {
|
||||
usage_entry_status_ = kUsageEntryNew;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::LoadUsageEntry(
|
||||
uint32_t index, const std::vector<uint8_t>& buffer) {
|
||||
OEMCryptoResult result =
|
||||
ce_->usage_table().LoadUsageEntry(this, &usage_entry_, index, buffer);
|
||||
if (usage_entry_) {
|
||||
usage_entry_status_ = kUsageEntryLoaded;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::UpdateUsageEntry(uint8_t* header_buffer,
|
||||
size_t* header_buffer_length,
|
||||
uint8_t* entry_buffer,
|
||||
size_t* entry_buffer_length) {
|
||||
if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
return ce_->usage_table().UpdateUsageEntry(this, usage_entry_, header_buffer,
|
||||
header_buffer_length, entry_buffer,
|
||||
entry_buffer_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::DeactivateUsageEntry(
|
||||
const std::vector<uint8_t>& pst) {
|
||||
if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
usage_entry_->Deactivate(pst);
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::ReportUsage(const std::vector<uint8_t>& pst,
|
||||
uint8_t* buffer,
|
||||
size_t* buffer_length) {
|
||||
if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
return usage_entry_->ReportUsage(pst, buffer, buffer_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::MoveEntry(uint32_t new_index) {
|
||||
if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
return ce_->usage_table().MoveEntry(usage_entry_, new_index);
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::CopyOldUsageEntry(
|
||||
const std::vector<uint8_t>& pst) {
|
||||
if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
return ce_->usage_table().CopyOldUsageEntry(usage_entry_, pst);
|
||||
}
|
||||
|
||||
CryptoEngine::CryptoEngine(wvcdm::FileSystem* file_system)
|
||||
: root_of_trust_(config_provisioning_method()),
|
||||
file_system_(file_system),
|
||||
usage_table_(new UsageTable(this)) {
|
||||
usage_table_(this, file_system) {
|
||||
ERR_load_crypto_strings();
|
||||
}
|
||||
|
||||
CryptoEngine::~CryptoEngine() {
|
||||
wvcdm::AutoLock lock(session_table_lock_);
|
||||
ActiveSessions::iterator it;
|
||||
for (it = sessions_.begin(); it != sessions_.end(); ++it) {
|
||||
delete it->second;
|
||||
}
|
||||
sessions_.clear();
|
||||
if (usage_table_) delete usage_table_;
|
||||
}
|
||||
|
||||
void CryptoEngine::Terminate() {}
|
||||
@@ -1066,7 +1158,7 @@ OEMCryptoResult SessionContext::DecryptCENC(
|
||||
return OEMCrypto_ERROR_DECRYPT_FAILED;
|
||||
}
|
||||
|
||||
OEMCryptoResult result = AllowKeyUse("DecryptCENC", 0, buffer_type);
|
||||
OEMCryptoResult result = CheckKeyUse("DecryptCENC", 0, buffer_type);
|
||||
if (result != OEMCrypto_SUCCESS) return result;
|
||||
|
||||
const std::vector<uint8_t>& content_key = current_content_key()->value();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "oemcrypto_auth_mock.h"
|
||||
#include "oemcrypto_key_mock.h"
|
||||
#include "oemcrypto_rsa_key_shared.h"
|
||||
#include "oemcrypto_usage_table_mock.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvoec_mock {
|
||||
@@ -86,7 +87,8 @@ class SessionContext {
|
||||
current_content_key_(NULL),
|
||||
rsa_key_(rsa_key),
|
||||
allowed_schemes_(kSign_RSASSA_PSS),
|
||||
usage_entry_(NULL) {}
|
||||
usage_entry_(NULL),
|
||||
usage_entry_status_(kNoUsageEntry) {}
|
||||
~SessionContext();
|
||||
|
||||
bool isValid() { return valid_; }
|
||||
@@ -137,11 +139,12 @@ class SessionContext {
|
||||
const uint8_t* enc_mac_keys, size_t num_keys,
|
||||
const OEMCrypto_KeyObject* key_array,
|
||||
const uint8_t* pst, size_t pst_length);
|
||||
bool 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,
|
||||
const std::vector<uint8_t>& pst, bool ctr_mode);
|
||||
OEMCryptoResult 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,
|
||||
bool ctr_mode);
|
||||
bool InstallRSAEncryptedKey(const uint8_t* encrypted_message_key,
|
||||
size_t encrypted_message_key_length);
|
||||
bool DecryptRSAKey(const uint8_t* enc_rsa_key, size_t enc_rsa_key_length,
|
||||
@@ -175,7 +178,19 @@ class SessionContext {
|
||||
void AddNonce(uint32_t nonce);
|
||||
bool CheckNonce(uint32_t nonce);
|
||||
void FlushNonces();
|
||||
void ReleaseUsageEntry();
|
||||
|
||||
OEMCryptoResult CreateNewUsageEntry(uint32_t* usage_entry_number);
|
||||
OEMCryptoResult LoadUsageEntry(uint32_t index,
|
||||
const std::vector<uint8_t>& buffer);
|
||||
OEMCryptoResult UpdateUsageEntry(uint8_t* header_buffer,
|
||||
size_t* header_buffer_length,
|
||||
uint8_t* entry_buffer,
|
||||
size_t* entry_buffer_length);
|
||||
OEMCryptoResult DeactivateUsageEntry(const std::vector<uint8_t>& pst);
|
||||
OEMCryptoResult ReportUsage(const std::vector<uint8_t>& pst, uint8_t* buffer,
|
||||
size_t* buffer_length);
|
||||
OEMCryptoResult MoveEntry(uint32_t new_index);
|
||||
OEMCryptoResult CopyOldUsageEntry(const std::vector<uint8_t>& pst);
|
||||
|
||||
private:
|
||||
bool DeriveKey(const std::vector<uint8_t>& key,
|
||||
@@ -185,9 +200,16 @@ class SessionContext {
|
||||
const std::vector<uint8_t>& iv,
|
||||
const std::vector<uint8_t>& message,
|
||||
std::vector<uint8_t>* decrypted);
|
||||
bool CheckNonceOrEntry(const KeyControlBlock& key_control_block,
|
||||
const std::vector<uint8_t>& pst);
|
||||
bool IsUsageEntryValid();
|
||||
// Either verify the nonce or usage entry, as required by the key control
|
||||
// block.
|
||||
OEMCryptoResult CheckNonceOrEntry(const KeyControlBlock& key_control_block);
|
||||
// If there is a usage entry, check that it is not inactive.
|
||||
// It also updates the status of the entry if needed.
|
||||
bool CheckUsageEntry();
|
||||
// Check that the usage entry status is valid for online use.
|
||||
OEMCryptoResult CheckStatusOnline(uint32_t nonce, uint32_t control);
|
||||
// Check that the usage entry status is valid for offline use.
|
||||
OEMCryptoResult CheckStatusOffline(uint32_t nonce, uint32_t control);
|
||||
OEMCryptoResult DecryptCBC(const uint8_t* key, const uint8_t* iv,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern,
|
||||
const uint8_t* cipher_data,
|
||||
@@ -200,7 +222,9 @@ class SessionContext {
|
||||
OEMCryptoResult DecryptCTR(const uint8_t* key_u8, const uint8_t* iv,
|
||||
size_t block_offset, const uint8_t* cipher_data,
|
||||
size_t cipher_data_length, uint8_t* clear_data);
|
||||
OEMCryptoResult AllowKeyUse(const std::string& log_string, uint32_t use_type,
|
||||
// Checks if the key is allowed for the specified type. If there is a usage
|
||||
// entry, it also checks the usage entry.
|
||||
OEMCryptoResult CheckKeyUse(const std::string& log_string, uint32_t use_type,
|
||||
OEMCryptoBufferType buffer_type);
|
||||
RSA* rsa_key() { return rsa_key_.get(); }
|
||||
|
||||
@@ -219,6 +243,12 @@ class SessionContext {
|
||||
time_t timer_start_;
|
||||
UsageTableEntry* usage_entry_;
|
||||
|
||||
enum UsageEntryStatus {
|
||||
kNoUsageEntry, // No entry loaded for this session.
|
||||
kUsageEntryNew, // After entry was created.
|
||||
kUsageEntryLoaded, // After loading entry or loading keys.
|
||||
};
|
||||
UsageEntryStatus usage_entry_status_;
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(SessionContext);
|
||||
};
|
||||
|
||||
@@ -275,7 +305,7 @@ class CryptoEngine {
|
||||
OEMCrypto_HDCP_Capability config_current_hdcp_capability();
|
||||
OEMCrypto_HDCP_Capability config_maximum_hdcp_capability();
|
||||
|
||||
UsageTable* usage_table() { return usage_table_; }
|
||||
UsageTable& usage_table() { return usage_table_; }
|
||||
wvcdm::FileSystem* file_system() { return file_system_; }
|
||||
|
||||
bool config_local_display_only();
|
||||
@@ -295,7 +325,7 @@ class CryptoEngine {
|
||||
AuthenticationRoot root_of_trust_;
|
||||
wvcdm::Lock session_table_lock_;
|
||||
wvcdm::FileSystem* file_system_;
|
||||
UsageTable* usage_table_;
|
||||
UsageTable usage_table_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoEngine);
|
||||
};
|
||||
|
||||
@@ -45,7 +45,14 @@ extern "C" OEMCryptoResult OEMCrypto_Initialize(void) {
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("------------------------- OEMCrypto_Initialize(void)\n");
|
||||
}
|
||||
|
||||
if (crypto_engine) {
|
||||
LOGE("------------------------- Calling Initialize without Terminate\n");
|
||||
if (crypto_engine->Initialized()) {
|
||||
crypto_engine->Terminate();
|
||||
}
|
||||
delete crypto_engine;
|
||||
crypto_engine = NULL;
|
||||
}
|
||||
// NOTE: This requires a compatible Filesystem implementation.
|
||||
wvcdm::FileSystem* fs = new wvcdm::FileSystem();
|
||||
crypto_engine = new CryptoEngine(fs);
|
||||
@@ -1400,6 +1407,13 @@ extern "C" bool OEMCrypto_IsAntiRollbackHwPresent() {
|
||||
}
|
||||
|
||||
extern "C" uint32_t OEMCrypto_SupportedCertificates() {
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_GetProvisioningMethod: OEMCrypto Not Initialized.");
|
||||
return 0;
|
||||
}
|
||||
if (crypto_engine->config_provisioning_method() == OEMCrypto_DrmCertificate) {
|
||||
return 0;
|
||||
}
|
||||
return OEMCrypto_Supports_RSA_2048bit | OEMCrypto_Supports_RSA_3072bit |
|
||||
OEMCrypto_Supports_RSA_CAST;
|
||||
if (!crypto_engine) {
|
||||
@@ -1575,14 +1589,7 @@ extern "C" OEMCryptoResult OEMCrypto_UpdateUsageTable() {
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_UpdateUsageTable();\n");
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_UpdateUsageTable: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
return crypto_engine->usage_table()->UpdateTable();
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_DeactivateUsageEntry(
|
||||
@@ -1600,8 +1607,13 @@ extern "C" OEMCryptoResult OEMCrypto_DeactivateUsageEntry(
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_DeactivateUsageEntry(): ERROR_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
std::vector<uint8_t> pstv(pst, pst + pst_length);
|
||||
return crypto_engine->usage_table()->DeactivateEntry(pstv);
|
||||
return session_ctx->DeactivateUsageEntry(pstv);
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session,
|
||||
@@ -1622,20 +1634,16 @@ extern "C" OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session,
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
if (!buffer_length) {
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_ReportUsage(): ERROR_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
std::vector<uint8_t> pstv(pst, pst + pst_length);
|
||||
UsageTableEntry* entry = crypto_engine->usage_table()->FindEntry(pstv);
|
||||
if (!entry) {
|
||||
LOGE("[OEMCrypto_ReportUsage(): Usage Table Entry not found]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
OEMCryptoResult sts =
|
||||
entry->ReportUsage(session_ctx, pstv, buffer, buffer_length);
|
||||
crypto_engine->usage_table()->UpdateTable();
|
||||
OEMCryptoResult sts = session_ctx->ReportUsage(pstv, buffer, buffer_length);
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
if (wvcdm::g_cutoff >= wvcdm::LOG_VERBOSE) {
|
||||
dump_hex("usage buffer", buffer, *buffer_length);
|
||||
@@ -1648,68 +1656,14 @@ extern "C" OEMCryptoResult OEMCrypto_DeleteUsageEntry(
|
||||
OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length,
|
||||
const uint8_t* message, size_t message_length, const uint8_t* signature,
|
||||
size_t signature_length) {
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_DeleteUsageEntry(\n");
|
||||
if (wvcdm::g_cutoff >= wvcdm::LOG_VERBOSE) {
|
||||
dump_hex("pst", pst, pst_length);
|
||||
dump_hex("message", message, message_length);
|
||||
dump_hex("signature", signature, signature_length);
|
||||
}
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_DeleteUsageEntry: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_DeleteUsageEntry(): ERROR_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
if (message == NULL || message_length == 0 || signature == NULL ||
|
||||
signature_length == 0 || pst == NULL || pst_length == 0) {
|
||||
LOGE("[OEMCrypto_DeleteUsageEntry(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
if (!RangeCheck(message, message_length, pst, pst_length, false)) {
|
||||
LOGE("[OEMCrypto_DeleteUsageEntry(): range check.]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
// Validate message signature
|
||||
if (!session_ctx->ValidateMessage(message, message_length, signature,
|
||||
signature_length)) {
|
||||
LOGE("[OEMCrypto_DeleteUsageEntry(): OEMCrypto_ERROR_SIGNATURE_FAILURE.]");
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
std::vector<uint8_t> pstv(pst, pst + pst_length);
|
||||
if (crypto_engine->usage_table()->DeleteEntry(pstv)) {
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
// TODO(fredgc): delete this.
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry(const uint8_t* pst,
|
||||
size_t pst_length) {
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry()\n");
|
||||
if (wvcdm::g_cutoff >= wvcdm::LOG_VERBOSE) {
|
||||
dump_hex("pst", pst, pst_length);
|
||||
}
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_ForceDeleteUsageEntry: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
std::vector<uint8_t> pstv(pst, pst + pst_length);
|
||||
if (crypto_engine->usage_table()->DeleteEntry(pstv)) {
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
// TODO(fredgc): delete this.
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_DeleteOldUsageTable() {
|
||||
@@ -1723,9 +1677,7 @@ extern "C" OEMCryptoResult OEMCrypto_DeleteOldUsageTable() {
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
crypto_engine->usage_table()->Clear();
|
||||
crypto_engine->usage_table()->UpdateTable();
|
||||
return OEMCrypto_SUCCESS;
|
||||
return crypto_engine->usage_table().DeleteOldUsageTable();
|
||||
}
|
||||
|
||||
extern "C" bool OEMCrypto_IsSRMUpdateSupported() { return false; }
|
||||
@@ -1744,48 +1696,183 @@ extern "C" OEMCryptoResult OEMCrypto_RemoveSRM() {
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_CreateUsageTableHeader(
|
||||
uint8_t* header_buffer, size_t* header_buffer_length) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
uint8_t* header_buffer,
|
||||
size_t* header_buffer_length) {
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_CreateUsageTableHeader()\n");
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_CreateUsageTableHeader: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
LOGE("OEMCrypto_CreateUsageTableHeader: Configured without Usage Tables.");
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
return crypto_engine->usage_table()
|
||||
.CreateUsageTableHeader(header_buffer, header_buffer_length);
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_LoadUsageTableHeader(
|
||||
const uint8_t* buffer, size_t buffer_length) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_LoadUsageTableHeader()\n");
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_LoadUsageTableHeader: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
if (!buffer) {
|
||||
LOGE("OEMCrypto_LoadUsageTableHeader: buffer null.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
std::vector<uint8_t> bufferv(buffer, buffer + buffer_length);
|
||||
return crypto_engine->usage_table().LoadUsageTableHeader(bufferv);
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_CreateNewUsageEntry(
|
||||
OEMCrypto_SESSION session, uint32_t* usage_entry_number) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_CreateNewUsageEntry(\n");
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_CreateNewUsageEntry: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_CreateNewUsageEntry(): ERROR_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
if (!usage_entry_number) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
OEMCryptoResult sts = session_ctx->CreateNewUsageEntry(usage_entry_number);
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- usage_entry_number = %d", *usage_entry_number);
|
||||
}
|
||||
return sts;
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_LoadUsageEntry(OEMCrypto_SESSION session,
|
||||
uint32_t index,
|
||||
const uint8_t* buffer,
|
||||
size_t buffer_size) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_LoadUsageEntry(\n");
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_LoadUsageEntry: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_LoadUsageEntry(): ERROR_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
if (!buffer) {
|
||||
LOGE("[OEMCrypto_LoadUsageEntry(): buffer null]");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
std::vector<uint8_t> bufferv(buffer, buffer + buffer_size);
|
||||
return session_ctx->LoadUsageEntry(index, bufferv);
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_UpdateUsageEntry(
|
||||
OEMCrypto_SESSION session, uint8_t* header_buffer,
|
||||
size_t* header_buffer_length, uint8_t* entry_buffer,
|
||||
size_t* entry_buffer_length) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_UpdateUsageEntry(\n");
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_UpdateUsageEntry: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
if (!header_buffer_length || !entry_buffer_length) {
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_UpdateUsageEntry(): ERROR_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
return session_ctx->UpdateUsageEntry(header_buffer, header_buffer_length,
|
||||
entry_buffer, entry_buffer_length);
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(
|
||||
uint32_t new_table_size, uint8_t* header_buffer,
|
||||
size_t* header_buffer_length) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader()\n");
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_ShrinkUsageTableHeader: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
if (!header_buffer) {
|
||||
LOGE("OEMCrypto_ShrinkUsageTableHeader: buffer null.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
return crypto_engine->usage_table().ShrinkUsageTableHeader(
|
||||
new_table_size, header_buffer, header_buffer_length);
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_MoveEntry(OEMCrypto_SESSION session,
|
||||
uint32_t new_index) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_MoveEntry(\n");
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_MoveEntry: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_MoveEntry(): ERROR_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
return session_ctx->MoveEntry(new_index);
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_CopyOldUsageEntry(
|
||||
OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
|
||||
LOGI("-- OEMCryptoResult OEMCrypto_CopyOldUsageEntry(\n");
|
||||
}
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_CopyOldUsageEntry: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (!crypto_engine->config_supports_usage_table()) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[OEMCrypto_CopyOldUsageEntry(): ERROR_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
std::vector<uint8_t> pstv(pst, pst + pst_length);
|
||||
return session_ctx->CopyOldUsageEntry(pstv);
|
||||
}
|
||||
|
||||
} // namespace wvoec_mock
|
||||
|
||||
@@ -24,83 +24,118 @@
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
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];
|
||||
wvoec_mock::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
|
||||
|
||||
namespace wvoec_mock {
|
||||
|
||||
UsageTableEntry::UsageTableEntry(const std::vector<uint8_t> &pst_hash,
|
||||
SessionContext *ctx)
|
||||
: pst_hash_(pst_hash),
|
||||
time_of_license_received_(time(NULL)),
|
||||
time_of_first_decrypt_(0),
|
||||
time_of_last_decrypt_(0),
|
||||
status_(kUnused),
|
||||
session_(ctx) {}
|
||||
|
||||
UsageTableEntry::~UsageTableEntry() {
|
||||
if (session_) session_->ReleaseUsageEntry();
|
||||
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(const StoredUsageEntry *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);
|
||||
session_ = NULL;
|
||||
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;
|
||||
}
|
||||
|
||||
void UsageTableEntry::SaveToBuffer(StoredUsageEntry *buffer) {
|
||||
if (pst_hash_.size() != sizeof(buffer->pst_hash)) {
|
||||
LOGE("Coding Error: pst hash has wrong size.");
|
||||
return;
|
||||
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();
|
||||
}
|
||||
memcpy(buffer->pst_hash, &pst_hash_[0], pst_hash_.size());
|
||||
buffer->time_of_license_received = time_of_license_received_;
|
||||
buffer->time_of_first_decrypt = time_of_first_decrypt_;
|
||||
buffer->time_of_last_decrypt = time_of_last_decrypt_;
|
||||
buffer->status = status_;
|
||||
memcpy(buffer->mac_key_server, &mac_key_server_[0], wvcdm::MAC_KEY_SIZE);
|
||||
memcpy(buffer->mac_key_client, &mac_key_client_[0], wvcdm::MAC_KEY_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UsageTableEntry::Deactivate() {
|
||||
if (status_ == kUnused) {
|
||||
status_ = kInactiveUnused;
|
||||
} else if (status_ == kActive) {
|
||||
status_ = kInactiveUsed;
|
||||
}
|
||||
if (session_) {
|
||||
session_->ReleaseUsageEntry();
|
||||
session_ = NULL;
|
||||
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_ = false;
|
||||
data_.generation_number++;
|
||||
usage_table_->IncrementGeneration();
|
||||
}
|
||||
|
||||
bool UsageTableEntry::UpdateTime() {
|
||||
int64_t now = time(NULL);
|
||||
switch (status_) {
|
||||
case kUnused:
|
||||
status_ = kActive;
|
||||
time_of_first_decrypt_ = now;
|
||||
time_of_last_decrypt_ = now;
|
||||
return true;
|
||||
case kActive:
|
||||
time_of_last_decrypt_ = now;
|
||||
return true;
|
||||
case kInactive:
|
||||
case kInactiveUsed:
|
||||
case kInactiveUnused:
|
||||
return false;
|
||||
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_INVALID_CONTEXT;
|
||||
}
|
||||
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_INVALID_CONTEXT;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
OEMCryptoResult UsageTableEntry::ReportUsage(SessionContext *session,
|
||||
const std::vector<uint8_t> &pst,
|
||||
uint8_t *buffer,
|
||||
size_t *buffer_length) {
|
||||
size_t length_needed = wvcdm::Unpacked_PST_Report::report_size(pst.size());
|
||||
if (*buffer_length < length_needed) {
|
||||
*buffer_length = length_needed;
|
||||
@@ -112,206 +147,426 @@ OEMCryptoResult UsageTableEntry::ReportUsage(SessionContext *session,
|
||||
}
|
||||
wvcdm::Unpacked_PST_Report pst_report(buffer);
|
||||
int64_t now = time(NULL);
|
||||
pst_report.set_seconds_since_license_received(now - time_of_license_received_);
|
||||
pst_report.set_seconds_since_first_decrypt(now - time_of_first_decrypt_);
|
||||
pst_report.set_seconds_since_last_decrypt(now - time_of_last_decrypt_);
|
||||
pst_report.set_status(status_);
|
||||
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(static_cast<uint8_t>(pst.size()));
|
||||
memcpy(pst_report.pst(), &pst[0], pst.size());
|
||||
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(), &mac_key_client_[0], mac_key_client_.size(),
|
||||
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("UsageTableEntry: could not compute signature.");
|
||||
LOGE("ReportUsage: could not compute signature.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
session->set_mac_key_server(mac_key_server_);
|
||||
session->set_mac_key_client(mac_key_client_);
|
||||
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
bool UsageTableEntry::VerifyOrSetMacKeys(const std::vector<uint8_t> &server,
|
||||
const std::vector<uint8_t> &client) {
|
||||
if (mac_key_server_.size() == 0) { // Not set yet, so set it now.
|
||||
mac_key_server_ = server;
|
||||
mac_key_client_ = client;
|
||||
return true;
|
||||
} else {
|
||||
return (mac_key_server_ == server && mac_key_client_ == client);
|
||||
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;
|
||||
}
|
||||
|
||||
UsageTable::UsageTable(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("UsageTable: Unable to get base path");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string filename = path + "UsageTable.dat";
|
||||
if (!file_system->Exists(filename)) {
|
||||
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
|
||||
LOGI("UsageTable: 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);
|
||||
StoredUsageTable *stored_table =
|
||||
reinterpret_cast<StoredUsageTable *>(&buffer[0]);
|
||||
StoredUsageTable *encrypted_table =
|
||||
reinterpret_cast<StoredUsageTable *>(&encrypted_buffer[0]);
|
||||
|
||||
file = file_system->Open(filename, wvcdm::FileSystem::kReadOnly);
|
||||
if (!file) {
|
||||
LOGE("UsageTable: 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.
|
||||
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);
|
||||
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("UsageTable: Could not recreate signature.");
|
||||
table_.clear();
|
||||
return;
|
||||
}
|
||||
if (memcmp(encrypted_table->signature, computed_signature, sig_length)) {
|
||||
LOGE("UsageTable: Invalid signature given: %s",
|
||||
wvcdm::HexEncode(&encrypted_buffer[0], sig_length).c_str());
|
||||
LOGE("UsageTable: 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("UsageTable: 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("UsageTable: File is one generation old. Acceptable rollback.");
|
||||
}
|
||||
} else if (stored_table->generation == generation_ - 1) {
|
||||
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
|
||||
LOGW("UsageTable: File is one generation new. Acceptable rollback.");
|
||||
}
|
||||
// This might happen if the generation number was rolled back?
|
||||
} else if (stored_table->generation != generation_) {
|
||||
LOGE("UsageTable: 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++) {
|
||||
UsageTableEntry *entry =
|
||||
new UsageTableEntry(&stored_table->entries[i].entry);
|
||||
table_[entry->pst_hash()] = entry;
|
||||
}
|
||||
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
|
||||
LOGI("UsageTable: loaded %d entries.", stored_table->count);
|
||||
}
|
||||
}
|
||||
|
||||
bool UsageTable::SaveToFile() {
|
||||
// This is always called by a locking function.
|
||||
// Update the generation number so we can detect rollback.
|
||||
generation_++;
|
||||
// Now save data to the file as seen in the constructor, above.
|
||||
size_t file_size = sizeof(StoredUsageTable) +
|
||||
table_.size() * sizeof(AlignedStoredUsageEntry);
|
||||
std::vector<uint8_t> buffer(file_size);
|
||||
std::vector<uint8_t> encrypted_buffer(file_size);
|
||||
StoredUsageTable *stored_table =
|
||||
reinterpret_cast<StoredUsageTable *>(&buffer[0]);
|
||||
StoredUsageTable *encrypted_table =
|
||||
reinterpret_cast<StoredUsageTable *>(&encrypted_buffer[0]);
|
||||
stored_table->generation = generation_;
|
||||
stored_table->count = 0;
|
||||
for (EntryMap::iterator i = table_.begin(); i != table_.end(); ++i) {
|
||||
UsageTableEntry *entry = i->second;
|
||||
entry->SaveToBuffer(&stored_table->entries[stored_table->count].entry);
|
||||
stored_table->count++;
|
||||
}
|
||||
|
||||
// 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 table.
|
||||
RAND_bytes(encrypted_table->iv, wvcdm::KEY_IV_SIZE);
|
||||
uint8_t iv_buffer[wvcdm::KEY_IV_SIZE];
|
||||
memcpy(iv_buffer, encrypted_table->iv, wvcdm::KEY_IV_SIZE);
|
||||
// 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(&buffer[SHA256_DIGEST_LENGTH + wvcdm::KEY_IV_SIZE],
|
||||
&encrypted_buffer[SHA256_DIGEST_LENGTH + wvcdm::KEY_IV_SIZE],
|
||||
file_size - SHA256_DIGEST_LENGTH - wvcdm::KEY_IV_SIZE,
|
||||
&aes_key, iv_buffer, AES_ENCRYPT);
|
||||
AES_cbc_encrypt(
|
||||
&clear_buffer[kEncryptionOffset], &signed_buffer[kEncryptionOffset],
|
||||
buffer_size - kEncryptionOffset, &aes_key, iv_buffer, AES_ENCRYPT);
|
||||
|
||||
// Sign the table.
|
||||
unsigned int sig_length = sizeof(stored_table->signature);
|
||||
// Sign the entry.
|
||||
unsigned int sig_length = SHA256_DIGEST_LENGTH;
|
||||
if (!HMAC(EVP_sha256(), &key[0], key.size(),
|
||||
&encrypted_buffer[SHA256_DIGEST_LENGTH],
|
||||
file_size - SHA256_DIGEST_LENGTH, encrypted_table->signature,
|
||||
&signed_buffer[SHA256_DIGEST_LENGTH],
|
||||
buffer_size - SHA256_DIGEST_LENGTH, encrypted->signature,
|
||||
&sig_length)) {
|
||||
LOGE("UsageTable: Could not sign table.");
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
|
||||
wvcdm::FileSystem *file_system = ce_->file_system();
|
||||
wvcdm::File *file;
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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_) 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_) 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())
|
||||
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;
|
||||
for (size_t i = new_table_size; i < sessions_.size(); i++) {
|
||||
if (sessions_[i]) {
|
||||
LOGE("ShrinkUsageTableHeader: session open for %d", i);
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
}
|
||||
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_INVALID_CONTEXT;
|
||||
}
|
||||
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.
|
||||
@@ -320,115 +575,76 @@ bool UsageTable::SaveToFile() {
|
||||
LOGE("UsageTable: Unable to get base path");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string filename = path + "UsageTable.dat";
|
||||
if (!file_system->Exists(filename)) {
|
||||
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
|
||||
LOGI("UsageTable: No saved usage table. Creating new table.");
|
||||
}
|
||||
}
|
||||
|
||||
file = file_system->Open(
|
||||
filename, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate);
|
||||
if (!file) {
|
||||
LOGE("UsageTable: Could not save usage table: %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
file->Write(reinterpret_cast<char *>(&encrypted_buffer[0]), file_size);
|
||||
file->Close();
|
||||
|
||||
// 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 filename2 = path + "GenerationNumber.dat";
|
||||
file = file_system->Open(
|
||||
filename2, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate);
|
||||
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 *>(&generation_), sizeof(int64_t));
|
||||
file->Write(reinterpret_cast<char*>(&master_generation_number_),
|
||||
sizeof(int64_t));
|
||||
file->Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
UsageTableEntry *UsageTable::FindEntry(const std::vector<uint8_t> &pst) {
|
||||
wvcdm::AutoLock lock(lock_);
|
||||
return FindEntryLocked(pst);
|
||||
}
|
||||
|
||||
UsageTableEntry *UsageTable::FindEntryLocked(const std::vector<uint8_t> &pst) {
|
||||
std::vector<uint8_t> pst_hash;
|
||||
if (!ComputeHash(pst, pst_hash)) {
|
||||
LOGE("UsageTable: Could not compute hash of pst.");
|
||||
return NULL;
|
||||
}
|
||||
EntryMap::iterator it = table_.find(pst_hash);
|
||||
if (it == table_.end()) {
|
||||
return NULL;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
UsageTableEntry *UsageTable::CreateEntry(const std::vector<uint8_t> &pst,
|
||||
SessionContext *ctx) {
|
||||
std::vector<uint8_t> pst_hash;
|
||||
if (!ComputeHash(pst, pst_hash)) {
|
||||
LOGE("UsageTable: Could not compute hash of pst.");
|
||||
return NULL;
|
||||
}
|
||||
UsageTableEntry *entry = new UsageTableEntry(pst_hash, ctx);
|
||||
wvcdm::AutoLock lock(lock_);
|
||||
table_[pst_hash] = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
OEMCryptoResult UsageTable::UpdateTable() {
|
||||
wvcdm::AutoLock lock(lock_);
|
||||
if (SaveToFile()) return OEMCrypto_SUCCESS;
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
OEMCryptoResult UsageTable::DeactivateEntry(const std::vector<uint8_t> &pst) {
|
||||
wvcdm::AutoLock lock(lock_);
|
||||
UsageTableEntry *entry = FindEntryLocked(pst);
|
||||
if (!entry) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
entry->Deactivate();
|
||||
if (SaveToFile()) return OEMCrypto_SUCCESS;
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
bool UsageTable::DeleteEntry(const std::vector<uint8_t> &pst) {
|
||||
std::vector<uint8_t> pst_hash;
|
||||
if (!ComputeHash(pst, pst_hash)) {
|
||||
LOGE("UsageTable: Could not compute hash of pst.");
|
||||
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;
|
||||
}
|
||||
wvcdm::AutoLock lock(lock_);
|
||||
EntryMap::iterator it = table_.find(pst_hash);
|
||||
if (it == table_.end()) return false;
|
||||
if (it->second) delete it->second;
|
||||
table_.erase(it);
|
||||
return SaveToFile();
|
||||
}
|
||||
|
||||
void UsageTable::Clear() {
|
||||
wvcdm::AutoLock lock(lock_);
|
||||
for (EntryMap::iterator i = table_.begin(); i != table_.end(); ++i) {
|
||||
if (i->second) delete i->second;
|
||||
// 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));
|
||||
master_generation_number_ = 0; // TODO(fredgc): remove after debugging.
|
||||
return true;
|
||||
}
|
||||
LOGE("UsageTable: File open failed: %s (clearing table)", path.c_str());
|
||||
master_generation_number_ = 0;
|
||||
return false;
|
||||
}
|
||||
table_.clear();
|
||||
file->Read(reinterpret_cast<char*>(&master_generation_number_),
|
||||
sizeof(int64_t));
|
||||
file->Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UsageTable::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;
|
||||
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;
|
||||
sessions_.clear();
|
||||
generation_numbers_.clear();
|
||||
header_loaded_ = true;
|
||||
return SaveUsageTableHeader(header_buffer, *header_buffer_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult UsageTable::CopyOldUsageEntry(UsageTableEntry* entry,
|
||||
const std::vector<uint8_t>& pst) {
|
||||
// TODO(fredgc): add this.
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
OEMCryptoResult UsageTable::DeleteOldUsageTable() {
|
||||
// TODO(fredgc): add this.
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
} // namespace wvoec_mock
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lock.h"
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "file_store.h"
|
||||
#include "lock.h"
|
||||
#include "openssl/sha.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
@@ -19,85 +20,104 @@ namespace wvoec_mock {
|
||||
|
||||
class SessionContext;
|
||||
class CryptoEngine;
|
||||
class UsageTable;
|
||||
|
||||
const size_t kMaxPSTLength = 255;
|
||||
// This is the data we store offline.
|
||||
struct StoredUsageEntry {
|
||||
// To save disk space, we only store a hash of the pst.
|
||||
uint8_t pst_hash[SHA256_DIGEST_LENGTH];
|
||||
int64_t generation_number;
|
||||
int64_t time_of_license_received;
|
||||
int64_t time_of_first_decrypt;
|
||||
int64_t time_of_last_decrypt;
|
||||
enum OEMCrypto_Usage_Entry_Status status;
|
||||
uint8_t mac_key_server[wvcdm::MAC_KEY_SIZE];
|
||||
uint8_t mac_key_client[wvcdm::MAC_KEY_SIZE];
|
||||
};
|
||||
typedef union {
|
||||
struct StoredUsageEntry entry;
|
||||
uint8_t padding[128]; // multiple of block size and bigger than entry size.
|
||||
} AlignedStoredUsageEntry;
|
||||
|
||||
struct StoredUsageTable {
|
||||
uint8_t signature[SHA256_DIGEST_LENGTH];
|
||||
uint8_t iv[wvcdm::KEY_IV_SIZE];
|
||||
int64_t generation;
|
||||
uint64_t count;
|
||||
AlignedStoredUsageEntry entries[];
|
||||
uint32_t index;
|
||||
uint8_t pst[kMaxPSTLength+1]; // add 1 for padding.
|
||||
uint8_t pst_length;
|
||||
};
|
||||
|
||||
class UsageTableEntry {
|
||||
public:
|
||||
UsageTableEntry(const std::vector<uint8_t> &pst_hash, SessionContext *ctx);
|
||||
UsageTableEntry(const StoredUsageEntry *buffer);
|
||||
~UsageTableEntry();
|
||||
void SaveToBuffer(StoredUsageEntry *buffer);
|
||||
OEMCrypto_Usage_Entry_Status status() const { return status_; }
|
||||
bool inactive() const { return status_ >= kInactive; }
|
||||
void Deactivate();
|
||||
bool UpdateTime();
|
||||
OEMCryptoResult ReportUsage(SessionContext *session,
|
||||
const std::vector<uint8_t> &pst,
|
||||
uint8_t *buffer,
|
||||
size_t *buffer_length);
|
||||
// Set them if not set, verify if already set.
|
||||
bool VerifyOrSetMacKeys(const std::vector<uint8_t> &server,
|
||||
const std::vector<uint8_t> &client);
|
||||
const std::vector<uint8_t> &pst_hash() const { return pst_hash_; }
|
||||
void set_session(SessionContext *session) { session_ = session; }
|
||||
UsageTableEntry(UsageTable* table, uint32_t index, int64_t generation);
|
||||
// owner_(owner), session_(session), loaded_(false) {}
|
||||
~UsageTableEntry(); // Free memory, remove reference in header.
|
||||
bool Inactive() { return data_.status >= kInactive; }
|
||||
OEMCryptoResult SetPST(const uint8_t* pst, size_t pst_length);
|
||||
bool VerifyPST(const uint8_t* pst, size_t pst_length);
|
||||
bool VerifyMacKeys(const std::vector<uint8_t>& server,
|
||||
const std::vector<uint8_t>& client);
|
||||
bool SetMacKeys(const std::vector<uint8_t>& server,
|
||||
const std::vector<uint8_t>& client);
|
||||
// Returns false if the entry is inactive. Otherwise, returns true.
|
||||
// If the status was unused, it is updated, and decrypt times are flaged
|
||||
// for update.
|
||||
bool CheckForUse();
|
||||
void Deactivate(const std::vector<uint8_t>& pst);
|
||||
OEMCryptoResult ReportUsage(const std::vector<uint8_t>& pst, uint8_t* buffer,
|
||||
size_t* buffer_length);
|
||||
void UpdateAndIncrement();
|
||||
OEMCryptoResult SaveData(CryptoEngine* ce, SessionContext* session,
|
||||
uint8_t* signed_buffer, size_t buffer_size);
|
||||
OEMCryptoResult LoadData(CryptoEngine* ce, uint32_t index,
|
||||
const std::vector<uint8_t>& buffer);
|
||||
int64_t generation_number() { return data_.generation_number; }
|
||||
void set_generation_number(int64_t value) { data_.generation_number = value; }
|
||||
void set_index(int32_t index) { data_.index = index; }
|
||||
uint32_t index() { return data_.index; }
|
||||
static size_t SignedEntrySize();
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> pst_hash_;
|
||||
int64_t time_of_license_received_;
|
||||
int64_t time_of_first_decrypt_;
|
||||
int64_t time_of_last_decrypt_;
|
||||
enum OEMCrypto_Usage_Entry_Status status_;
|
||||
std::vector<uint8_t> mac_key_server_;
|
||||
std::vector<uint8_t> mac_key_client_;
|
||||
|
||||
SessionContext *session_;
|
||||
UsageTable* usage_table_; // Owner of this object.
|
||||
bool recent_decrypt_;
|
||||
bool forbid_report_;
|
||||
StoredUsageEntry data_;
|
||||
};
|
||||
|
||||
class UsageTable {
|
||||
public:
|
||||
UsageTable(CryptoEngine *ce);
|
||||
~UsageTable() { Clear(); }
|
||||
UsageTableEntry *FindEntry(const std::vector<uint8_t> &pst);
|
||||
UsageTableEntry *CreateEntry(const std::vector<uint8_t> &pst,
|
||||
SessionContext *ctx);
|
||||
OEMCryptoResult UpdateTable();
|
||||
OEMCryptoResult DeactivateEntry(const std::vector<uint8_t> &pst);
|
||||
bool DeleteEntry(const std::vector<uint8_t> &pst);
|
||||
void Clear();
|
||||
UsageTable(CryptoEngine* ce, wvcdm::FileSystem* file_system)
|
||||
: ce_(ce), file_system_(file_system), header_loaded_(false){};
|
||||
|
||||
OEMCryptoResult CreateNewUsageEntry(SessionContext* session,
|
||||
UsageTableEntry** entry,
|
||||
uint32_t* usage_entry_number);
|
||||
OEMCryptoResult LoadUsageEntry(SessionContext* session,
|
||||
UsageTableEntry** entry, uint32_t index,
|
||||
const std::vector<uint8_t>& buffer);
|
||||
OEMCryptoResult UpdateUsageEntry(SessionContext* session,
|
||||
UsageTableEntry* entry,
|
||||
uint8_t* header_buffer,
|
||||
size_t* header_buffer_length,
|
||||
uint8_t* entry_buffer,
|
||||
size_t* entry_buffer_length);
|
||||
OEMCryptoResult MoveEntry(UsageTableEntry* entry, uint32_t new_index);
|
||||
OEMCryptoResult CopyOldUsageEntry(UsageTableEntry* entry,
|
||||
const std::vector<uint8_t>& pst);
|
||||
OEMCryptoResult DeleteOldUsageTable();
|
||||
OEMCryptoResult CreateUsageTableHeader(uint8_t* header_buffer,
|
||||
size_t* header_buffer_length);
|
||||
OEMCryptoResult LoadUsageTableHeader(const std::vector<uint8_t>& buffer);
|
||||
OEMCryptoResult ShrinkUsageTableHeader(uint32_t new_table_size,
|
||||
uint8_t* header_buffer,
|
||||
size_t* header_buffer_length);
|
||||
// Diassociates this entry with it's session.
|
||||
void ReleaseEntry(uint32_t index) { sessions_[index] = 0; }
|
||||
void IncrementGeneration();
|
||||
static size_t SignedHeaderSize(size_t count);
|
||||
|
||||
private:
|
||||
UsageTableEntry *FindEntryLocked(const std::vector<uint8_t> &pst);
|
||||
bool SaveToFile();
|
||||
bool ComputeHash(const std::vector<uint8_t> &pst,
|
||||
std::vector<uint8_t> &pst_hash);
|
||||
OEMCryptoResult SaveUsageTableHeader(uint8_t* signed_buffer,
|
||||
size_t buffer_size);
|
||||
bool SaveGenerationNumber();
|
||||
bool LoadGenerationNumber(bool or_make_new_one);
|
||||
|
||||
typedef std::map<std::vector<uint8_t>, UsageTableEntry *> EntryMap;
|
||||
EntryMap table_;
|
||||
wvcdm::Lock lock_;
|
||||
int64_t generation_;
|
||||
CryptoEngine *ce_;
|
||||
CryptoEngine* ce_;
|
||||
wvcdm::FileSystem* file_system_;
|
||||
bool header_loaded_;
|
||||
int64_t master_generation_number_;
|
||||
std::vector<int64_t> generation_numbers_;
|
||||
std::vector<SessionContext*> sessions_;
|
||||
};
|
||||
|
||||
} // namespace wvoec_mock
|
||||
|
||||
Reference in New Issue
Block a user