From 3d977d999c69cc3fad9e9f394425fc2c40c79f39 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Wed, 25 Jan 2017 18:56:01 -0800 Subject: [PATCH 1/3] 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 --- libwvdrmengine/oemcrypto/mock/Android.mk | 2 + .../mock/src/oemcrypto_engine_mock.cpp | 282 ++++-- .../mock/src/oemcrypto_engine_mock.h | 56 +- .../oemcrypto/mock/src/oemcrypto_mock.cpp | 263 ++++-- .../mock/src/oemcrypto_usage_table_mock.cpp | 884 +++++++++++------- .../mock/src/oemcrypto_usage_table_mock.h | 138 +-- .../oemcrypto/test/oec_session_util.cpp | 59 +- .../oemcrypto/test/oec_session_util.h | 31 +- .../oemcrypto/test/oemcrypto_test.cpp | 605 ++++-------- 9 files changed, 1259 insertions(+), 1061 deletions(-) diff --git a/libwvdrmengine/oemcrypto/mock/Android.mk b/libwvdrmengine/oemcrypto/mock/Android.mk index 2b8f89fe..8bcf11f6 100644 --- a/libwvdrmengine/oemcrypto/mock/Android.mk +++ b/libwvdrmengine/oemcrypto/mock/Android.mk @@ -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 \ diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp index 76e9f44d..282c0545 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.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& 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 key_id; std::vector enc_key_data; std::vector key_data_iv; std::vector key_control; std::vector key_control_iv; - std::vector 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& key_data, - const std::vector& key_data_iv, - const std::vector& key_control, - const std::vector& key_control_iv, - const std::vector& pst, - bool ctr_mode) { +OEMCryptoResult SessionContext::InstallKey( + const KeyId& key_id, const std::vector& key_data, + const std::vector& key_data_iv, + const std::vector& key_control, + const std::vector& key_control_iv, bool ctr_mode) { // Decrypt encrypted key_data using derived encryption key and offered iv std::vector content_key; std::vector 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& 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& pst) { + if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT; + usage_entry_->Deactivate(pst); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::ReportUsage(const std::vector& 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& 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& content_key = current_content_key()->value(); diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h index 2d34c7d2..49e5ad66 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h @@ -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& key_data, - const std::vector& key_data_iv, - const std::vector& key_control, - const std::vector& key_control_iv, - const std::vector& pst, bool ctr_mode); + OEMCryptoResult InstallKey(const KeyId& key_id, + const std::vector& key_data, + const std::vector& key_data_iv, + const std::vector& key_control, + const std::vector& 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& 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& pst); + OEMCryptoResult ReportUsage(const std::vector& pst, uint8_t* buffer, + size_t* buffer_length); + OEMCryptoResult MoveEntry(uint32_t new_index); + OEMCryptoResult CopyOldUsageEntry(const std::vector& pst); private: bool DeriveKey(const std::vector& key, @@ -185,9 +200,16 @@ class SessionContext { const std::vector& iv, const std::vector& message, std::vector* decrypted); - bool CheckNonceOrEntry(const KeyControlBlock& key_control_block, - const std::vector& 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); }; diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp index d7e2aa13..2c8b6d98 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp @@ -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 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 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 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 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 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 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 pstv(pst, pst + pst_length); + return session_ctx->CopyOldUsageEntry(pstv); } } // namespace wvoec_mock diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_usage_table_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_usage_table_mock.cpp index ee27f61b..7bd50cea 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_usage_table_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_usage_table_mock.cpp @@ -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 &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& server, + const std::vector& 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& server, + const std::vector& 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& 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& 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 &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(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 &server, - const std::vector &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 encrypted_buffer(file_size); - std::vector buffer(file_size); - StoredUsageTable *stored_table = - reinterpret_cast(&buffer[0]); - StoredUsageTable *encrypted_table = - reinterpret_cast(&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(&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 clear_buffer(buffer_size); + memset(&clear_buffer[0], 0, buffer_size); + memset(signed_buffer, 0, buffer_size); + SignedEntryBlock* clear = + reinterpret_cast(&clear_buffer[0]); + SignedEntryBlock* encrypted = + reinterpret_cast(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 &key = ce_->DeviceRootKey(override_to_real); + const std::vector& 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(&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 buffer(file_size); - std::vector encrypted_buffer(file_size); - StoredUsageTable *stored_table = - reinterpret_cast(&buffer[0]); - StoredUsageTable *encrypted_table = - reinterpret_cast(&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 &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& 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 clear_buffer(buffer.size()); + SignedEntryBlock* clear = + reinterpret_cast(&clear_buffer[0]); + const SignedEntryBlock* encrypted = + reinterpret_cast(&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& 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(kEntryVerification), + kMagicLength).c_str(), + reinterpret_cast(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& 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 clear_buffer(buffer_size); + memset(&clear_buffer[0], 0, buffer_size); + memset(signed_buffer, 0, buffer_size); + SignedHeaderBlock* clear = + reinterpret_cast(&clear_buffer[0]); + SignedHeaderBlock* encrypted = + reinterpret_cast(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(&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& 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& buffer) { + if (!LoadGenerationNumber(false)) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + + if (buffer.size() < SignedHeaderSize(0)) return OEMCrypto_ERROR_SHORT_BUFFER; + std::vector clear_buffer(buffer.size()); + SignedHeaderBlock* clear = + reinterpret_cast(&clear_buffer[0]); + const SignedHeaderBlock* encrypted = + reinterpret_cast(&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& 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(kHeaderVerification), + kMagicLength).c_str(), + reinterpret_cast(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(&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(&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(&generation_), sizeof(int64_t)); + file->Write(reinterpret_cast(&master_generation_number_), + sizeof(int64_t)); file->Close(); return true; } -UsageTableEntry *UsageTable::FindEntry(const std::vector &pst) { - wvcdm::AutoLock lock(lock_); - return FindEntryLocked(pst); -} - -UsageTableEntry *UsageTable::FindEntryLocked(const std::vector &pst) { - std::vector 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 &pst, - SessionContext *ctx) { - std::vector 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 &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 &pst) { - std::vector 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(&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(&master_generation_number_), + sizeof(int64_t)); + file->Close(); + return true; } -bool UsageTable::ComputeHash(const std::vector &pst, - std::vector &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& 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 diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_usage_table_mock.h b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_usage_table_mock.h index 03c0f8e2..fc51c489 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_usage_table_mock.h +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_usage_table_mock.h @@ -10,8 +10,9 @@ #include #include -#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 &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 &pst, - uint8_t *buffer, - size_t *buffer_length); - // Set them if not set, verify if already set. - bool VerifyOrSetMacKeys(const std::vector &server, - const std::vector &client); - const std::vector &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& server, + const std::vector& client); + bool SetMacKeys(const std::vector& server, + const std::vector& 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& pst); + OEMCryptoResult ReportUsage(const std::vector& 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& 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 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 mac_key_server_; - std::vector 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 &pst); - UsageTableEntry *CreateEntry(const std::vector &pst, - SessionContext *ctx); - OEMCryptoResult UpdateTable(); - OEMCryptoResult DeactivateEntry(const std::vector &pst); - bool DeleteEntry(const std::vector &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& 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& pst); + OEMCryptoResult DeleteOldUsageTable(); + OEMCryptoResult CreateUsageTableHeader(uint8_t* header_buffer, + size_t* header_buffer_length); + OEMCryptoResult LoadUsageTableHeader(const std::vector& 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 &pst); - bool SaveToFile(); - bool ComputeHash(const std::vector &pst, - std::vector &pst_hash); + OEMCryptoResult SaveUsageTableHeader(uint8_t* signed_buffer, + size_t buffer_size); + bool SaveGenerationNumber(); + bool LoadGenerationNumber(bool or_make_new_one); - typedef std::map, 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 generation_numbers_; + std::vector sessions_; }; } // namespace wvoec_mock diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp index be99018b..ae5d584b 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp @@ -819,8 +819,47 @@ void Session::InstallRSASessionTestKey(const vector& wrapped_rsa_key) { GenerateDerivedKeysFromSessionKey(); } +void Session::CreateNewUsageEntry() { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_CreateNewUsageEntry(session_id(), &usage_entry_number_)); +} + +void Session::UpdateUsageEntry(std::vector* header_buffer) { + size_t header_buffer_length = 0; + size_t entry_buffer_length = 0; + ASSERT_EQ( + OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_UpdateUsageEntry(session_id(), NULL, &header_buffer_length, + NULL, &entry_buffer_length)); + ASSERT_LT(0u, header_buffer_length); + header_buffer->resize(header_buffer_length); + ASSERT_LT(0u, entry_buffer_length); + encrypted_usage_entry_.resize(entry_buffer_length); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_UpdateUsageEntry( + session_id(), &(header_buffer->front()), &header_buffer_length, + &encrypted_usage_entry_[0], &entry_buffer_length)); +} + +void Session::DeactivateUsageEntry(const std::string& pst, + OEMCryptoResult expect_result) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_DeactivateUsageEntry( + session_id(), reinterpret_cast(pst.c_str()), + pst.length())); +} + +void Session::LoadUsageEntry(uint32_t index, const vector& buffer) { + usage_entry_number_ = index; + encrypted_usage_entry_ = buffer; + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(session_id(), index, &buffer[0], buffer.size())); +} + void Session::GenerateReport(const std::string& pst, bool expect_success, Session* other) { + ASSERT_TRUE(open_); if (other) { // If other is specified, copy mac keys. mac_key_server_ = other->mac_key_server_; mac_key_client_ = other->mac_key_client_; @@ -834,7 +873,7 @@ void Session::GenerateReport(const std::string& pst, bool expect_success, } if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { ASSERT_EQ(wvcdm::Unpacked_PST_Report::report_size(pst.length()), length); - pst_report_buffer_.resize(length); + pst_report_buffer_.assign(length, 0xFF); // Fill with garbage values. } sts = OEMCrypto_ReportUsage(session_id(), reinterpret_cast(pst.c_str()), @@ -843,6 +882,7 @@ void Session::GenerateReport(const std::string& pst, bool expect_success, ASSERT_NE(OEMCrypto_SUCCESS, sts); return; } + ASSERT_EQ(pst_report_buffer_.size(), length); ASSERT_EQ(OEMCrypto_SUCCESS, sts); vector computed_signature(SHA_DIGEST_LENGTH); unsigned int sig_len = SHA_DIGEST_LENGTH; @@ -857,23 +897,6 @@ void Session::GenerateReport(const std::string& pst, bool expect_success, EXPECT_EQ(0, memcmp(pst.c_str(), pst_report().pst(), pst.length())); } -void Session::DeleteEntry(const std::string& pst) { - uint8_t* pst_ptr = encrypted_license().pst; - memcpy(pst_ptr, pst.c_str(), min(sizeof(license_.pst), pst.length())); - ServerSignBuffer(reinterpret_cast(&padded_message_), - message_size_, &signature_); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_DeleteUsageEntry(session_id(), pst_ptr, pst.length(), - message_ptr(), message_size_, - &signature_[0], signature_.size())); -} - -void Session::ForceDeleteEntry(const std::string& pst) { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_ForceDeleteUsageEntry( - reinterpret_cast(pst.c_str()), pst.length())); -} - const uint8_t* Session::message_ptr() { return reinterpret_cast(&encrypted_license()); } diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.h b/libwvdrmengine/oemcrypto/test/oec_session_util.h index 285bd64a..29de6223 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.h +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.h @@ -248,6 +248,29 @@ class Session { // Loads the specified wrapped_rsa_key into OEMCrypto, and then runs // GenerateDerivedKeysFromSessionKey to install known encryption and mac keys. void InstallRSASessionTestKey(const vector& wrapped_rsa_key); + // Creates a new usage entry, and keeps track of the index. + void CreateNewUsageEntry(); + // Copy encrypted usage entry from other session, and then load it. + // This session must already be open. + void LoadUsageEntry(uint32_t index, const vector& buffer); + // Copy encrypted usage entry from other session. + // This session must already be open. + void LoadUsageEntry(const Session& other) { + LoadUsageEntry(other.usage_entry_number(), other.encrypted_usage_entry()); + } + // Reload previously used usage entry. + void ReloadUsageEntry() { LoadUsageEntry(*this); } + // Update the usage entry and save the header to the specified buffer. + void UpdateUsageEntry(std::vector* header_buffer); + // Deactivate this sessions usage entry. + void DeactivateUsageEntry(const std::string& pst, + OEMCryptoResult expect_result = OEMCrypto_SUCCESS); + // The usage entry number for this session's usage entry. + uint32_t usage_entry_number() const { return usage_entry_number_; } + // The encrypted buffer holding the recently updated and saved usage entry. + const vector& encrypted_usage_entry() const { + return encrypted_usage_entry_; + } // Generates a usage report for the specified pst. If expect_success is true, // the report's signature is verified, and several fields are given sanity // checks. If other is not null, then the mac keys are copied from other in @@ -259,12 +282,6 @@ class Session { wvcdm::Unpacked_PST_Report pst_report() { return wvcdm::Unpacked_PST_Report(&pst_report_buffer_[0]); } - // Creates a signed delete usage table entry message and calls - // OEMCrypto_DeleteUsageEntry on it. - void DeleteEntry(const std::string& pst); - // Calls OEMCrypto_ForceDeleteUsageEntry to delete a usage table entry without - // a signed message. - void ForceDeleteEntry(const std::string& pst); // The unencrypted license response or license renewal response. MessageData& license() { return license_; } @@ -310,6 +327,8 @@ class Session { OEMCrypto_KeyObject key_array_[kMaxNumKeys]; std::vector signature_; int num_keys_; + vector encrypted_usage_entry_; + uint32_t usage_entry_number_; }; } // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index fbf06efb..2dd4feca 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -610,7 +610,19 @@ class OEMCryptoSessionTests : public OEMCryptoClientTest { virtual void SetUp() { OEMCryptoClientTest::SetUp(); EnsureTestKeys(); - if (global_features.usage_table) OEMCrypto_DeleteOldUsageTable(); + if (global_features.usage_table) { + CreateUsageTableHeader(); + } + } + + void CreateUsageTableHeader() { + size_t header_buffer_length = 0; + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_CreateUsageTableHeader(NULL, &header_buffer_length)); + encrypted_usage_header_.resize(header_buffer_length); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_CreateUsageTableHeader(&encrypted_usage_header_[0], + &header_buffer_length)); } void EnsureTestKeys() { @@ -734,6 +746,7 @@ class OEMCryptoSessionTests : public OEMCryptoClientTest { ASSERT_EQ(NULL, find(wrapped_rsa_key_, encoded_rsa_key_)); } + vector encrypted_usage_header_; std::vector encoded_rsa_key_; std::vector wrapped_rsa_key_; }; @@ -1212,6 +1225,7 @@ TEST_F(OEMCryptoSessionTests, LoadKeysWithNoDerivedKeys) { ASSERT_NE(OEMCrypto_SUCCESS, sts); } +// TODO(fredgc): also add shared license bit. // To prevent initial loading shared licenses without usage table or nonce, // LoadKeys should reject an empty list of keys. TEST_F(OEMCryptoSessionTests, LoadKeyNoKeys) { @@ -4352,9 +4366,10 @@ TEST_F(GenericCryptoKeyIdLengthTest, UniformLongKeyId) { TestWithKey(2); } -TEST_F(OEMCryptoClientTest, UpdateUsageTableTest) { - EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); -} +// TODO(fredgc): write a new test for this. -- save reload, add skew error. +// TEST_F(OEMCryptoClientTest, UpdateUsageTableTest) { +// EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); +// } class UsageTableTest : public GenericCryptoTest { public: @@ -4363,22 +4378,15 @@ class UsageTableTest : public GenericCryptoTest { new_mac_keys_ = true; } - void DeactivatePST(const std::string& pst) { - ASSERT_EQ( - OEMCrypto_SUCCESS, - OEMCrypto_DeactivateUsageEntry( - 0, reinterpret_cast(pst.c_str()), pst.length())); - } - void LoadOfflineLicense(Session& s, const std::string& pst) { ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceOrEntry, s.get_nonce(), pst)); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); s.GenerateReport(pst); EXPECT_EQ(kUnused, s.pst_report().status()); EXPECT_NEAR(0, s.pst_report().seconds_since_license_received(), @@ -4420,7 +4428,6 @@ class UsageTableTestWithMAC : public UsageTableTest, TEST_P(UsageTableTestWithMAC, OnlineLicense) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); @@ -4428,9 +4435,9 @@ TEST_P(UsageTableTestWithMAC, OnlineLicense) { 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, s.get_nonce(), pst)); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); s.GenerateReport(pst); s.GenerateReport(pst); // test repeated report generation s.GenerateReport(pst); @@ -4439,24 +4446,28 @@ TEST_P(UsageTableTestWithMAC, OnlineLicense) { EXPECT_NEAR(0, s.pst_report().seconds_since_license_received(), kTimeTolerance); ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); s.GenerateReport(pst); EXPECT_EQ(kActive, s.pst_report().status()); EXPECT_NEAR(0, s.pst_report().seconds_since_license_received(), kTimeTolerance); EXPECT_NEAR(0, s.pst_report().seconds_since_first_decrypt(), kTimeTolerance); EXPECT_NEAR(0, s.pst_report().seconds_since_last_decrypt(), kTimeTolerance); - ASSERT_NO_FATAL_FAILURE(DeactivatePST(pst)); + // Flag the entry as inactive. + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); + // It should report as inactive. EXPECT_EQ(kInactiveUsed, s.pst_report().status()); EXPECT_NEAR(0, s.pst_report().seconds_since_license_received(), kTimeTolerance); + // Decrypt should fail. ASSERT_NO_FATAL_FAILURE( s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); } TEST_P(UsageTableTestWithMAC, OnlineLicenseWithRefresh) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); @@ -4464,6 +4475,7 @@ TEST_P(UsageTableTestWithMAC, OnlineLicenseWithRefresh) { 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, s.get_nonce(), pst)); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); time_t loaded = time(NULL); ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); @@ -4475,7 +4487,8 @@ TEST_P(UsageTableTestWithMAC, OnlineLicenseWithRefresh) { kAllKeys, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, s.get_nonce(), OEMCrypto_SUCCESS)); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); time_t report_generated = time(NULL); EXPECT_EQ(kActive, s.pst_report().status()); // license received at LoadKeys, not at RefreshKeys. @@ -4490,7 +4503,6 @@ TEST_P(UsageTableTestWithMAC, OnlineLicenseWithRefresh) { TEST_F(UsageTableTest, RepeatOnlineLicense) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); @@ -4498,13 +4510,15 @@ TEST_F(UsageTableTest, RepeatOnlineLicense) { 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, s.get_nonce(), pst)); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s.close()); Session s2; ASSERT_NO_FATAL_FAILURE(s2.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); uint8_t* pst_ptr = s.encrypted_license().pst; + s2.LoadUsageEntry(s); // Use the same entry. // Trying to reuse a PST is bad. We use session ID for s2, everything else // reused from s. ASSERT_NE( @@ -4519,7 +4533,6 @@ TEST_F(UsageTableTest, RepeatOnlineLicense) { // A license with non-zero replay control bits needs a valid pst.. TEST_F(UsageTableTest, OnlineEmptyPST) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); @@ -4527,6 +4540,7 @@ TEST_F(UsageTableTest, OnlineEmptyPST) { 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, s.get_nonce())); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), s.message_size(), &s.signature()[0], s.signature().size(), s.encrypted_license().mac_key_iv, @@ -4536,8 +4550,27 @@ TEST_F(UsageTableTest, OnlineEmptyPST) { ASSERT_NO_FATAL_FAILURE(s.close()); } +// A license with non-zero replay control bits needs a valid pst.. +TEST_F(UsageTableTest, OnlineMissingEntry) { + std::string pst = "my_pst"; + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); + ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( + 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, + s.get_nonce(), pst)); + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + // ENTRY NOT CREATED: ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + OEMCryptoResult sts = OEMCrypto_LoadKeys( + s.session_id(), s.message_ptr(), s.message_size(), &s.signature()[0], + s.signature().size(), s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, s.num_keys(), s.key_array(), + s.encrypted_license().pst, pst.length(), NULL); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NO_FATAL_FAILURE(s.close()); +} + TEST_F(UsageTableTest, EmptyTable) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); @@ -4546,337 +4579,27 @@ TEST_F(UsageTableTest, EmptyTable) { 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, s.get_nonce(), pst)); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); ASSERT_NO_FATAL_FAILURE(s.close()); OEMCrypto_DeleteOldUsageTable(); Session s2; ASSERT_NO_FATAL_FAILURE(s2.open()); - s2.GenerateReport(pst, false); + ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); + ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(pst, false)); ASSERT_NO_FATAL_FAILURE(s2.close()); } -TEST_F(UsageTableTest, FiftyEntries) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - Session s1; - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); - std::string pst1 = "pst saved"; - ASSERT_NO_FATAL_FAILURE(s1.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s1.get_nonce(), pst1)); - ASSERT_NO_FATAL_FAILURE(s1.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst1, new_mac_keys_)); - sleep(kShortSleep); - - const size_t ENTRY_COUNT = 49; // API says should hold at least 50 entries. - vector sessions(ENTRY_COUNT); - for (size_t i = 0; i < ENTRY_COUNT; i++) { - ASSERT_NO_FATAL_FAILURE(sessions[i].open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&sessions[i])); - std::string pst = "pst "; - char c = 'A' + i; - pst = pst + c; - ASSERT_NO_FATAL_FAILURE(sessions[i].FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - sessions[i].get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(sessions[i].EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)); - sessions[i].GenerateReport(pst); - ASSERT_NO_FATAL_FAILURE(sessions[i].close()); - } - for (size_t i = 0; i < ENTRY_COUNT; i++) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - std::string pst = "pst "; - char c = 'A' + i; - pst = pst + c; - s.GenerateReport(pst, true, &sessions[i]); - EXPECT_EQ(kUnused, s.pst_report().status()); - ASSERT_NO_FATAL_FAILURE(s.close()); - } - sleep(kShortSleep); - // If I add too many entries, it can delete the older ones first, except - // it shouldn't delete the one attached to an open session. (s1) - for (size_t i = 0; i < ENTRY_COUNT; i++) { - ASSERT_NO_FATAL_FAILURE(sessions[i].open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&sessions[i])); - std::string pst = "newer pst "; - char c = 'A' + i; - pst = pst + c; - ASSERT_NO_FATAL_FAILURE(sessions[i].FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - sessions[i].get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(sessions[i].EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)); - sessions[i].GenerateReport(pst); - ASSERT_NO_FATAL_FAILURE(sessions[i].close()); - } - for (int i = 0; i < 49; i++) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - std::string pst = "newer pst "; - char c = 'A' + i; - pst = pst + c; - s.GenerateReport(pst, true, &sessions[i]); - EXPECT_EQ(kUnused, s.pst_report().status()); - ASSERT_NO_FATAL_FAILURE(s.close()); - } - ASSERT_NO_FATAL_FAILURE(s1.close()); - ASSERT_NO_FATAL_FAILURE( - s1.open()); // Make sure s1's entry is still in the table. - s1.GenerateReport(pst1); - EXPECT_EQ(kUnused, s1.pst_report().status()); - ASSERT_NO_FATAL_FAILURE(s1.close()); -} - -TEST_P(UsageTableTestWithMAC, DeleteUnusedEntry) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - std::string pst = "my pst"; - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.GenerateReport(pst); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // New session should be able to generate report and copy mac keys. - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - s2.GenerateReport(pst, true, &s); - EXPECT_EQ(kUnused, s2.pst_report().status()); - ASSERT_NO_FATAL_FAILURE(s2.DeleteEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - // Now that session is deleted, we can't generate a report for it. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - s3.GenerateReport(pst, false); - ASSERT_NO_FATAL_FAILURE(s3.close()); -} - -TEST_P(UsageTableTestWithMAC, DeleteActiveEntry) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - std::string pst = "my pst"; - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - s.GenerateReport(pst); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // New session should be able to generate report and copy mac keys. - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(pst, true, &s)); - EXPECT_EQ(kActive, s2.pst_report().status()); - ASSERT_NO_FATAL_FAILURE(s2.DeleteEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - // Now that session is deleted, we can't generate a report for it. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, false)); - ASSERT_NO_FATAL_FAILURE(s3.close()); -} - -TEST_P(UsageTableTestWithMAC, ForceDeleteActiveEntry) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - std::string pst = "my pst"; - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - ASSERT_NO_FATAL_FAILURE(s.ForceDeleteEntry(pst)); - - // Now that session is deleted, we can't generate a report for it. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, false)); - ASSERT_NO_FATAL_FAILURE(s3.close()); -} - -TEST_P(UsageTableTestWithMAC, DeleteInactiveEntry) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - std::string pst = "my pst"; - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.GenerateReport(pst); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(DeactivatePST(pst)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // New session should be able to generate report and copy mac keys. - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - s2.GenerateReport(pst, true, &s); - EXPECT_EQ(kInactiveUsed, s2.pst_report().status()); - ASSERT_NO_FATAL_FAILURE(s2.DeleteEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - // Now that session is deleted, we can't generate a report for it. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - s3.GenerateReport(pst, false); - ASSERT_NO_FATAL_FAILURE(s3.close()); -} - -TEST_P(UsageTableTestWithMAC, DeleteEntryBadSignature) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - std::string pst = "my pst"; - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.GenerateReport(pst); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // New session should be able to generate report and copy mac keys. - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - s2.GenerateReport(pst, true, &s); - uint8_t* pst_ptr = s2.encrypted_license().pst; - memcpy(pst_ptr, pst.c_str(), min(sizeof(s2.license().pst), pst.length())); - vector signature(SHA256_DIGEST_LENGTH); - // Cannot delete without correct signature. - // ServerSignMessage(s2.encrypted_license(), &signature); - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_DeleteUsageEntry(s2.session_id(), pst_ptr, pst.length(), - s2.message_ptr(), s2.message_size(), - &signature[0], signature.size())); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - // The session is not deleted, we can still generate a report for it. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - s3.GenerateReport(pst, true, &s); - EXPECT_EQ(kUnused, s3.pst_report().status()); - ASSERT_NO_FATAL_FAILURE(s3.close()); -} - -TEST_P(UsageTableTestWithMAC, DeleteEntryWrongSession) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - std::string pst = "my pst"; - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.GenerateReport(pst); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // New session should not be able to delete without using GenerateReport - // to load mac keys. - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - // s2.GenerateReport(pst, true, &s); - uint8_t* pst_ptr = s2.encrypted_license().pst; - memcpy(pst_ptr, pst.c_str(), min(sizeof(s2.license().pst), pst.length())); - std::vector signature(SHA256_DIGEST_LENGTH); - s2.ServerSignBuffer(reinterpret_cast(&s2.encrypted_license()), - sizeof(s2.encrypted_license()), &signature); - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_DeleteUsageEntry(s2.session_id(), pst_ptr, pst.length(), - s2.message_ptr(), s2.message_size(), - &signature[0], signature.size())); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - // The session is not deleted, we can still generate a report for it. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - s3.GenerateReport(pst, true, &s); - EXPECT_EQ(kUnused, s3.pst_report().status()); - ASSERT_NO_FATAL_FAILURE(s3.close()); -} - -TEST_P(UsageTableTestWithMAC, DeleteEntryBadRange) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - std::string pst = "my pst"; - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.GenerateReport(pst); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // New session should not be able to delete if pst doesn't point into - // message. - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - s2.GenerateReport(pst, true, &s); - uint8_t* pst_ptr = s2.license().pst; - memcpy(pst_ptr, pst.c_str(), min(sizeof(s2.license().pst), pst.length())); - std::vector signature(SHA256_DIGEST_LENGTH); - s2.ServerSignBuffer(reinterpret_cast(&s2.encrypted_license()), - sizeof(s2.encrypted_license()), &signature); - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_DeleteUsageEntry(s2.session_id(), pst_ptr, pst.length(), - s2.message_ptr(), s2.message_size(), - &signature[0], signature.size())); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - // The session is not deleted, we can still generate a report for it. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - s3.GenerateReport(pst, true, &s); - EXPECT_EQ(kUnused, s3.pst_report().status()); - ASSERT_NO_FATAL_FAILURE(s3.close()); -} - -TEST_P(UsageTableTestWithMAC, DeactivateBadPST) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - std::string pst = "nonexistant pst"; - OEMCryptoResult sts = OEMCrypto_DeactivateUsageEntry( - 0, reinterpret_cast(pst.c_str()), pst.length()); - EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); - std::string null_pst = ""; - sts = OEMCrypto_DeactivateUsageEntry( - 0, reinterpret_cast(null_pst.c_str()), null_pst.length()); - EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); -} - TEST_P(UsageTableTestWithMAC, GenericCryptoEncrypt) { std::string pst = "A PST"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); MakeFourKeys( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, nonce, pst); ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; unsigned int key_index = 0; @@ -4892,7 +4615,8 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoEncrypt) { OEMCrypto_AES_CBC_128_NO_PADDING, &encrypted[0]); ASSERT_EQ(OEMCrypto_SUCCESS, sts); EXPECT_EQ(expected_encrypted, encrypted); - session_.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst)); EXPECT_EQ(kActive, session_.pst_report().status()); EXPECT_NEAR(0, session_.pst_report().seconds_since_license_received(), kTimeTolerance); @@ -4900,7 +4624,8 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoEncrypt) { kTimeTolerance); EXPECT_NEAR(0, session_.pst_report().seconds_since_last_decrypt(), kTimeTolerance); - ASSERT_NO_FATAL_FAILURE(DeactivatePST(pst)); + ASSERT_NO_FATAL_FAILURE(session_.DeactivateUsageEntry(pst)); + ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst)); EXPECT_EQ(kInactiveUsed, session_.pst_report().status()); EXPECT_NEAR(0, session_.pst_report().seconds_since_license_received(), @@ -4915,12 +4640,12 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoEncrypt) { TEST_P(UsageTableTestWithMAC, GenericCryptoDecrypt) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); MakeFourKeys( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, nonce, pst); ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; unsigned int key_index = 1; @@ -4936,7 +4661,8 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoDecrypt) { OEMCrypto_AES_CBC_128_NO_PADDING, &resultant[0]); ASSERT_EQ(OEMCrypto_SUCCESS, sts); EXPECT_EQ(clear_buffer_, resultant); - session_.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst)); EXPECT_EQ(kActive, session_.pst_report().status()); EXPECT_NEAR(0, session_.pst_report().seconds_since_license_received(), kTimeTolerance); @@ -4944,8 +4670,9 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoDecrypt) { kTimeTolerance); EXPECT_NEAR(0, session_.pst_report().seconds_since_last_decrypt(), kTimeTolerance); - ASSERT_NO_FATAL_FAILURE(DeactivatePST(pst)); - session_.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(session_.DeactivateUsageEntry(pst)); + ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst)); EXPECT_EQ(kInactiveUsed, session_.pst_report().status()); EXPECT_NEAR(0, session_.pst_report().seconds_since_license_received(), kTimeTolerance); @@ -4959,12 +4686,12 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoDecrypt) { TEST_P(UsageTableTestWithMAC, GenericCryptoSign) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); MakeFourKeys( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, nonce, pst); ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; @@ -4989,7 +4716,8 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoSign) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); ASSERT_EQ(expected_signature, signature); - session_.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst)); EXPECT_EQ(kActive, session_.pst_report().status()); EXPECT_NEAR(0, session_.pst_report().seconds_since_license_received(), kTimeTolerance); @@ -4997,7 +4725,8 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoSign) { kTimeTolerance); EXPECT_NEAR(0, session_.pst_report().seconds_since_last_decrypt(), kTimeTolerance); - ASSERT_NO_FATAL_FAILURE(DeactivatePST(pst)); + ASSERT_NO_FATAL_FAILURE(session_.DeactivateUsageEntry(pst)); + ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst)); EXPECT_EQ(kInactiveUsed, session_.pst_report().status()); EXPECT_NEAR(0, session_.pst_report().seconds_since_license_received(), @@ -5014,12 +4743,12 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoSign) { TEST_P(UsageTableTestWithMAC, GenericCryptoVerify) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); uint32_t nonce = session_.get_nonce(); MakeFourKeys( 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, nonce, pst); ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); OEMCryptoResult sts; @@ -5035,8 +4764,8 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoVerify) { clear_buffer_.size(), OEMCrypto_HMAC_SHA256, &signature[0], signature.size()); ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - session_.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst)); EXPECT_EQ(kActive, session_.pst_report().status()); EXPECT_NEAR(0, session_.pst_report().seconds_since_license_received(), kTimeTolerance); @@ -5044,7 +4773,8 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoVerify) { kTimeTolerance); EXPECT_NEAR(0, session_.pst_report().seconds_since_last_decrypt(), kTimeTolerance); - ASSERT_NO_FATAL_FAILURE(DeactivatePST(pst)); + ASSERT_NO_FATAL_FAILURE(session_.DeactivateUsageEntry(pst)); + ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst)); EXPECT_EQ(kInactiveUsed, session_.pst_report().status()); EXPECT_NEAR(0, session_.pst_report().seconds_since_license_received(), @@ -5058,20 +4788,19 @@ TEST_P(UsageTableTestWithMAC, GenericCryptoVerify) { TEST_P(UsageTableTestWithMAC, OfflineLicense) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); } TEST_P(UsageTableTestWithMAC, OfflineLicenseRefresh) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceOrEntry, s.get_nonce(), pst)); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); time_t loaded = time(NULL); ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); @@ -5081,7 +4810,8 @@ TEST_P(UsageTableTestWithMAC, OfflineLicenseRefresh) { size_t kAllKeys = 1; ASSERT_NO_FATAL_FAILURE(s.RefreshTestKeys( kAllKeys, wvoec_mock::kControlNonceOrEntry, 0, OEMCrypto_SUCCESS)); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); time_t report_generated = time(NULL); EXPECT_EQ(kActive, s.pst_report().status()); // license received at LoadKeys, not at RefreshKeys. @@ -5096,7 +4826,6 @@ TEST_P(UsageTableTestWithMAC, OfflineLicenseRefresh) { TEST_P(UsageTableTestWithMAC, ReloadOfflineLicense) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); @@ -5104,14 +4833,16 @@ TEST_P(UsageTableTestWithMAC, ReloadOfflineLicense) { ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); // We will reuse the encrypted and signed message, so we don't call // FillSimpleMessage again. + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.GenerateReport(pst); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_EQ(kUnused, s.pst_report().status()); EXPECT_NEAR(0, s.pst_report().seconds_since_license_received(), kTimeTolerance); ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_EQ(kActive, s.pst_report().status()); EXPECT_NEAR(0, s.pst_report().seconds_since_license_received(), kTimeTolerance); @@ -5122,7 +4853,6 @@ TEST_P(UsageTableTestWithMAC, ReloadOfflineLicense) { TEST_P(UsageTableTestWithMAC, ReloadOfflineLicenseWithRefresh) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); time_t loaded = time(NULL); @@ -5131,15 +4861,17 @@ TEST_P(UsageTableTestWithMAC, ReloadOfflineLicenseWithRefresh) { ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); // We will reuse the encrypted and signed message, so we don't call // FillSimpleMessage again. + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.GenerateReport(pst); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_EQ(kUnused, s.pst_report().status()); EXPECT_NEAR(0, s.pst_report().seconds_since_license_received(), kTimeTolerance); ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); time_t decrypt_time = time(NULL); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); time_t report_generated = time(NULL); EXPECT_EQ(kActive, s.pst_report().status()); // license received at first LoadKeys. @@ -5153,7 +4885,8 @@ TEST_P(UsageTableTestWithMAC, ReloadOfflineLicenseWithRefresh) { size_t kAllKeys = 1; ASSERT_NO_FATAL_FAILURE(s.RefreshTestKeys( kAllKeys, wvoec_mock::kControlNonceOrEntry, 0, OEMCrypto_SUCCESS)); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); report_generated = time(NULL); EXPECT_EQ(kActive, s.pst_report().status()); // license received at LoadKeys, not at RefreshKeys. @@ -5169,7 +4902,6 @@ TEST_P(UsageTableTestWithMAC, ReloadOfflineLicenseWithRefresh) { TEST_P(UsageTableTestWithMAC, BadReloadOfflineLicense) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); @@ -5180,6 +4912,7 @@ TEST_P(UsageTableTestWithMAC, BadReloadOfflineLicense) { ASSERT_NO_FATAL_FAILURE(s2.FillSimpleMessage( 0, wvoec_mock::kControlNonceOrEntry, s2.get_nonce(), pst)); ASSERT_NO_FATAL_FAILURE(s2.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); uint8_t* pst_ptr = s2.encrypted_license().pst; ASSERT_NE( OEMCrypto_SUCCESS, @@ -5193,18 +4926,20 @@ TEST_P(UsageTableTestWithMAC, BadReloadOfflineLicense) { // Offline license with same mac keys should still be OK. ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_EQ(kUnused, s.pst_report().status()); } // An offline license should not load on the first call if the nonce is bad. TEST_P(UsageTableTestWithMAC, OfflineBadNonce) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE( s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, 42, pst)); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); @@ -5220,10 +4955,10 @@ TEST_P(UsageTableTestWithMAC, OfflineBadNonce) { // An offline license needs a valid pst. TEST_P(UsageTableTestWithMAC, OfflineEmptyPST) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE( s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, s.get_nonce())); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); @@ -5238,20 +4973,21 @@ TEST_P(UsageTableTestWithMAC, OfflineEmptyPST) { TEST_P(UsageTableTestWithMAC, DeactivateOfflineLicense) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE( s.LoadTestKeys(pst, new_mac_keys_)); // Reload the license ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); // Should be able to decrypt. - ASSERT_NO_FATAL_FAILURE(DeactivatePST(pst)); // Then deactivate. + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); // Then deactivate. // After deactivate, should not be able to decrypt. ASSERT_NO_FATAL_FAILURE( s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_EQ(kInactiveUsed, s.pst_report().status()); EXPECT_NEAR(0, s.pst_report().seconds_since_license_received(), kTimeTolerance); @@ -5260,6 +4996,7 @@ TEST_P(UsageTableTestWithMAC, DeactivateOfflineLicense) { Session s2; ASSERT_NO_FATAL_FAILURE(s2.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); + ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); // Offile license can not be reused if it has been deactivated. uint8_t* pst_ptr = s.encrypted_license().pst; EXPECT_NE( @@ -5269,19 +5006,22 @@ TEST_P(UsageTableTestWithMAC, DeactivateOfflineLicense) { s.encrypted_license().mac_key_iv, s.encrypted_license().mac_keys, s.num_keys(), s.key_array(), pst_ptr, pst.length(), NULL)); + s2.close(); // But we can still generate a report. Session s3; ASSERT_NO_FATAL_FAILURE(s3.open()); - s3.GenerateReport(pst, true, &s); + ASSERT_NO_FATAL_FAILURE(s3.LoadUsageEntry(s)); + ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, true, &s)); EXPECT_EQ(kInactiveUsed, s3.pst_report().status()); } TEST_P(UsageTableTestWithMAC, BadRange) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( 0, wvoec_mock::kControlNonceOrEntry, s.get_nonce(), pst)); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); @@ -5296,7 +5036,6 @@ TEST_P(UsageTableTestWithMAC, BadRange) { } TEST_F(UsageTableTest, TimingTest) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); std::string pst1 = "my_pst_1"; std::string pst2 = "my_pst_2"; std::string pst3 = "my_pst_3"; @@ -5313,12 +5052,14 @@ TEST_F(UsageTableTest, TimingTest) { sleep(kLongSleep); ASSERT_NO_FATAL_FAILURE(s1.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); + ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst1, new_mac_keys_)); time_t first_decrypt1 = time(NULL); ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); ASSERT_NO_FATAL_FAILURE(s2.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); + ASSERT_NO_FATAL_FAILURE(s2.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE(s2.LoadTestKeys(pst2, new_mac_keys_)); time_t first_decrypt2 = time(NULL); ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); @@ -5329,7 +5070,9 @@ TEST_F(UsageTableTest, TimingTest) { ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); sleep(kLongSleep); - ASSERT_NO_FATAL_FAILURE(DeactivatePST(pst1)); + ASSERT_NO_FATAL_FAILURE(s1.DeactivateUsageEntry(pst1)); + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s1.close()); ASSERT_NO_FATAL_FAILURE(s2.close()); @@ -5341,6 +5084,9 @@ TEST_F(UsageTableTest, TimingTest) { sleep(kShortSleep); OEMCrypto_Initialize(); EnsureTestKeys(); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageTableHeader(&encrypted_usage_header_[0], + encrypted_usage_header_.size())); // Test teardown expects session_ to be open. ASSERT_NO_FATAL_FAILURE(session_.open()); @@ -5349,20 +5095,28 @@ TEST_F(UsageTableTest, TimingTest) { time_t third_decrypt = time(NULL); ASSERT_NO_FATAL_FAILURE(s2.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); + ASSERT_NO_FATAL_FAILURE(s2.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE(s2.LoadTestKeys(pst2, new_mac_keys_)); ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s2.close()); ASSERT_NO_FATAL_FAILURE(s1.open()); ASSERT_NO_FATAL_FAILURE(s2.open()); ASSERT_NO_FATAL_FAILURE(s3.open()); + ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s2.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s3.ReloadUsageEntry()); sleep(kLongSleep); time_t report_generated1 = time(NULL); - s1.GenerateReport(pst1); + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s1.GenerateReport(pst1)); time_t report_generated2 = time(NULL); - s2.GenerateReport(pst2); + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(pst2)); time_t report_generated3 = time(NULL); - s3.GenerateReport(pst3); + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst3)); EXPECT_EQ(kInactiveUsed, s1.pst_report().status()); EXPECT_NEAR(report_generated1 - loaded1, @@ -5388,7 +5142,6 @@ TEST_F(UsageTableTest, TimingTest) { TEST_F(UsageTableTest, VerifyUsageTimes) { std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); @@ -5396,11 +5149,12 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, s.get_nonce(), pst)); ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); const int kLicenseReceivedTimeTolerance = kSpeedMultiplier; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_EQ(kUnused, s.pst_report().status()); EXPECT_NEAR(0, s.pst_report().seconds_since_license_received(), kLicenseReceivedTimeTolerance); @@ -5420,8 +5174,8 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_EQ(kUnused, s.pst_report().status()); EXPECT_NEAR(s.pst_report().seconds_since_license_received(), kIdleInSeconds, kLicenseReceivedTimeTolerance); @@ -5432,7 +5186,8 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { time_t start_time = time(NULL); do { ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_EQ(kActive, s.pst_report().status()); playback_time = time(NULL) - start_time; ASSERT_LE(0, playback_time); @@ -5444,8 +5199,8 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { } while (playback_time < kPlaybackLoopInSeconds); cout << "\nSimulated playback time = " << playback_time << " seconds.\n"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_NEAR(s.pst_report().seconds_since_license_received(), playback_time + kIdleInSeconds, kLicenseReceivedTimeTolerance); EXPECT_NEAR(s.pst_report().seconds_since_first_decrypt(), playback_time, @@ -5468,8 +5223,8 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { // |<--->| = seconds_since_last_decrypt // |<----------------------------->| = seconds_since_first_decrypt // |<------------------------------------| = seconds_since_license_received - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - s.GenerateReport(pst); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_NEAR(s.pst_report().seconds_since_license_received(), playback_time + 2 * kIdleInSeconds, kLicenseReceivedTimeTolerance); @@ -5478,7 +5233,8 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { EXPECT_NEAR(s.pst_report().seconds_since_last_decrypt(), kIdleInSeconds, kUsageTableTimeTolerance); - ASSERT_NO_FATAL_FAILURE(DeactivatePST(pst)); + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_EQ(kInactiveUsed, s.pst_report().status()); ASSERT_NO_FATAL_FAILURE( @@ -5488,6 +5244,7 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { // This is a special case where a collection of licenses can be shared with // multiple devices. In order for this to work, a single session must first // load a device specific license, and then a shared content license. +#if 0 // TODO(fredgc,jfore): fix this in http://go/wvgerrit/23184/ TEST_F(UsageTableTest, LoadSharedLicense) { // session_.generatersasignature. // session_.GenerateNonce @@ -5496,15 +5253,15 @@ TEST_F(UsageTableTest, LoadSharedLicense) { // LoadKeys replay control = 0. uses same mac key. // check second loadkeys without first fails. std::string pst = "my_pst"; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, true)); ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - // The second set of keys are not loaded. + // The second set of keys are in the shared license. for (unsigned int i = 0; i < s.num_keys(); i++) { memset(s.license().keys[i].key_id, 'A' + i, s.license().keys[i].key_id_length); @@ -5512,26 +5269,28 @@ TEST_F(UsageTableTest, LoadSharedLicense) { // TODO(fredgc,jfore): Decide if first set of keys need to stay loaded, or if // they are replaced. ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, false)); ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); ASSERT_NO_FATAL_FAILURE(s.close()); } +#endif TEST_F(UsageTableTest, PSTLargeBuffer) { std::string pst(kMaxPSTLength, 'a'); // A large PST. - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); Session s; ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE( s.LoadTestKeys(pst, new_mac_keys_)); // Reload the license ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); // Should be able to decrypt. - ASSERT_NO_FATAL_FAILURE(DeactivatePST(pst)); // Then deactivate. + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); // Then deactivate. // After deactivate, should not be able to decrypt. ASSERT_NO_FATAL_FAILURE( s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); EXPECT_EQ(kInactiveUsed, s.pst_report().status()); EXPECT_NEAR(0, s.pst_report().seconds_since_license_received(), @@ -5543,6 +5302,7 @@ TEST_F(UsageTableTest, PSTLargeBuffer) { ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); // Offile license can not be reused if it has been deactivated. uint8_t* pst_ptr = s.encrypted_license().pst; + ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); EXPECT_NE( OEMCrypto_SUCCESS, OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), s.message_size(), @@ -5550,67 +5310,16 @@ TEST_F(UsageTableTest, PSTLargeBuffer) { s.encrypted_license().mac_key_iv, s.encrypted_license().mac_keys, s.num_keys(), s.key_array(), pst_ptr, pst.length(), NULL)); + s2.close(); // But we can still generate a report. Session s3; ASSERT_NO_FATAL_FAILURE(s3.open()); + ASSERT_NO_FATAL_FAILURE(s3.LoadUsageEntry(s)); + ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, true, &s)); EXPECT_EQ(kInactiveUsed, s3.pst_report().status()); } -TEST_F(UsageTableTest, DeleteEntryLargeBuffer) { - std::string pst(kMaxPSTLength, 'a'); // A large PST. - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - s.GenerateReport(pst); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // New session should be able to generate report and copy mac keys. - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(pst, true, &s)); - EXPECT_EQ(kActive, s2.pst_report().status()); - ASSERT_NO_FATAL_FAILURE(s2.DeleteEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - // Now that session is deleted, we can't generate a report for it. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, false)); - ASSERT_NO_FATAL_FAILURE(s3.close()); -} - -TEST_F(UsageTableTest, ForceDeleteLargeBuffer) { - std::string pst(kMaxPSTLength, 'a'); // A large PST. - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.GenerateReport(pst)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - ASSERT_NO_FATAL_FAILURE(s.ForceDeleteEntry(pst)); - - // Now that session is deleted, we can't generate a report for it. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, false)); - ASSERT_NO_FATAL_FAILURE(s3.close()); -} - INSTANTIATE_TEST_CASE_P(TestUsageTables, UsageTableTestWithMAC, Values(true, false)); // With and without new_mac_keys. } // namespace wvoec From 650a0fdead17df522476db059ce6fec7a3538f41 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Fri, 27 Jan 2017 15:20:03 -0800 Subject: [PATCH 2/3] Add Shared License bit to key control block Merge from widevine of http://go/wvgerrit/23184 This adds the shared license bit to the key control block for the reference code and the unit tests. b/31458046 Change-Id: I4e360ea5dd2e6cee145663d4ab4f384b65cac427 --- .../mock/src/oemcrypto_engine_mock.cpp | 18 +++++- .../mock/src/oemcrypto_engine_mock.h | 3 +- .../oemcrypto/mock/src/oemcrypto_key_mock.cpp | 2 + .../oemcrypto/mock/src/oemcrypto_key_mock.h | 1 + .../oemcrypto/test/oemcrypto_test.cpp | 62 ++++++++++++++----- 5 files changed, 68 insertions(+), 18 deletions(-) diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp index 282c0545..84bc20b9 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp @@ -433,6 +433,10 @@ OEMCryptoResult SessionContext::LoadKeys( StartTimer(); + // If there are already keys installed in this session, then we can load + // a shared license. + bool second_license = (session_keys_.size() > 0); + // Decrypt and install keys in key object // Each key will have a key control block. They will all have the same nonce. OEMCryptoResult status = OEMCrypto_SUCCESS; @@ -459,7 +463,7 @@ OEMCryptoResult SessionContext::LoadKeys( OEMCryptoResult result = InstallKey( key_id, enc_key_data, key_data_iv, key_control, key_control_iv, - key_array[i].cipher_mode == OEMCrypto_CipherMode_CTR); + key_array[i].cipher_mode == OEMCrypto_CipherMode_CTR, second_license); if (result != OEMCrypto_SUCCESS) { status = result; break; @@ -505,7 +509,7 @@ OEMCryptoResult SessionContext::LoadKeys( return OEMCrypto_ERROR_WRONG_PST; } if (!usage_entry_->VerifyMacKeys(mac_key_server_, mac_key_client_)) { - LOGE("LoadKeys: Usage table entry does not match.\n"); + LOGE("LoadKeys: Usage table entry mac keys do not match.\n"); return OEMCrypto_ERROR_WRONG_KEYS; } if (usage_entry_->Inactive()) return OEMCrypto_ERROR_LICENSE_INACTIVE; @@ -519,7 +523,8 @@ OEMCryptoResult SessionContext::InstallKey( const KeyId& key_id, const std::vector& key_data, const std::vector& key_data_iv, const std::vector& key_control, - const std::vector& key_control_iv, bool ctr_mode) { + const std::vector& key_control_iv, bool ctr_mode, + bool second_license) { // Decrypt encrypted key_data using derived encryption key and offered iv std::vector content_key; std::vector key_control_str; @@ -579,6 +584,13 @@ OEMCryptoResult SessionContext::InstallKey( return result; } + if (key_control_block.control_bits() & kSharedLicense) { + if (!second_license) { + LOGE("LoadKeys: Shared License, but no keys previously loaded."); + return OEMCrypto_ERROR_MISSING_MASTER; + } + } + Key key(content_key, key_control_block, ctr_mode); session_keys_.Insert(key_id, key); return OEMCrypto_SUCCESS; diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h index 49e5ad66..aeff4f68 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h @@ -44,6 +44,7 @@ class SessionKeyTable { Key* Find(const KeyId key_id); void Remove(const KeyId key_id); void UpdateDuration(const KeyControlBlock& control); + size_t size() const { return keys_.size(); } private: KeyMap keys_; @@ -144,7 +145,7 @@ class SessionContext { const std::vector& key_data_iv, const std::vector& key_control, const std::vector& key_control_iv, - bool ctr_mode); + bool ctr_mode, bool second_license); 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, diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_key_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_key_mock.cpp index afba52c7..8bfafb96 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_key_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_key_mock.cpp @@ -59,6 +59,8 @@ KeyControlBlock::KeyControlBlock( LOGD(" nonce: %08X", nonce()); LOGD(" magic: %08X", verification()); LOGD(" bits: %08X", control_bits()); + LOGD(" bit kSharedLicense %s.", + (control_bits() & kSharedLicense) ? "set" : "unset"); LOGD(" bit kControlSRMVersionRequired %s.", (control_bits() & kControlSRMVersionRequired) ? "set" : "unset"); LOGD(" bit kControlDisableAnalogOutput %s.", diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_key_mock.h b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_key_mock.h index 0312327c..925fa517 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_key_mock.h +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_key_mock.h @@ -15,6 +15,7 @@ const uint32_t kControlObserveDataPath = (1<<31); const uint32_t kControlObserveHDCP = (1<<30); const uint32_t kControlObserveCGMS = (1<<29); const uint32_t kControlRequireAntiRollbackHardware = (1<<28); +const uint32_t kSharedLicense = (1<<23); const uint32_t kControlSRMVersionRequired = (1<<22); const uint32_t kControlDisableAnalogOutput = (1<<21); const uint32_t kControlSecurityPatchLevelShift = 15; diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 2dd4feca..e0d9a89d 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -5244,14 +5244,37 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { // This is a special case where a collection of licenses can be shared with // multiple devices. In order for this to work, a single session must first // load a device specific license, and then a shared content license. -#if 0 // TODO(fredgc,jfore): fix this in http://go/wvgerrit/23184/ TEST_F(UsageTableTest, LoadSharedLicense) { - // session_.generatersasignature. - // session_.GenerateNonce - // DeriveKeysFromSessionKey - (specify enc/mac keys. - // LoadKeys replay control = 2. loads new mac keys. - // LoadKeys replay control = 0. uses same mac key. - // check second loadkeys without first fails. + std::string pst = "my_pst"; + Session s; + ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); + + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); + // We will reuse the encrypted and signed message, so we don't call + // FillSimpleMessage again. + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, true)); + + // The second set of keys are in the shared license. They will have the + // same mac keys as the original license, so we leave that alone. + // We given them different key ids so we can test that they were loaded. + // For this test, we leave the key content the same -- in real life it + // will be different. + for (unsigned int i = 0; i < s.num_keys(); i++) { + memset(s.license().keys[i].key_id, 'A' + i, + s.license().keys[i].key_id_length); + s.license().keys[i].control.nonce = 0; + s.license().keys[i].control.control_bits = + htonl(wvoec_mock::kSharedLicense); + } + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, false)); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.close()); +} + +TEST_F(UsageTableTest, LoadSharedLicenseWithNoMaster) { std::string pst = "my_pst"; Session s; ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); @@ -5259,21 +5282,32 @@ TEST_F(UsageTableTest, LoadSharedLicense) { ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, true)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); + // This time, we do NOT load the master license. This should + // generate an error below. + // ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, true)); + // The second set of keys are in the shared license. + // We given them different key ids so we can test that they were loaded. + // For this test, we leave the key content the same -- in real life it + // will be different. for (unsigned int i = 0; i < s.num_keys(); i++) { memset(s.license().keys[i].key_id, 'A' + i, s.license().keys[i].key_id_length); + s.license().keys[i].control.nonce = 0; + s.license().keys[i].control.control_bits = + htonl(wvoec_mock::kSharedLicense); } - // TODO(fredgc,jfore): Decide if first set of keys need to stay loaded, or if - // they are replaced. ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, false)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); + uint8_t* pst_ptr = s.encrypted_license().pst; + ASSERT_EQ(OEMCrypto_ERROR_MISSING_MASTER, + OEMCrypto_LoadKeys( + s.session_id(), s.message_ptr(), s.message_size(), + &s.signature()[0], s.signature().size(), + s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, + s.num_keys(), s.key_array(), pst_ptr, pst.length(), NULL)); ASSERT_NO_FATAL_FAILURE(s.close()); } -#endif TEST_F(UsageTableTest, PSTLargeBuffer) { std::string pst(kMaxPSTLength, 'a'); // A large PST. From d06fa606c750e9a62e09b831a43c7a8a5a49daf9 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Fri, 27 Jan 2017 15:21:08 -0800 Subject: [PATCH 3/3] Refactor OEMCrypto Engine Merge from widevine repo of http://go/wvgerrit/23280 This CL moves some of the oemcrypto mock classes into their own files. There are no real code changes. Change-Id: I4e4a6a01d8e75051bc0eb2a5d58361c438c7f41b --- libwvdrmengine/oemcrypto/mock/Android.mk | 3 + .../mock/src/oemcrypto_engine_mock.cpp | 1358 +---------------- .../mock/src/oemcrypto_engine_mock.h | 235 +-- .../oemcrypto/mock/src/oemcrypto_mock.cpp | 1 + .../mock/src/oemcrypto_nonce_table.cpp | 67 + .../mock/src/oemcrypto_nonce_table.h | 38 + .../oemcrypto/mock/src/oemcrypto_session.cpp | 1288 ++++++++++++++++ .../oemcrypto/mock/src/oemcrypto_session.h | 210 +++ .../mock/src/oemcrypto_session_key_table.cpp | 46 + .../mock/src/oemcrypto_session_key_table.h | 45 + 10 files changed, 1706 insertions(+), 1585 deletions(-) create mode 100644 libwvdrmengine/oemcrypto/mock/src/oemcrypto_nonce_table.cpp create mode 100644 libwvdrmengine/oemcrypto/mock/src/oemcrypto_nonce_table.h create mode 100644 libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.cpp create mode 100644 libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.h create mode 100644 libwvdrmengine/oemcrypto/mock/src/oemcrypto_session_key_table.cpp create mode 100644 libwvdrmengine/oemcrypto/mock/src/oemcrypto_session_key_table.h diff --git a/libwvdrmengine/oemcrypto/mock/Android.mk b/libwvdrmengine/oemcrypto/mock/Android.mk index 8bcf11f6..fac879d6 100644 --- a/libwvdrmengine/oemcrypto/mock/Android.mk +++ b/libwvdrmengine/oemcrypto/mock/Android.mk @@ -12,7 +12,10 @@ LOCAL_SRC_FILES:= \ src/oemcrypto_keybox_testkey.cpp \ src/oemcrypto_logging.cpp \ src/oemcrypto_mock.cpp \ + src/oemcrypto_nonce_table.cpp \ src/oemcrypto_rsa_key_shared.cpp \ + src/oemcrypto_session.cpp \ + src/oemcrypto_session_key_table.cpp \ src/oemcrypto_usage_table_mock.cpp \ src/wvcrc.cpp \ diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp index 84bc20b9..a60a6dad 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp @@ -11,1074 +11,21 @@ #include #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include #include "keys.h" #include "log.h" #include "oemcrypto_key_mock.h" -#include "oemcrypto_logging.h" #include "oemcrypto_rsa_key_shared.h" -#include "oemcrypto_usage_table_mock.h" #include "string_conversions.h" #include "wv_cdm_constants.h" -static const int kPssSaltLength = 20; - -namespace { - -// Increment counter for AES-CTR. The CENC spec specifies we increment only -// the low 64 bits of the IV counter, and leave the high 64 bits alone. -void ctr128_inc64(uint8_t* counter) { - uint32_t n = 16; - do { - if (++counter[--n] != 0) return; - } while (n > 8); -} - -void dump_openssl_error() { - while (unsigned long err = ERR_get_error()) { - char buffer[120]; - LOGE("openssl error -- %lu -- %s", err, ERR_error_string(err, buffer)); - } -} - -} // namespace - namespace wvoec_mock { -SessionKeyTable::~SessionKeyTable() { - for (KeyMap::iterator i = keys_.begin(); i != keys_.end(); ++i) { - if (NULL != i->second) { - delete i->second; - } - } -} - -bool SessionKeyTable::Insert(const KeyId key_id, const Key& key_data) { - if (keys_.find(key_id) != keys_.end()) return false; - keys_[key_id] = new Key(key_data); - return true; -} - -Key* SessionKeyTable::Find(const KeyId key_id) { - if (keys_.find(key_id) == keys_.end()) { - return NULL; - } - return keys_[key_id]; -} - -void SessionKeyTable::Remove(const KeyId key_id) { - if (keys_.find(key_id) != keys_.end()) { - delete keys_[key_id]; - keys_.erase(key_id); - } -} - -void SessionKeyTable::UpdateDuration(const KeyControlBlock& control) { - for (KeyMap::iterator it = keys_.begin(); it != keys_.end(); ++it) { - it->second->UpdateDuration(control); - } -} - -SessionContext::~SessionContext() { - if (usage_entry_) { - delete usage_entry_; - usage_entry_ = NULL; - } -} - -// Internal utility function to derive key using CMAC-128 -bool SessionContext::DeriveKey(const std::vector& key, - const std::vector& context, int counter, - std::vector* out) { - if (key.empty() || counter > 4 || context.empty() || out == NULL) { - LOGE("[DeriveKey(): OEMCrypto_ERROR_INVALID_CONTEXT]"); - return false; - } - - const EVP_CIPHER* cipher = EVP_aes_128_cbc(); - CMAC_CTX* cmac_ctx = CMAC_CTX_new(); - - if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, 0)) { - LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]"); - return false; - } - - std::vector message; - message.push_back(counter); - message.insert(message.end(), context.begin(), context.end()); - - if (!CMAC_Update(cmac_ctx, &message[0], message.size())) { - LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]"); - return false; - } - - size_t reslen; - uint8_t res[128]; - if (!CMAC_Final(cmac_ctx, res, &reslen)) { - LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]"); - return false; - } - - out->assign(res, res + reslen); - - CMAC_CTX_free(cmac_ctx); - - return true; -} - -bool SessionContext::DeriveKeys(const std::vector& master_key, - const std::vector& mac_key_context, - const std::vector& enc_key_context) { - // Generate derived key for mac key - std::vector mac_key_server; - std::vector mac_key_client; - std::vector mac_key_part2; - if (!DeriveKey(master_key, mac_key_context, 1, &mac_key_server)) { - return false; - } - if (!DeriveKey(master_key, mac_key_context, 2, &mac_key_part2)) { - return false; - } - mac_key_server.insert(mac_key_server.end(), mac_key_part2.begin(), - mac_key_part2.end()); - - if (!DeriveKey(master_key, mac_key_context, 3, &mac_key_client)) { - return false; - } - if (!DeriveKey(master_key, mac_key_context, 4, &mac_key_part2)) { - return false; - } - mac_key_client.insert(mac_key_client.end(), mac_key_part2.begin(), - mac_key_part2.end()); - - // Generate derived key for encryption key - std::vector enc_key; - if (!DeriveKey(master_key, enc_key_context, 1, &enc_key)) { - return false; - } - - if (LogCategoryEnabled(kLoggingDumpDerivedKeys)) { - LOGI((" mac_key_context = " + wvcdm::b2a_hex(mac_key_context)).c_str()); - LOGI((" enc_key_context = " + wvcdm::b2a_hex(enc_key_context)).c_str()); - LOGI((" mac_key_server = " + wvcdm::b2a_hex(mac_key_server)).c_str()); - LOGI((" mac_key_client = " + wvcdm::b2a_hex(mac_key_client)).c_str()); - LOGI((" enc_key = " + wvcdm::b2a_hex(enc_key)).c_str()); - } - - set_mac_key_server(mac_key_server); - set_mac_key_client(mac_key_client); - set_encryption_key(enc_key); - return true; -} - -bool SessionContext::RSADeriveKeys( - const std::vector& enc_session_key, - const std::vector& mac_key_context, - const std::vector& enc_key_context) { - if (!rsa_key()) { - LOGE("[RSADeriveKeys(): no RSA key set]"); - return false; - } - if (enc_session_key.size() != static_cast(RSA_size(rsa_key()))) { - LOGE("[RSADeriveKeys(): encrypted session key wrong size:%zu, expected %d]", - enc_session_key.size(), RSA_size(rsa_key())); - dump_openssl_error(); - return false; - } - session_key_.resize(RSA_size(rsa_key())); - int decrypted_size = - RSA_private_decrypt(enc_session_key.size(), &enc_session_key[0], - &session_key_[0], rsa_key(), RSA_PKCS1_OAEP_PADDING); - if (-1 == decrypted_size) { - LOGE("[RSADeriveKeys(): error decrypting session key.]"); - dump_openssl_error(); - return false; - } - session_key_.resize(decrypted_size); - if (decrypted_size != static_cast(wvcdm::KEY_SIZE)) { - LOGE("[RSADeriveKeys(): error. session key is wrong size: %d.]", - decrypted_size); - dump_openssl_error(); - session_key_.clear(); - return false; - } - return DeriveKeys(session_key_, mac_key_context, enc_key_context); -} - -// Utility function to generate a message signature -bool SessionContext::GenerateSignature(const uint8_t* message, - size_t message_length, - uint8_t* signature, - size_t* signature_length) { - if (message == NULL || message_length == 0 || signature == NULL || - signature_length == 0) { - LOGE("[OEMCrypto_GenerateSignature(): OEMCrypto_ERROR_INVALID_CONTEXT]"); - return false; - } - - if (mac_key_client_.empty() || - mac_key_client_.size() != wvcdm::MAC_KEY_SIZE) { - LOGE("[GenerateSignature(): No MAC Key]"); - return false; - } - - if (*signature_length < SHA256_DIGEST_LENGTH) { - *signature_length = SHA256_DIGEST_LENGTH; - return false; - } - - unsigned int md_len = *signature_length; - if (HMAC(EVP_sha256(), &mac_key_client_[0], mac_key_client_.size(), message, - message_length, signature, &md_len)) { - *signature_length = md_len; - return true; - } - return false; -} - -size_t SessionContext::RSASignatureSize() { - if (!rsa_key()) { - LOGE("[GenerateRSASignature(): no RSA key set]"); - return 0; - } - return static_cast(RSA_size(rsa_key())); -} - -OEMCryptoResult SessionContext::GenerateRSASignature( - const uint8_t* message, size_t message_length, uint8_t* signature, - size_t* signature_length, RSA_Padding_Scheme padding_scheme) { - if (message == NULL || message_length == 0 || signature == NULL || - signature_length == 0) { - LOGE("[GenerateRSASignature(): OEMCrypto_ERROR_INVALID_CONTEXT]"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - if (!rsa_key()) { - LOGE("[GenerateRSASignature(): no RSA key set]"); - return OEMCrypto_ERROR_INVALID_RSA_KEY; - } - if (*signature_length < static_cast(RSA_size(rsa_key()))) { - *signature_length = RSA_size(rsa_key()); - return OEMCrypto_ERROR_SHORT_BUFFER; - } - if ((padding_scheme & allowed_schemes_) != padding_scheme) { - LOGE("[GenerateRSASignature(): padding_scheme not allowed]"); - return OEMCrypto_ERROR_INVALID_RSA_KEY; - } - // This is the standard padding scheme used for license requests. - if (padding_scheme == kSign_RSASSA_PSS) { - // Hash the message using SHA1. - uint8_t hash[SHA_DIGEST_LENGTH]; - if (!SHA1(message, message_length, hash)) { - LOGE("[GeneratRSASignature(): error creating signature hash.]"); - dump_openssl_error(); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - - // Add PSS padding. - std::vector padded_digest(*signature_length); - int status = RSA_padding_add_PKCS1_PSS_mgf1( - rsa_key(), &padded_digest[0], hash, EVP_sha1(), NULL, kPssSaltLength); - if (status == -1) { - LOGE("[GeneratRSASignature(): error padding hash.]"); - dump_openssl_error(); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - - // Encrypt PSS padded digest. - status = RSA_private_encrypt(*signature_length, &padded_digest[0], - signature, rsa_key(), RSA_NO_PADDING); - if (status == -1) { - LOGE("[GeneratRSASignature(): error in private encrypt.]"); - dump_openssl_error(); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - // This is the alternate padding scheme used by cast receivers only. - } else if (padding_scheme == kSign_PKCS1_Block1) { - if (message_length > 83) { - LOGE("[GeneratRSASignature(): RSA digest too large.]"); - return OEMCrypto_ERROR_SIGNATURE_FAILURE; - } - // Pad the message with PKCS1 padding, and then encrypt. - size_t status = RSA_private_encrypt(message_length, message, signature, - rsa_key(), RSA_PKCS1_PADDING); - if (status != *signature_length) { - LOGE("[GeneratRSASignature(): error in RSA private encrypt. status=%d]", - status); - dump_openssl_error(); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - } else { // Bad RSA_Padding_Scheme - return OEMCrypto_ERROR_INVALID_RSA_KEY; - } - return OEMCrypto_SUCCESS; -} - -// Validate message signature -bool SessionContext::ValidateMessage(const uint8_t* given_message, - size_t message_length, - const uint8_t* given_signature, - size_t signature_length) { - if (signature_length != SHA256_DIGEST_LENGTH) { - return false; - } - uint8_t computed_signature[SHA256_DIGEST_LENGTH]; - memset(computed_signature, 0, SHA256_DIGEST_LENGTH); - unsigned int md_len = SHA256_DIGEST_LENGTH; - if (!HMAC(EVP_sha256(), &mac_key_server_[0], mac_key_server_.size(), - given_message, message_length, computed_signature, &md_len)) { - LOGE("ValidateMessage: Could not compute signature."); - return false; - } - if (memcmp(given_signature, computed_signature, signature_length)) { - LOGE("Invalid signature given: %s", - wvcdm::HexEncode(given_signature, signature_length).c_str()); - LOGE("Invalid signature computed: %s", - wvcdm::HexEncode(computed_signature, signature_length).c_str()); - return false; - } - return true; -} - -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. - 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; - default: - if ((key_control_block.control_bits() & kControlNonceEnabled) && - (!CheckNonce(key_control_block.nonce()))) { - LOGE("LoadKeys: BAD Nonce"); - return OEMCrypto_ERROR_INVALID_NONCE; - } - } - return OEMCrypto_SUCCESS; -} - -void SessionContext::StartTimer() { timer_start_ = time(NULL); } - -uint32_t SessionContext::CurrentTimer() { - time_t now = time(NULL); - return now - timer_start_; -} - -OEMCryptoResult SessionContext::LoadKeys( - const uint8_t* message, size_t message_length, const uint8_t* signature, - size_t signature_length, const uint8_t* enc_mac_key_iv, - const uint8_t* enc_mac_keys, size_t num_keys, - const OEMCrypto_KeyObject* key_array, const uint8_t* pst, - size_t pst_length) { - // Validate message signature - if (!ValidateMessage(message, message_length, signature, signature_length)) { - return OEMCrypto_ERROR_SIGNATURE_FAILURE; - } - - StartTimer(); - - // If there are already keys installed in this session, then we can load - // a shared license. - bool second_license = (session_keys_.size() > 0); - - // Decrypt and install keys in key object - // Each key will have a key control block. They will all have the same nonce. - OEMCryptoResult status = OEMCrypto_SUCCESS; - std::vector key_id; - std::vector enc_key_data; - std::vector key_data_iv; - std::vector key_control; - std::vector key_control_iv; - 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); - enc_key_data.assign(key_array[i].key_data, - key_array[i].key_data + key_array[i].key_data_length); - 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 = OEMCrypto_ERROR_UNKNOWN_FAILURE; - break; - } - key_control.assign(key_array[i].key_control, - key_array[i].key_control + wvcdm::KEY_CONTROL_SIZE); - key_control_iv.assign(key_array[i].key_control_iv, - key_array[i].key_control_iv + wvcdm::KEY_IV_SIZE); - - OEMCryptoResult result = InstallKey( - key_id, enc_key_data, key_data_iv, key_control, key_control_iv, - key_array[i].cipher_mode == OEMCrypto_CipherMode_CTR, second_license); - if (result != OEMCrypto_SUCCESS) { - status = result; - break; - } - } - FlushNonces(); - if (status != OEMCrypto_SUCCESS) return status; - - // enc_mac_key can be NULL if license renewal is not supported - if (enc_mac_keys != NULL) { - // V2.1 license protocol: update mac keys after processing license response - const std::vector enc_mac_keys_str = std::vector( - enc_mac_keys, enc_mac_keys + 2 * wvcdm::MAC_KEY_SIZE); - const std::vector enc_mac_key_iv_str = std::vector( - enc_mac_key_iv, enc_mac_key_iv + wvcdm::KEY_IV_SIZE); - - if (!UpdateMacKeys(enc_mac_keys_str, enc_mac_key_iv_str)) { - LOGE("Failed to update mac keys.\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 mac keys do not match.\n"); - return OEMCrypto_ERROR_WRONG_KEYS; - } - if (usage_entry_->Inactive()) return OEMCrypto_ERROR_LICENSE_INACTIVE; - break; - } - } - return OEMCrypto_SUCCESS; -} - -OEMCryptoResult SessionContext::InstallKey( - const KeyId& key_id, const std::vector& key_data, - const std::vector& key_data_iv, - const std::vector& key_control, - const std::vector& key_control_iv, bool ctr_mode, - bool second_license) { - // Decrypt encrypted key_data using derived encryption key and offered iv - std::vector content_key; - std::vector key_control_str; - - if (!DecryptMessage(encryption_key_, key_data_iv, key_data, &content_key)) { - LOGE("[Installkey(): Could not decrypt key data]"); - 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()); - } - - // Key control must be supplied by license server - if (key_control.empty()) { - LOGE("[Installkey(): WARNING: No Key Control]"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - if (key_control_iv.empty()) { - LOGE("[Installkey(): ERROR: No Key Control IV]"); - 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 OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - - KeyControlBlock key_control_block(key_control_str); - if (!key_control_block.valid()) { - LOGE("Error parsing key control."); - 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 OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - uint8_t minimum_patch_level = - (key_control_block.control_bits() & kControlSecurityPatchLevelMask) >> - kControlSecurityPatchLevelShift; - if (minimum_patch_level > OEMCrypto_Security_Patch_Level()) { - LOGE("[InstallKey(): security patch level: %d. Minimum:%d]", - OEMCrypto_Security_Patch_Level(), minimum_patch_level); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - - OEMCryptoResult result = CheckNonceOrEntry(key_control_block); - if (result != OEMCrypto_SUCCESS) { - LOGE("LoadKeys: Failed Nonce/PST check."); - return result; - } - - if (key_control_block.control_bits() & kSharedLicense) { - if (!second_license) { - LOGE("LoadKeys: Shared License, but no keys previously loaded."); - return OEMCrypto_ERROR_MISSING_MASTER; - } - } - - Key key(content_key, key_control_block, ctr_mode); - session_keys_.Insert(key_id, key); - return OEMCrypto_SUCCESS; -} - -bool SessionContext::InstallRSAEncryptedKey( - const uint8_t* encrypted_message_key, size_t encrypted_message_key_length) { - encryption_key_.resize(RSA_size(rsa_key())); - int decrypted_size = RSA_private_decrypt( - encrypted_message_key_length, encrypted_message_key, &encryption_key_[0], - rsa_key(), RSA_PKCS1_OAEP_PADDING); - if (-1 == decrypted_size) { - LOGE("[RSADeriveKeys(): error decrypting session key.]"); - dump_openssl_error(); - return false; - } - encryption_key_.resize(decrypted_size); - if (decrypted_size != static_cast(wvcdm::KEY_SIZE)) { - LOGE("[RSADeriveKeys(): error. session key is wrong size: %d.]", - decrypted_size); - dump_openssl_error(); - encryption_key_.clear(); - return false; - } - return true; -} - -OEMCryptoResult SessionContext::RefreshKey( - const KeyId& key_id, const std::vector& key_control, - const std::vector& key_control_iv) { - if (key_id.empty()) { - // Key control is not encrypted if key id is NULL - KeyControlBlock key_control_block(key_control); - if (!key_control_block.valid()) { - LOGE("Parse key control error."); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - if ((key_control_block.control_bits() & kControlNonceEnabled) && - (!CheckNonce(key_control_block.nonce()))) { - LOGE("KCB: BAD Nonce"); - return OEMCrypto_ERROR_INVALID_NONCE; - } - // Apply duration to all keys in this session - session_keys_.UpdateDuration(key_control_block); - return OEMCrypto_SUCCESS; - } - - Key* content_key = session_keys_.Find(key_id); - - if (NULL == content_key) { - if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { - LOGD("Error: no matching content key."); - } - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - - if (key_control.empty()) { - if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { - LOGD("Error: no key_control."); - } - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - - const std::vector content_key_value = content_key->value(); - - // Decrypt encrypted key control block - std::vector control; - if (key_control_iv.empty()) { - if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { - LOGD("Key control block is NOT encrypted."); - } - control = key_control; - } else { - if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { - LOGD("Key control block is encrypted."); - } - if (!DecryptMessage(content_key_value, key_control_iv, key_control, - &control)) { - if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { - LOGD("Error decrypting key control block."); - } - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - } - - KeyControlBlock key_control_block(control); - if (!key_control_block.valid()) { - if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { - LOGD("Parse key control error."); - } - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - if ((key_control_block.control_bits() & kControlNonceEnabled) && - (!CheckNonce(key_control_block.nonce()))) { - LOGE("KCB: BAD Nonce"); - return OEMCrypto_ERROR_INVALID_NONCE; - } - content_key->UpdateDuration(key_control_block); - return OEMCrypto_SUCCESS; -} - -bool SessionContext::DecryptRSAKey(const uint8_t* enc_rsa_key, - size_t enc_rsa_key_length, - const uint8_t* enc_rsa_key_iv, - uint8_t* pkcs8_rsa_key) { - // Decrypt rsa key with keybox. - uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; - memcpy(iv_buffer, enc_rsa_key_iv, wvcdm::KEY_IV_SIZE); - AES_KEY aes_key; - AES_set_decrypt_key(&encryption_key_[0], 128, &aes_key); - AES_cbc_encrypt(enc_rsa_key, pkcs8_rsa_key, enc_rsa_key_length, &aes_key, - iv_buffer, AES_DECRYPT); - return true; -} - -bool SessionContext::EncryptRSAKey(const uint8_t* pkcs8_rsa_key, - size_t enc_rsa_key_length, - const uint8_t* enc_rsa_key_iv, - uint8_t* enc_rsa_key) { - // Encrypt rsa key with keybox. - uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; - memcpy(iv_buffer, enc_rsa_key_iv, wvcdm::KEY_IV_SIZE); - AES_KEY aes_key; - AES_set_encrypt_key(&encryption_key_[0], 128, &aes_key); - AES_cbc_encrypt(pkcs8_rsa_key, enc_rsa_key, enc_rsa_key_length, &aes_key, - iv_buffer, AES_ENCRYPT); - return true; -} - -bool SessionContext::LoadRSAKey(const uint8_t* pkcs8_rsa_key, - size_t rsa_key_length) { - rsa_key_.reset(); - if (rsa_key_length < 8) { - LOGE("[LoadRSAKey(): Very Short Buffer]"); - return false; - } - if ((memcmp(pkcs8_rsa_key, "SIGN", 4) == 0)) { - uint32_t* schemes_n = (uint32_t*)(pkcs8_rsa_key + 4); - allowed_schemes_ = htonl(*schemes_n); - pkcs8_rsa_key += 8; - rsa_key_length -= 8; - } else { - allowed_schemes_ = kSign_RSASSA_PSS; - } - return rsa_key_.LoadPkcs8RsaKey(pkcs8_rsa_key, rsa_key_length); -} - -OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string, - uint32_t use_type, - OEMCryptoBufferType buffer_type) { - const KeyControlBlock& control = current_content_key()->control(); - if (use_type && (!(control.control_bits() & use_type))) { - LOGE("[%s(): control bit says not allowed.", log_string.c_str()); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - if (control.control_bits() & kControlDataPathSecure) { - if (!ce_->config_closed_platform() && - buffer_type == OEMCrypto_BufferType_Clear) { - LOGE("[%s(): Secure key with insecure buffer]", log_string.c_str()); - return OEMCrypto_ERROR_DECRYPT_FAILED; - } - } - if (control.control_bits() & kControlReplayMask) { - if (!CheckUsageEntry()) { - LOGE("[%s(): usage entry not valid]", log_string.c_str()); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - } - if (control.duration() > 0) { - if (control.duration() < CurrentTimer()) { - LOGE("[%s(): key expired.", log_string.c_str()); - return OEMCrypto_ERROR_KEY_EXPIRED; - } - } - if (!ce_->config_local_display_only()) { - // Only look at HDCP and Analog restrictions if the display is non-local. - if (control.control_bits() & kControlHDCPRequired) { - uint8_t required_hdcp = - (control.control_bits() & kControlHDCPVersionMask) >> - kControlHDCPVersionShift; - // For reference implementation, we pretend we can handle the current - // HDCP version. - if (required_hdcp > ce_->config_current_hdcp_capability() || - ce_->config_current_hdcp_capability() == 0) { - return OEMCrypto_ERROR_INSUFFICIENT_HDCP; - } - } - if (control.control_bits() & kControlSRMVersionRequired) { - LOGE("[%s(): control bit says SRM version required.", log_string.c_str()); - return OEMCrypto_ERROR_INSUFFICIENT_HDCP; - } - } - if (!ce_->config_local_display_only() || - buffer_type == OEMCrypto_BufferType_Clear) { - if (control.control_bits() & kControlDisableAnalogOutput) { - LOGE("[%s(): control bit says disable analog.", log_string.c_str()); - return OEMCrypto_ERROR_ANALOG_OUTPUT; - } - } - return OEMCrypto_SUCCESS; -} - -OEMCryptoResult SessionContext::Generic_Encrypt(const uint8_t* in_buffer, - size_t buffer_length, - const uint8_t* iv, - OEMCrypto_Algorithm algorithm, - uint8_t* out_buffer) { - // Check there is a content key - if (current_content_key() == NULL) { - LOGE("[Generic_Encrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); - return OEMCrypto_ERROR_NO_CONTENT_KEY; - } - const std::vector& key = current_content_key()->value(); - // Set the AES key. - if (static_cast(key.size()) != AES_BLOCK_SIZE) { - LOGE("[Generic_Encrypt(): CONTENT_KEY has wrong size: %d", key.size()); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - OEMCryptoResult result = CheckKeyUse("Generic_Encrypt", kControlAllowEncrypt, - OEMCrypto_BufferType_Clear); - if (result != OEMCrypto_SUCCESS) return result; - if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) { - LOGE("[Generic_Encrypt(): algorithm bad."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - if (buffer_length % AES_BLOCK_SIZE != 0) { - LOGE("[Generic_Encrypt(): buffers size bad."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - const uint8_t* key_u8 = &key[0]; - AES_KEY aes_key; - if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) { - LOGE("[Generic_Encrypt(): FAILURE]"); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; - memcpy(iv_buffer, iv, wvcdm::KEY_IV_SIZE); - AES_cbc_encrypt(in_buffer, out_buffer, buffer_length, &aes_key, iv_buffer, - AES_ENCRYPT); - return OEMCrypto_SUCCESS; -} - -OEMCryptoResult SessionContext::Generic_Decrypt(const uint8_t* in_buffer, - size_t buffer_length, - const uint8_t* iv, - OEMCrypto_Algorithm algorithm, - uint8_t* out_buffer) { - // Check there is a content key - if (current_content_key() == NULL) { - LOGE("[Generic_Decrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); - return OEMCrypto_ERROR_NO_CONTENT_KEY; - } - const std::vector& key = current_content_key()->value(); - // Set the AES key. - if (static_cast(key.size()) != AES_BLOCK_SIZE) { - LOGE("[Generic_Decrypt(): CONTENT_KEY has wrong size."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - OEMCryptoResult result = CheckKeyUse("Generic_Decrypt", kControlAllowDecrypt, - OEMCrypto_BufferType_Clear); - if (result != OEMCrypto_SUCCESS) return result; - - if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) { - LOGE("[Generic_Decrypt(): bad algorithm."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - if (buffer_length % AES_BLOCK_SIZE != 0) { - LOGE("[Generic_Decrypt(): bad buffer size."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - const uint8_t* key_u8 = &key[0]; - AES_KEY aes_key; - if (AES_set_decrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) { - LOGE("[Generic_Decrypt(): FAILURE]"); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; - memcpy(iv_buffer, iv, wvcdm::KEY_IV_SIZE); - AES_cbc_encrypt(in_buffer, out_buffer, buffer_length, &aes_key, iv_buffer, - AES_DECRYPT); - return OEMCrypto_SUCCESS; -} - -OEMCryptoResult SessionContext::Generic_Sign(const uint8_t* in_buffer, - size_t buffer_length, - OEMCrypto_Algorithm algorithm, - uint8_t* signature, - size_t* signature_length) { - // Check there is a content key - if (current_content_key() == NULL) { - LOGE("[Generic_Sign(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); - return OEMCrypto_ERROR_NO_CONTENT_KEY; - } - if (*signature_length < SHA256_DIGEST_LENGTH) { - *signature_length = SHA256_DIGEST_LENGTH; - LOGE("[Generic_Sign(): bad signature length."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - const std::vector& key = current_content_key()->value(); - if (static_cast(key.size()) != SHA256_DIGEST_LENGTH) { - LOGE("[Generic_Sign(): CONTENT_KEY has wrong size; %d", key.size()); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - OEMCryptoResult result = CheckKeyUse("Generic_Sign", kControlAllowSign, - OEMCrypto_BufferType_Clear); - if (result != OEMCrypto_SUCCESS) return result; - if (algorithm != OEMCrypto_HMAC_SHA256) { - LOGE("[Generic_Sign(): bad algorithm."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - unsigned int md_len = *signature_length; - if (HMAC(EVP_sha256(), &key[0], key.size(), in_buffer, buffer_length, - signature, &md_len)) { - *signature_length = md_len; - return OEMCrypto_SUCCESS; - } - LOGE("[Generic_Sign(): hmac failed."); - dump_openssl_error(); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; -} - -OEMCryptoResult SessionContext::Generic_Verify(const uint8_t* in_buffer, - size_t buffer_length, - OEMCrypto_Algorithm algorithm, - const uint8_t* signature, - size_t signature_length) { - // Check there is a content key - if (current_content_key() == NULL) { - LOGE("[Decrypt_Verify(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - if (signature_length < SHA256_DIGEST_LENGTH) { - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - const std::vector& key = current_content_key()->value(); - if (static_cast(key.size()) != SHA256_DIGEST_LENGTH) { - LOGE("[Generic_Verify(): CONTENT_KEY has wrong size: %d", key.size()); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - OEMCryptoResult result = CheckKeyUse("Generic_Verify", kControlAllowVerify, - OEMCrypto_BufferType_Clear); - if (result != OEMCrypto_SUCCESS) return result; - if (algorithm != OEMCrypto_HMAC_SHA256) { - LOGE("[Generic_Verify(): bad algorithm."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - unsigned int md_len = signature_length; - uint8_t computed_signature[SHA256_DIGEST_LENGTH]; - if (HMAC(EVP_sha256(), &key[0], key.size(), in_buffer, buffer_length, - computed_signature, &md_len)) { - if (0 == memcmp(signature, computed_signature, SHA256_DIGEST_LENGTH)) { - return OEMCrypto_SUCCESS; - } else { - return OEMCrypto_ERROR_SIGNATURE_FAILURE; - } - } - LOGE("[Generic_Verify(): HMAC failed."); - dump_openssl_error(); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; -} - -bool SessionContext::UpdateMacKeys(const std::vector& enc_mac_keys, - const std::vector& iv) { - // Decrypt mac key from enc_mac_key using device_keya - std::vector mac_keys; - if (!DecryptMessage(encryption_key_, iv, enc_mac_keys, &mac_keys)) { - return false; - } - mac_key_server_ = std::vector( - mac_keys.begin(), mac_keys.begin() + wvcdm::MAC_KEY_SIZE); - mac_key_client_ = std::vector(mac_keys.begin() + wvcdm::MAC_KEY_SIZE, - mac_keys.end()); - return true; -} - -bool SessionContext::QueryKeyControlBlock(const KeyId& key_id, uint32_t* data) { - const Key* content_key = session_keys_.Find(key_id); - if (LogCategoryEnabled(kLoggingTraceDecryption)) { - LOGI(("Select Key: key_id = " + wvcdm::b2a_hex(key_id)).c_str()); - if (content_key) { - LOGI(("Select Key: key = " + wvcdm::b2a_hex(content_key->value())) - .c_str()); - } else { - LOGI("Select Key: key = null."); - } - } - if (NULL == content_key) { - LOGE("[QueryKeyControlBlock(): No key matches key id]"); - return false; - } - data[0] = 0; // verification optional. - data[1] = htonl(content_key->control().duration()); - data[2] = 0; // nonce optional. - data[3] = htonl(content_key->control().control_bits()); - return true; -} - -OEMCryptoResult SessionContext::SelectContentKey(const KeyId& key_id) { - const Key* content_key = session_keys_.Find(key_id); - - if (LogCategoryEnabled(kLoggingTraceDecryption)) { - LOGI((" Select Key: key_id = " + wvcdm::b2a_hex(key_id)).c_str()); - LOGI((" Select Key: key = " + wvcdm::b2a_hex(content_key->value())) - .c_str()); - } - - if (NULL == content_key) { - LOGE("[SelectContentKey(): No key matches key id]"); - return OEMCrypto_ERROR_NO_CONTENT_KEY; - } - current_content_key_ = content_key; - const KeyControlBlock& control = current_content_key()->control(); - if (control.duration() > 0) { - if (control.duration() < CurrentTimer()) { - LOGE("[SelectContentKey(): KEY_EXPIRED]"); - return OEMCrypto_ERROR_KEY_EXPIRED; - } - } - return OEMCrypto_SUCCESS; -} - -void SessionContext::AddNonce(uint32_t nonce) { nonce_table_.AddNonce(nonce); } - -bool SessionContext::CheckNonce(uint32_t nonce) { - return nonce_table_.CheckNonce(nonce); -} - -void SessionContext::FlushNonces() { nonce_table_.Flush(); } - -bool SessionContext::CheckUsageEntry() { - if (!usage_entry_) return false; - return usage_entry_->CheckForUse(); -} - -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& 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& pst) { - if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT; - usage_entry_->Deactivate(pst); - return OEMCrypto_SUCCESS; -} - -OEMCryptoResult SessionContext::ReportUsage(const std::vector& 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& pst) { - if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT; - return ce_->usage_table().CopyOldUsageEntry(usage_entry_, pst); -} +// Note: The class CryptoEngine is configured at compile time by compiling in +// different device property files. The methods in this file are generic to +// all configurations. See the files oemcrypto_engine_device_properties*.cpp +// for methods that are configured for specific configurations. CryptoEngine::CryptoEngine(wvcdm::FileSystem* file_system) : root_of_trust_(config_provisioning_method()), @@ -1129,301 +76,4 @@ SessionContext* CryptoEngine::FindSession(SessionId sid) { return NULL; } -// Internal utility function to decrypt the message -bool SessionContext::DecryptMessage(const std::vector& key, - const std::vector& iv, - const std::vector& message, - std::vector* decrypted) { - if (key.empty() || iv.empty() || message.empty() || !decrypted) { - LOGE("[DecryptMessage(): OEMCrypto_ERROR_INVALID_CONTEXT]"); - return false; - } - decrypted->resize(message.size()); - uint8_t iv_buffer[16]; - memcpy(iv_buffer, &iv[0], 16); - AES_KEY aes_key; - AES_set_decrypt_key(&key[0], 128, &aes_key); - AES_cbc_encrypt(&message[0], &(decrypted->front()), message.size(), &aes_key, - iv_buffer, AES_DECRYPT); - return true; -} - -OEMCryptoResult SessionContext::DecryptCENC( - const uint8_t* iv, size_t block_offset, - const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, - size_t cipher_data_length, bool is_encrypted, uint8_t* clear_data, - OEMCryptoBufferType buffer_type) { - // If the data is clear, we do not need a current key selected. - if (!is_encrypted) { - if (buffer_type != OEMCrypto_BufferType_Direct) { - memcpy(reinterpret_cast(clear_data), cipher_data, - cipher_data_length); - return OEMCrypto_SUCCESS; - } - // For reference implementation, we quietly drop the clear direct video. - return OEMCrypto_SUCCESS; - } - - // Check there is a content key - if (current_content_key() == NULL) { - LOGE("[DecryptCTR(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); - return OEMCrypto_ERROR_DECRYPT_FAILED; - } - - OEMCryptoResult result = CheckKeyUse("DecryptCENC", 0, buffer_type); - if (result != OEMCrypto_SUCCESS) return result; - - const std::vector& content_key = current_content_key()->value(); - - // Set the AES key. - if (static_cast(content_key.size()) != AES_BLOCK_SIZE) { - LOGE("[DecryptCTR(): CONTENT_KEY has wrong size: %d", content_key.size()); - return OEMCrypto_ERROR_DECRYPT_FAILED; - } - const uint8_t* key_u8 = &content_key[0]; - - if (buffer_type == OEMCrypto_BufferType_Direct) { - // For reference implementation, we quietly drop the decrypted direct video. - return OEMCrypto_SUCCESS; - } - - if (buffer_type == OEMCrypto_BufferType_Secure) { - // For reference implementation, we also quietly drop secure data. - return OEMCrypto_SUCCESS; - } - - if (!current_content_key()->ctr_mode()) { - if (block_offset > 0) return OEMCrypto_ERROR_INVALID_CONTEXT; - return DecryptCBC(key_u8, iv, pattern, cipher_data, cipher_data_length, - clear_data); - } - if (pattern->skip > 0) { - return PatternDecryptCTR(key_u8, iv, block_offset, pattern, cipher_data, - cipher_data_length, clear_data); - } - return DecryptCTR(key_u8, iv, block_offset, cipher_data, cipher_data_length, - clear_data); -} - -OEMCryptoResult SessionContext::DecryptCBC( - const uint8_t* key, const uint8_t* initial_iv, - const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, - size_t cipher_data_length, uint8_t* clear_data) { - AES_KEY aes_key; - AES_set_decrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); - uint8_t iv[AES_BLOCK_SIZE]; - memcpy(iv, &initial_iv[0], AES_BLOCK_SIZE); - - size_t l = 0; - size_t pattern_offset = pattern->offset; - while (l < cipher_data_length) { - size_t size = - std::min(cipher_data_length - l, static_cast(AES_BLOCK_SIZE)); - size_t pattern_length = pattern->encrypt + pattern->skip; - bool skip_block = - (pattern_offset >= pattern->encrypt) && (pattern_length > 0); - if (pattern_length > 0) { - pattern_offset = (pattern_offset + 1) % pattern_length; - } - if (skip_block || (size < AES_BLOCK_SIZE)) { - memcpy(&clear_data[l], &cipher_data[l], size); - } else { - uint8_t aes_output[AES_BLOCK_SIZE]; - AES_decrypt(&cipher_data[l], aes_output, &aes_key); - for (size_t n = 0; n < AES_BLOCK_SIZE; n++) { - clear_data[l + n] = aes_output[n] ^ iv[n]; - } - memcpy(iv, &cipher_data[l], AES_BLOCK_SIZE); - } - l += size; - } - return OEMCrypto_SUCCESS; -} - -OEMCryptoResult SessionContext::PatternDecryptCTR( - const uint8_t* key, const uint8_t* initial_iv, size_t block_offset, - const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, - size_t cipher_data_length, uint8_t* clear_data) { - AES_KEY aes_key; - AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); - uint8_t iv[AES_BLOCK_SIZE]; - memcpy(iv, &initial_iv[0], AES_BLOCK_SIZE); - - size_t l = 0; - size_t pattern_offset = pattern->offset; - while (l < cipher_data_length) { - size_t size = - std::min(cipher_data_length - l, AES_BLOCK_SIZE - block_offset); - size_t pattern_length = pattern->encrypt + pattern->skip; - bool skip_block = - (pattern_offset >= pattern->encrypt) && (pattern_length > 0); - if (pattern_length > 0) { - pattern_offset = (pattern_offset + 1) % pattern_length; - } - if (skip_block) { - memcpy(&clear_data[l], &cipher_data[l], size); - } else { - uint8_t aes_output[AES_BLOCK_SIZE]; - AES_encrypt(iv, aes_output, &aes_key); - for (size_t n = 0; n < size; n++) { - clear_data[l + n] = aes_output[n + block_offset] ^ cipher_data[l + n]; - } - ctr128_inc64(iv); - } - l += size; - block_offset = 0; - } - return OEMCrypto_SUCCESS; -} - -// This is a special case of PatternDecryptCTR with no skip pattern. It uses -// more optimized versions of openssl's implementation of AES CTR mode. -OEMCryptoResult SessionContext::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) { - // Local copy (will be modified). - // Allocated as 64-bit ints to enforce 64-bit alignment for later access as a - // 64-bit value. - uint64_t aes_iv[2]; - assert(sizeof(aes_iv) == AES_BLOCK_SIZE); - // The double-cast is needed to comply with strict aliasing rules. - uint8_t* aes_iv_u8 = - reinterpret_cast(reinterpret_cast(aes_iv)); - memcpy(aes_iv_u8, &iv[0], AES_BLOCK_SIZE); - - // The CENC spec specifies we increment only the low 64 bits of the IV - // counter, and leave the high 64 bits alone. This is different from the - // OpenSSL implementation, which increments the entire 128 bit iv. That is - // why we implement the CTR loop ourselves. - size_t l = 0; - if (block_offset > 0 && l < cipher_data_length) { - // Encrypt the IV. - uint8_t ecount_buf[AES_BLOCK_SIZE]; - - AES_KEY aes_key; - if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) { - LOGE("[DecryptCTR(): FAILURE]"); - return OEMCrypto_ERROR_DECRYPT_FAILED; - } - AES_encrypt(aes_iv_u8, ecount_buf, &aes_key); - for (int n = block_offset; n < AES_BLOCK_SIZE && l < cipher_data_length; - ++n, ++l) { - clear_data[l] = cipher_data[l] ^ ecount_buf[n]; - } - ctr128_inc64(aes_iv_u8); - block_offset = 0; - } - - uint64_t remaining = cipher_data_length - l; - int out_len = 0; - - while (remaining) { - EVP_CIPHER_CTX ctx; - EVP_CIPHER_CTX_init(&ctx); - EVP_CIPHER_CTX_set_padding(&ctx, 0); - if (!EVP_DecryptInit_ex(&ctx, EVP_aes_128_ctr(), NULL, key_u8, aes_iv_u8)) { - LOGE("[DecryptCTR(): EVP_INIT ERROR]"); - return OEMCrypto_ERROR_DECRYPT_FAILED; - } - - // Test the MSB of the counter portion of the initialization vector. If the - // value is 0xFF the counter is near wrapping. In this case we calculate - // the number of bytes we can safely decrypt before the counter wraps. - uint64_t decrypt_length = 0; - if (aes_iv_u8[8] == 0xFF) { - uint64_t bottom_64_bits = wvcdm::ntohll64(aes_iv[1]); - uint64_t bytes_before_iv_wrap = (~bottom_64_bits + 1) * AES_BLOCK_SIZE; - decrypt_length = - bytes_before_iv_wrap < remaining ? bytes_before_iv_wrap : remaining; - } else { - decrypt_length = remaining; - } - - if (!EVP_DecryptUpdate(&ctx, &clear_data[l], &out_len, &cipher_data[l], - decrypt_length)) { - LOGE("[DecryptCTR(): EVP_UPDATE_ERROR]"); - return OEMCrypto_ERROR_DECRYPT_FAILED; - } - l += decrypt_length; - remaining = cipher_data_length - l; - - int final; - if (!EVP_DecryptFinal_ex( - &ctx, &clear_data[cipher_data_length - remaining], & final)) { - LOGE("[DecryptCTR(): EVP_FINAL_ERROR]"); - return OEMCrypto_ERROR_DECRYPT_FAILED; - } - EVP_CIPHER_CTX_cleanup(&ctx); - - // If remaining is not zero, reset the iv before the second pass. - if (remaining) { - memcpy(aes_iv_u8, &iv[0], AES_BLOCK_SIZE); - memset(&aes_iv_u8[8], 0, AES_BLOCK_SIZE / 2); - } - } - - return OEMCrypto_SUCCESS; -} - -void NonceTable::AddNonce(uint32_t nonce) { - int new_slot = -1; - int oldest_slot = -1; - - // Flush any nonces that have been checked but not flushed. - // After flush, nonces will be either valid or invalid. - Flush(); - - for (int i = 0; i < kTableSize; ++i) { - // Increase age of all valid nonces. - if (kNTStateValid == state_[i]) { - ++age_[i]; - if (-1 == oldest_slot) { - oldest_slot = i; - } else { - if (age_[i] > age_[oldest_slot]) { - oldest_slot = i; - } - } - } else { - if (-1 == new_slot) { - age_[i] = 0; - nonces_[i] = nonce; - state_[i] = kNTStateValid; - new_slot = i; - } - } - } - if (-1 == new_slot) { - // reuse oldest - // assert (oldest_slot != -1) - int i = oldest_slot; - age_[i] = 0; - nonces_[i] = nonce; - state_[i] = kNTStateValid; - } -} - -bool NonceTable::CheckNonce(uint32_t nonce) { - for (int i = 0; i < kTableSize; ++i) { - if (kNTStateInvalid != state_[i]) { - if (nonce == nonces_[i]) { - state_[i] = kNTStateFlushPending; - return true; - } - } - } - return false; -} - -void NonceTable::Flush() { - for (int i = 0; i < kTableSize; ++i) { - if (kNTStateFlushPending == state_[i]) { - state_[i] = kNTStateInvalid; - } - } -} - } // namespace wvoec_mock diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h index aeff4f68..d12c7308 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h @@ -2,8 +2,8 @@ // // Mock implementation of OEMCrypto APIs // -#ifndef OEMCRYPTO_ENGINE_MOCK_H_ -#define OEMCRYPTO_ENGINE_MOCK_H_ +#ifndef MOCK_OEMCRYPTO_ENGINE_MOCK_H_ +#define MOCK_OEMCRYPTO_ENGINE_MOCK_H_ #include #include @@ -18,241 +18,14 @@ #include "oemcrypto_auth_mock.h" #include "oemcrypto_key_mock.h" #include "oemcrypto_rsa_key_shared.h" +#include "oemcrypto_session.h" #include "oemcrypto_usage_table_mock.h" #include "wv_cdm_types.h" namespace wvoec_mock { -class SessionContext; -class CryptoEngine; -class UsageTable; -class UsageTableEntry; - -typedef uint32_t SessionId; typedef std::map ActiveSessions; -typedef std::vector KeyId; -typedef std::map KeyMap; - -// SessionKeyTable holds the keys for the current session -class SessionKeyTable { - public: - SessionKeyTable() {} - ~SessionKeyTable(); - - bool Insert(const KeyId key_id, const Key& key_data); - Key* Find(const KeyId key_id); - void Remove(const KeyId key_id); - void UpdateDuration(const KeyControlBlock& control); - size_t size() const { return keys_.size(); } - - private: - KeyMap keys_; - - CORE_DISALLOW_COPY_AND_ASSIGN(SessionKeyTable); -}; - -class NonceTable { - public: - static const int kTableSize = 16; - NonceTable() { - for (int i = 0; i < kTableSize; ++i) { - state_[i] = kNTStateInvalid; - } - } - ~NonceTable() {} - void AddNonce(uint32_t nonce); - bool CheckNonce(uint32_t nonce); - void Flush(); - - private: - enum NonceTableState { - kNTStateInvalid, - kNTStateValid, - kNTStateFlushPending - }; - NonceTableState state_[kTableSize]; - uint32_t age_[kTableSize]; - uint32_t nonces_[kTableSize]; -}; - -class SessionContext { - private: - SessionContext() {} - - public: - SessionContext(CryptoEngine* ce, SessionId sid, const RSA_shared_ptr& rsa_key) - : valid_(true), - ce_(ce), - id_(sid), - current_content_key_(NULL), - rsa_key_(rsa_key), - allowed_schemes_(kSign_RSASSA_PSS), - usage_entry_(NULL), - usage_entry_status_(kNoUsageEntry) {} - ~SessionContext(); - - bool isValid() { return valid_; } - - bool DeriveKeys(const std::vector& master_key, - const std::vector& mac_context, - const std::vector& enc_context); - bool RSADeriveKeys(const std::vector& enc_session_key, - const std::vector& mac_context, - const std::vector& enc_context); - bool GenerateSignature(const uint8_t* message, size_t message_length, - uint8_t* signature, size_t* signature_length); - size_t RSASignatureSize(); - OEMCryptoResult GenerateRSASignature(const uint8_t* message, - size_t message_length, - uint8_t* signature, - size_t* signature_length, - RSA_Padding_Scheme padding_scheme); - bool ValidateMessage(const uint8_t* message, size_t message_length, - const uint8_t* signature, size_t signature_length); - OEMCryptoResult DecryptCENC(const uint8_t* iv, size_t block_offset, - const OEMCrypto_CENCEncryptPatternDesc* pattern, - const uint8_t* cipher_data, - size_t cipher_data_length, bool is_encrypted, - uint8_t* clear_data, - OEMCryptoBufferType buffer_type); - - OEMCryptoResult Generic_Encrypt(const uint8_t* in_buffer, - size_t buffer_length, const uint8_t* iv, - OEMCrypto_Algorithm algorithm, - uint8_t* out_buffer); - OEMCryptoResult Generic_Decrypt(const uint8_t* in_buffer, - size_t buffer_length, const uint8_t* iv, - OEMCrypto_Algorithm algorithm, - uint8_t* out_buffer); - OEMCryptoResult Generic_Sign(const uint8_t* in_buffer, size_t buffer_length, - OEMCrypto_Algorithm algorithm, - uint8_t* signature, size_t* signature_length); - OEMCryptoResult Generic_Verify(const uint8_t* in_buffer, size_t buffer_length, - OEMCrypto_Algorithm algorithm, - const uint8_t* signature, - size_t signature_length); - void StartTimer(); - uint32_t CurrentTimer(); // (seconds). - OEMCryptoResult LoadKeys(const uint8_t* message, size_t message_length, - const uint8_t* signature, size_t signature_length, - const uint8_t* enc_mac_key_iv, - const uint8_t* enc_mac_keys, size_t num_keys, - const OEMCrypto_KeyObject* key_array, - const uint8_t* pst, size_t pst_length); - OEMCryptoResult InstallKey(const KeyId& key_id, - const std::vector& key_data, - const std::vector& key_data_iv, - const std::vector& key_control, - const std::vector& key_control_iv, - bool ctr_mode, bool second_license); - 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, - const uint8_t* wrapped_rsa_key_iv, uint8_t* pkcs8_rsa_key); - bool EncryptRSAKey(const uint8_t* pkcs8_rsa_key, size_t enc_rsa_key_length, - const uint8_t* enc_rsa_key_iv, uint8_t* enc_rsa_key); - bool LoadRSAKey(const uint8_t* pkcs8_rsa_key, size_t rsa_key_length); - OEMCryptoResult RefreshKey(const KeyId& key_id, - const std::vector& key_control, - const std::vector& key_control_iv); - bool UpdateMacKeys(const std::vector& mac_keys, - const std::vector& iv); - bool QueryKeyControlBlock(const KeyId& key_id, uint32_t* data); - OEMCryptoResult SelectContentKey(const KeyId& key_id); - const Key* current_content_key(void) { return current_content_key_; } - void set_mac_key_server(const std::vector& mac_key_server) { - mac_key_server_ = mac_key_server; - } - const std::vector& mac_key_server() { return mac_key_server_; } - void set_mac_key_client(const std::vector& mac_key_client) { - mac_key_client_ = mac_key_client; - } - const std::vector& mac_key_client() { return mac_key_client_; } - - void set_encryption_key(const std::vector& enc_key) { - encryption_key_ = enc_key; - } - const std::vector& encryption_key() { return encryption_key_; } - uint32_t allowed_schemes() const { return allowed_schemes_; } - - void AddNonce(uint32_t nonce); - bool CheckNonce(uint32_t nonce); - void FlushNonces(); - - OEMCryptoResult CreateNewUsageEntry(uint32_t* usage_entry_number); - OEMCryptoResult LoadUsageEntry(uint32_t index, - const std::vector& 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& pst); - OEMCryptoResult ReportUsage(const std::vector& pst, uint8_t* buffer, - size_t* buffer_length); - OEMCryptoResult MoveEntry(uint32_t new_index); - OEMCryptoResult CopyOldUsageEntry(const std::vector& pst); - - private: - bool DeriveKey(const std::vector& key, - const std::vector& context, int counter, - std::vector* out); - bool DecryptMessage(const std::vector& key, - const std::vector& iv, - const std::vector& message, - std::vector* decrypted); - // 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, - size_t cipher_data_length, uint8_t* clear_data); - OEMCryptoResult PatternDecryptCTR( - const uint8_t* key, const uint8_t* iv, size_t block_offset, - const OEMCrypto_CENCEncryptPatternDesc* pattern, - const uint8_t* cipher_data, size_t cipher_data_length, - uint8_t* clear_data); - 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); - // 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(); } - - bool valid_; - CryptoEngine* ce_; - SessionId id_; - std::vector mac_key_server_; - std::vector mac_key_client_; - std::vector encryption_key_; - std::vector session_key_; - const Key* current_content_key_; - SessionKeyTable session_keys_; - NonceTable nonce_table_; - RSA_shared_ptr rsa_key_; - uint32_t allowed_schemes_; // for RSA signatures. - 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); -}; - class CryptoEngine { public: CryptoEngine(wvcdm::FileSystem* file_system); @@ -333,4 +106,4 @@ class CryptoEngine { } // namespace wvoec_mock -#endif // OEMCRYPTO_ENGINE_MOCK_H_ +#endif // MOCK_OEMCRYPTO_ENGINE_MOCK_H_ diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp index 2c8b6d98..c7416b1a 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp @@ -21,6 +21,7 @@ #include "log.h" #include "oemcrypto_engine_mock.h" #include "oemcrypto_logging.h" +#include "oemcrypto_session.h" #include "oemcrypto_usage_table_mock.h" #include "string_conversions.h" #include "wv_cdm_constants.h" diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_nonce_table.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_nonce_table.cpp new file mode 100644 index 00000000..e7f5e911 --- /dev/null +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_nonce_table.cpp @@ -0,0 +1,67 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Mock implementation of OEMCrypto APIs +// +#include "oemcrypto_nonce_table.h" + +namespace wvoec_mock { + +void NonceTable::AddNonce(uint32_t nonce) { + int new_slot = -1; + int oldest_slot = -1; + + // Flush any nonces that have been checked but not flushed. + // After flush, nonces will be either valid or invalid. + Flush(); + + for (int i = 0; i < kTableSize; ++i) { + // Increase age of all valid nonces. + if (kNTStateValid == state_[i]) { + ++age_[i]; + if (-1 == oldest_slot) { + oldest_slot = i; + } else { + if (age_[i] > age_[oldest_slot]) { + oldest_slot = i; + } + } + } else { + if (-1 == new_slot) { + age_[i] = 0; + nonces_[i] = nonce; + state_[i] = kNTStateValid; + new_slot = i; + } + } + } + if (-1 == new_slot) { + // reuse oldest + // assert (oldest_slot != -1) + int i = oldest_slot; + age_[i] = 0; + nonces_[i] = nonce; + state_[i] = kNTStateValid; + } +} + +bool NonceTable::CheckNonce(uint32_t nonce) { + for (int i = 0; i < kTableSize; ++i) { + if (kNTStateInvalid != state_[i]) { + if (nonce == nonces_[i]) { + state_[i] = kNTStateFlushPending; + return true; + } + } + } + return false; +} + +void NonceTable::Flush() { + for (int i = 0; i < kTableSize; ++i) { + if (kNTStateFlushPending == state_[i]) { + state_[i] = kNTStateInvalid; + } + } +} + +} // namespace wvoec_mock diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_nonce_table.h b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_nonce_table.h new file mode 100644 index 00000000..493c1a83 --- /dev/null +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_nonce_table.h @@ -0,0 +1,38 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Mock implementation of OEMCrypto APIs +// +#ifndef MOCK_OEMCRYPTO_NONCE_TABLE_H_ +#define MOCK_OEMCRYPTO_NONCE_TABLE_H_ + +#include + +namespace wvoec_mock { + +class NonceTable { + public: + static const int kTableSize = 16; + NonceTable() { + for (int i = 0; i < kTableSize; ++i) { + state_[i] = kNTStateInvalid; + } + } + ~NonceTable() {} + void AddNonce(uint32_t nonce); + bool CheckNonce(uint32_t nonce); + void Flush(); + + private: + enum NonceTableState { + kNTStateInvalid, + kNTStateValid, + kNTStateFlushPending + }; + NonceTableState state_[kTableSize]; + uint32_t age_[kTableSize]; + uint32_t nonces_[kTableSize]; +}; + +} // namespace wvoec_mock + +#endif // MOCK_OEMCRYPTO_NONCE_TABLE_H_ diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.cpp new file mode 100644 index 00000000..17abccbd --- /dev/null +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.cpp @@ -0,0 +1,1288 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Mock implementation of OEMCrypto APIs +// +#include "oemcrypto_session.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "keys.h" +#include "log.h" +#include "oemcrypto_engine_mock.h" +#include "oemcrypto_key_mock.h" +#include "oemcrypto_logging.h" +#include "oemcrypto_rsa_key_shared.h" +#include "string_conversions.h" +#include "wv_cdm_constants.h" + +static const int kPssSaltLength = 20; + +namespace { + +// Increment counter for AES-CTR. The CENC spec specifies we increment only +// the low 64 bits of the IV counter, and leave the high 64 bits alone. +void ctr128_inc64(uint8_t* counter) { + uint32_t n = 16; + do { + if (++counter[--n] != 0) return; + } while (n > 8); +} + +void dump_openssl_error() { + while (unsigned long err = ERR_get_error()) { + char buffer[120]; + LOGE("openssl error -- %lu -- %s", err, ERR_error_string(err, buffer)); + } +} + +} // namespace + +namespace wvoec_mock { + +SessionContext::~SessionContext() { + if (usage_entry_) { + delete usage_entry_; + usage_entry_ = NULL; + } +} + +// Internal utility function to derive key using CMAC-128 +bool SessionContext::DeriveKey(const std::vector& key, + const std::vector& context, int counter, + std::vector* out) { + if (key.empty() || counter > 4 || context.empty() || out == NULL) { + LOGE("[DeriveKey(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return false; + } + + const EVP_CIPHER* cipher = EVP_aes_128_cbc(); + CMAC_CTX* cmac_ctx = CMAC_CTX_new(); + + if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, 0)) { + LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]"); + return false; + } + + std::vector message; + message.push_back(counter); + message.insert(message.end(), context.begin(), context.end()); + + if (!CMAC_Update(cmac_ctx, &message[0], message.size())) { + LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]"); + return false; + } + + size_t reslen; + uint8_t res[128]; + if (!CMAC_Final(cmac_ctx, res, &reslen)) { + LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]"); + return false; + } + + out->assign(res, res + reslen); + + CMAC_CTX_free(cmac_ctx); + + return true; +} + +bool SessionContext::DeriveKeys(const std::vector& master_key, + const std::vector& mac_key_context, + const std::vector& enc_key_context) { + // Generate derived key for mac key + std::vector mac_key_server; + std::vector mac_key_client; + std::vector mac_key_part2; + if (!DeriveKey(master_key, mac_key_context, 1, &mac_key_server)) { + return false; + } + if (!DeriveKey(master_key, mac_key_context, 2, &mac_key_part2)) { + return false; + } + mac_key_server.insert(mac_key_server.end(), mac_key_part2.begin(), + mac_key_part2.end()); + + if (!DeriveKey(master_key, mac_key_context, 3, &mac_key_client)) { + return false; + } + if (!DeriveKey(master_key, mac_key_context, 4, &mac_key_part2)) { + return false; + } + mac_key_client.insert(mac_key_client.end(), mac_key_part2.begin(), + mac_key_part2.end()); + + // Generate derived key for encryption key + std::vector enc_key; + if (!DeriveKey(master_key, enc_key_context, 1, &enc_key)) { + return false; + } + + if (LogCategoryEnabled(kLoggingDumpDerivedKeys)) { + LOGI((" mac_key_context = " + wvcdm::b2a_hex(mac_key_context)).c_str()); + LOGI((" enc_key_context = " + wvcdm::b2a_hex(enc_key_context)).c_str()); + LOGI((" mac_key_server = " + wvcdm::b2a_hex(mac_key_server)).c_str()); + LOGI((" mac_key_client = " + wvcdm::b2a_hex(mac_key_client)).c_str()); + LOGI((" enc_key = " + wvcdm::b2a_hex(enc_key)).c_str()); + } + + set_mac_key_server(mac_key_server); + set_mac_key_client(mac_key_client); + set_encryption_key(enc_key); + return true; +} + +bool SessionContext::RSADeriveKeys( + const std::vector& enc_session_key, + const std::vector& mac_key_context, + const std::vector& enc_key_context) { + if (!rsa_key()) { + LOGE("[RSADeriveKeys(): no RSA key set]"); + return false; + } + if (enc_session_key.size() != static_cast(RSA_size(rsa_key()))) { + LOGE("[RSADeriveKeys(): encrypted session key wrong size:%zu, expected %d]", + enc_session_key.size(), RSA_size(rsa_key())); + dump_openssl_error(); + return false; + } + session_key_.resize(RSA_size(rsa_key())); + int decrypted_size = + RSA_private_decrypt(enc_session_key.size(), &enc_session_key[0], + &session_key_[0], rsa_key(), RSA_PKCS1_OAEP_PADDING); + if (-1 == decrypted_size) { + LOGE("[RSADeriveKeys(): error decrypting session key.]"); + dump_openssl_error(); + return false; + } + session_key_.resize(decrypted_size); + if (decrypted_size != static_cast(wvcdm::KEY_SIZE)) { + LOGE("[RSADeriveKeys(): error. session key is wrong size: %d.]", + decrypted_size); + dump_openssl_error(); + session_key_.clear(); + return false; + } + return DeriveKeys(session_key_, mac_key_context, enc_key_context); +} + +// Utility function to generate a message signature +bool SessionContext::GenerateSignature(const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length) { + if (message == NULL || message_length == 0 || signature == NULL || + signature_length == 0) { + LOGE("[OEMCrypto_GenerateSignature(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return false; + } + + if (mac_key_client_.empty() || + mac_key_client_.size() != wvcdm::MAC_KEY_SIZE) { + LOGE("[GenerateSignature(): No MAC Key]"); + return false; + } + + if (*signature_length < SHA256_DIGEST_LENGTH) { + *signature_length = SHA256_DIGEST_LENGTH; + return false; + } + + unsigned int md_len = *signature_length; + if (HMAC(EVP_sha256(), &mac_key_client_[0], mac_key_client_.size(), message, + message_length, signature, &md_len)) { + *signature_length = md_len; + return true; + } + return false; +} + +size_t SessionContext::RSASignatureSize() { + if (!rsa_key()) { + LOGE("[GenerateRSASignature(): no RSA key set]"); + return 0; + } + return static_cast(RSA_size(rsa_key())); +} + +OEMCryptoResult SessionContext::GenerateRSASignature( + const uint8_t* message, size_t message_length, uint8_t* signature, + size_t* signature_length, RSA_Padding_Scheme padding_scheme) { + if (message == NULL || message_length == 0 || signature == NULL || + signature_length == 0) { + LOGE("[GenerateRSASignature(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!rsa_key()) { + LOGE("[GenerateRSASignature(): no RSA key set]"); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + if (*signature_length < static_cast(RSA_size(rsa_key()))) { + *signature_length = RSA_size(rsa_key()); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if ((padding_scheme & allowed_schemes_) != padding_scheme) { + LOGE("[GenerateRSASignature(): padding_scheme not allowed]"); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + // This is the standard padding scheme used for license requests. + if (padding_scheme == kSign_RSASSA_PSS) { + // Hash the message using SHA1. + uint8_t hash[SHA_DIGEST_LENGTH]; + if (!SHA1(message, message_length, hash)) { + LOGE("[GeneratRSASignature(): error creating signature hash.]"); + dump_openssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Add PSS padding. + std::vector padded_digest(*signature_length); + int status = RSA_padding_add_PKCS1_PSS_mgf1( + rsa_key(), &padded_digest[0], hash, EVP_sha1(), NULL, kPssSaltLength); + if (status == -1) { + LOGE("[GeneratRSASignature(): error padding hash.]"); + dump_openssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Encrypt PSS padded digest. + status = RSA_private_encrypt(*signature_length, &padded_digest[0], + signature, rsa_key(), RSA_NO_PADDING); + if (status == -1) { + LOGE("[GeneratRSASignature(): error in private encrypt.]"); + dump_openssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + // This is the alternate padding scheme used by cast receivers only. + } else if (padding_scheme == kSign_PKCS1_Block1) { + if (message_length > 83) { + LOGE("[GeneratRSASignature(): RSA digest too large.]"); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + // Pad the message with PKCS1 padding, and then encrypt. + size_t status = RSA_private_encrypt(message_length, message, signature, + rsa_key(), RSA_PKCS1_PADDING); + if (status != *signature_length) { + LOGE("[GeneratRSASignature(): error in RSA private encrypt. status=%d]", + status); + dump_openssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } else { // Bad RSA_Padding_Scheme + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + return OEMCrypto_SUCCESS; +} + +// Validate message signature +bool SessionContext::ValidateMessage(const uint8_t* given_message, + size_t message_length, + const uint8_t* given_signature, + size_t signature_length) { + if (signature_length != SHA256_DIGEST_LENGTH) { + return false; + } + uint8_t computed_signature[SHA256_DIGEST_LENGTH]; + memset(computed_signature, 0, SHA256_DIGEST_LENGTH); + unsigned int md_len = SHA256_DIGEST_LENGTH; + if (!HMAC(EVP_sha256(), &mac_key_server_[0], mac_key_server_.size(), + given_message, message_length, computed_signature, &md_len)) { + LOGE("ValidateMessage: Could not compute signature."); + return false; + } + if (memcmp(given_signature, computed_signature, signature_length)) { + LOGE("Invalid signature given: %s", + wvcdm::HexEncode(given_signature, signature_length).c_str()); + LOGE("Invalid signature computed: %s", + wvcdm::HexEncode(computed_signature, signature_length).c_str()); + return false; + } + return true; +} + +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. + 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; + default: + if ((key_control_block.control_bits() & kControlNonceEnabled) && + (!CheckNonce(key_control_block.nonce()))) { + LOGE("LoadKeys: BAD Nonce"); + return OEMCrypto_ERROR_INVALID_NONCE; + } + } + return OEMCrypto_SUCCESS; +} + +void SessionContext::StartTimer() { timer_start_ = time(NULL); } + +uint32_t SessionContext::CurrentTimer() { + time_t now = time(NULL); + return now - timer_start_; +} + +OEMCryptoResult SessionContext::LoadKeys( + const uint8_t* message, size_t message_length, const uint8_t* signature, + size_t signature_length, const uint8_t* enc_mac_key_iv, + const uint8_t* enc_mac_keys, size_t num_keys, + const OEMCrypto_KeyObject* key_array, const uint8_t* pst, + size_t pst_length) { + // Validate message signature + if (!ValidateMessage(message, message_length, signature, signature_length)) { + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + + StartTimer(); + + // If there are already keys installed in this session, then we can load + // a shared license. + bool second_license = (session_keys_.size() > 0); + + // Decrypt and install keys in key object + // Each key will have a key control block. They will all have the same nonce. + OEMCryptoResult status = OEMCrypto_SUCCESS; + std::vector key_id; + std::vector enc_key_data; + std::vector key_data_iv; + std::vector key_control; + std::vector key_control_iv; + 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); + enc_key_data.assign(key_array[i].key_data, + key_array[i].key_data + key_array[i].key_data_length); + 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 = OEMCrypto_ERROR_UNKNOWN_FAILURE; + break; + } + key_control.assign(key_array[i].key_control, + key_array[i].key_control + wvcdm::KEY_CONTROL_SIZE); + key_control_iv.assign(key_array[i].key_control_iv, + key_array[i].key_control_iv + wvcdm::KEY_IV_SIZE); + + OEMCryptoResult result = InstallKey( + key_id, enc_key_data, key_data_iv, key_control, key_control_iv, + key_array[i].cipher_mode == OEMCrypto_CipherMode_CTR, second_license); + if (result != OEMCrypto_SUCCESS) { + status = result; + break; + } + } + FlushNonces(); + if (status != OEMCrypto_SUCCESS) return status; + + // enc_mac_key can be NULL if license renewal is not supported + if (enc_mac_keys != NULL) { + // V2.1 license protocol: update mac keys after processing license response + const std::vector enc_mac_keys_str = std::vector( + enc_mac_keys, enc_mac_keys + 2 * wvcdm::MAC_KEY_SIZE); + const std::vector enc_mac_key_iv_str = std::vector( + enc_mac_key_iv, enc_mac_key_iv + wvcdm::KEY_IV_SIZE); + + if (!UpdateMacKeys(enc_mac_keys_str, enc_mac_key_iv_str)) { + LOGE("Failed to update mac keys.\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 mac keys do not match.\n"); + return OEMCrypto_ERROR_WRONG_KEYS; + } + if (usage_entry_->Inactive()) return OEMCrypto_ERROR_LICENSE_INACTIVE; + break; + } + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::InstallKey( + const KeyId& key_id, const std::vector& key_data, + const std::vector& key_data_iv, + const std::vector& key_control, + const std::vector& key_control_iv, bool ctr_mode, + bool second_license) { + // Decrypt encrypted key_data using derived encryption key and offered iv + std::vector content_key; + std::vector key_control_str; + + if (!DecryptMessage(encryption_key_, key_data_iv, key_data, &content_key)) { + LOGE("[Installkey(): Could not decrypt key data]"); + 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()); + } + + // Key control must be supplied by license server + if (key_control.empty()) { + LOGE("[Installkey(): WARNING: No Key Control]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (key_control_iv.empty()) { + LOGE("[Installkey(): ERROR: No Key Control IV]"); + 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 OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + KeyControlBlock key_control_block(key_control_str); + if (!key_control_block.valid()) { + LOGE("Error parsing key control."); + 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 OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + uint8_t minimum_patch_level = + (key_control_block.control_bits() & kControlSecurityPatchLevelMask) >> + kControlSecurityPatchLevelShift; + if (minimum_patch_level > OEMCrypto_Security_Patch_Level()) { + LOGE("[InstallKey(): security patch level: %d. Minimum:%d]", + OEMCrypto_Security_Patch_Level(), minimum_patch_level); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + OEMCryptoResult result = CheckNonceOrEntry(key_control_block); + if (result != OEMCrypto_SUCCESS) { + LOGE("LoadKeys: Failed Nonce/PST check."); + return result; + } + + if (key_control_block.control_bits() & kSharedLicense) { + if (!second_license) { + LOGE("LoadKeys: Shared License, but no keys previously loaded."); + return OEMCrypto_ERROR_MISSING_MASTER; + } + } + + Key key(content_key, key_control_block, ctr_mode); + session_keys_.Insert(key_id, key); + return OEMCrypto_SUCCESS; +} + +bool SessionContext::InstallRSAEncryptedKey( + const uint8_t* encrypted_message_key, size_t encrypted_message_key_length) { + encryption_key_.resize(RSA_size(rsa_key())); + int decrypted_size = RSA_private_decrypt( + encrypted_message_key_length, encrypted_message_key, &encryption_key_[0], + rsa_key(), RSA_PKCS1_OAEP_PADDING); + if (-1 == decrypted_size) { + LOGE("[RSADeriveKeys(): error decrypting session key.]"); + dump_openssl_error(); + return false; + } + encryption_key_.resize(decrypted_size); + if (decrypted_size != static_cast(wvcdm::KEY_SIZE)) { + LOGE("[RSADeriveKeys(): error. session key is wrong size: %d.]", + decrypted_size); + dump_openssl_error(); + encryption_key_.clear(); + return false; + } + return true; +} + +OEMCryptoResult SessionContext::RefreshKey( + const KeyId& key_id, const std::vector& key_control, + const std::vector& key_control_iv) { + if (key_id.empty()) { + // Key control is not encrypted if key id is NULL + KeyControlBlock key_control_block(key_control); + if (!key_control_block.valid()) { + LOGE("Parse key control error."); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if ((key_control_block.control_bits() & kControlNonceEnabled) && + (!CheckNonce(key_control_block.nonce()))) { + LOGE("KCB: BAD Nonce"); + return OEMCrypto_ERROR_INVALID_NONCE; + } + // Apply duration to all keys in this session + session_keys_.UpdateDuration(key_control_block); + return OEMCrypto_SUCCESS; + } + + Key* content_key = session_keys_.Find(key_id); + + if (NULL == content_key) { + if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { + LOGD("Error: no matching content key."); + } + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + if (key_control.empty()) { + if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { + LOGD("Error: no key_control."); + } + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + const std::vector content_key_value = content_key->value(); + + // Decrypt encrypted key control block + std::vector control; + if (key_control_iv.empty()) { + if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { + LOGD("Key control block is NOT encrypted."); + } + control = key_control; + } else { + if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { + LOGD("Key control block is encrypted."); + } + if (!DecryptMessage(content_key_value, key_control_iv, key_control, + &control)) { + if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { + LOGD("Error decrypting key control block."); + } + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } + + KeyControlBlock key_control_block(control); + if (!key_control_block.valid()) { + if (LogCategoryEnabled(kLoggingDumpKeyControlBlocks)) { + LOGD("Parse key control error."); + } + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if ((key_control_block.control_bits() & kControlNonceEnabled) && + (!CheckNonce(key_control_block.nonce()))) { + LOGE("KCB: BAD Nonce"); + return OEMCrypto_ERROR_INVALID_NONCE; + } + content_key->UpdateDuration(key_control_block); + return OEMCrypto_SUCCESS; +} + +bool SessionContext::DecryptRSAKey(const uint8_t* enc_rsa_key, + size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, + uint8_t* pkcs8_rsa_key) { + // Decrypt rsa key with keybox. + uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; + memcpy(iv_buffer, enc_rsa_key_iv, wvcdm::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_decrypt_key(&encryption_key_[0], 128, &aes_key); + AES_cbc_encrypt(enc_rsa_key, pkcs8_rsa_key, enc_rsa_key_length, &aes_key, + iv_buffer, AES_DECRYPT); + return true; +} + +bool SessionContext::EncryptRSAKey(const uint8_t* pkcs8_rsa_key, + size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, + uint8_t* enc_rsa_key) { + // Encrypt rsa key with keybox. + uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; + memcpy(iv_buffer, enc_rsa_key_iv, wvcdm::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&encryption_key_[0], 128, &aes_key); + AES_cbc_encrypt(pkcs8_rsa_key, enc_rsa_key, enc_rsa_key_length, &aes_key, + iv_buffer, AES_ENCRYPT); + return true; +} + +bool SessionContext::LoadRSAKey(const uint8_t* pkcs8_rsa_key, + size_t rsa_key_length) { + rsa_key_.reset(); + if (rsa_key_length < 8) { + LOGE("[LoadRSAKey(): Very Short Buffer]"); + return false; + } + if ((memcmp(pkcs8_rsa_key, "SIGN", 4) == 0)) { + uint32_t* schemes_n = (uint32_t*)(pkcs8_rsa_key + 4); + allowed_schemes_ = htonl(*schemes_n); + pkcs8_rsa_key += 8; + rsa_key_length -= 8; + } else { + allowed_schemes_ = kSign_RSASSA_PSS; + } + return rsa_key_.LoadPkcs8RsaKey(pkcs8_rsa_key, rsa_key_length); +} + +OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string, + uint32_t use_type, + OEMCryptoBufferType buffer_type) { + const KeyControlBlock& control = current_content_key()->control(); + if (use_type && (!(control.control_bits() & use_type))) { + LOGE("[%s(): control bit says not allowed.", log_string.c_str()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (control.control_bits() & kControlDataPathSecure) { + if (!ce_->config_closed_platform() && + buffer_type == OEMCrypto_BufferType_Clear) { + LOGE("[%s(): Secure key with insecure buffer]", log_string.c_str()); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + } + if (control.control_bits() & kControlReplayMask) { + if (!CheckUsageEntry()) { + LOGE("[%s(): usage entry not valid]", log_string.c_str()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } + if (control.duration() > 0) { + if (control.duration() < CurrentTimer()) { + LOGE("[%s(): key expired.", log_string.c_str()); + return OEMCrypto_ERROR_KEY_EXPIRED; + } + } + if (!ce_->config_local_display_only()) { + // Only look at HDCP and Analog restrictions if the display is non-local. + if (control.control_bits() & kControlHDCPRequired) { + uint8_t required_hdcp = + (control.control_bits() & kControlHDCPVersionMask) >> + kControlHDCPVersionShift; + // For reference implementation, we pretend we can handle the current + // HDCP version. + if (required_hdcp > ce_->config_current_hdcp_capability() || + ce_->config_current_hdcp_capability() == 0) { + return OEMCrypto_ERROR_INSUFFICIENT_HDCP; + } + } + if (control.control_bits() & kControlSRMVersionRequired) { + LOGE("[%s(): control bit says SRM version required.", log_string.c_str()); + return OEMCrypto_ERROR_INSUFFICIENT_HDCP; + } + } + if (!ce_->config_local_display_only() || + buffer_type == OEMCrypto_BufferType_Clear) { + if (control.control_bits() & kControlDisableAnalogOutput) { + LOGE("[%s(): control bit says disable analog.", log_string.c_str()); + return OEMCrypto_ERROR_ANALOG_OUTPUT; + } + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::Generic_Encrypt(const uint8_t* in_buffer, + size_t buffer_length, + const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer) { + // Check there is a content key + if (current_content_key() == NULL) { + LOGE("[Generic_Encrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + const std::vector& key = current_content_key()->value(); + // Set the AES key. + if (static_cast(key.size()) != AES_BLOCK_SIZE) { + LOGE("[Generic_Encrypt(): CONTENT_KEY has wrong size: %d", key.size()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + OEMCryptoResult result = CheckKeyUse("Generic_Encrypt", kControlAllowEncrypt, + OEMCrypto_BufferType_Clear); + if (result != OEMCrypto_SUCCESS) return result; + if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) { + LOGE("[Generic_Encrypt(): algorithm bad."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (buffer_length % AES_BLOCK_SIZE != 0) { + LOGE("[Generic_Encrypt(): buffers size bad."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const uint8_t* key_u8 = &key[0]; + AES_KEY aes_key; + if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) { + LOGE("[Generic_Encrypt(): FAILURE]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; + memcpy(iv_buffer, iv, wvcdm::KEY_IV_SIZE); + AES_cbc_encrypt(in_buffer, out_buffer, buffer_length, &aes_key, iv_buffer, + AES_ENCRYPT); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::Generic_Decrypt(const uint8_t* in_buffer, + size_t buffer_length, + const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer) { + // Check there is a content key + if (current_content_key() == NULL) { + LOGE("[Generic_Decrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + const std::vector& key = current_content_key()->value(); + // Set the AES key. + if (static_cast(key.size()) != AES_BLOCK_SIZE) { + LOGE("[Generic_Decrypt(): CONTENT_KEY has wrong size."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + OEMCryptoResult result = CheckKeyUse("Generic_Decrypt", kControlAllowDecrypt, + OEMCrypto_BufferType_Clear); + if (result != OEMCrypto_SUCCESS) return result; + + if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) { + LOGE("[Generic_Decrypt(): bad algorithm."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (buffer_length % AES_BLOCK_SIZE != 0) { + LOGE("[Generic_Decrypt(): bad buffer size."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const uint8_t* key_u8 = &key[0]; + AES_KEY aes_key; + if (AES_set_decrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) { + LOGE("[Generic_Decrypt(): FAILURE]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; + memcpy(iv_buffer, iv, wvcdm::KEY_IV_SIZE); + AES_cbc_encrypt(in_buffer, out_buffer, buffer_length, &aes_key, iv_buffer, + AES_DECRYPT); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::Generic_Sign(const uint8_t* in_buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + uint8_t* signature, + size_t* signature_length) { + // Check there is a content key + if (current_content_key() == NULL) { + LOGE("[Generic_Sign(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + if (*signature_length < SHA256_DIGEST_LENGTH) { + *signature_length = SHA256_DIGEST_LENGTH; + LOGE("[Generic_Sign(): bad signature length."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const std::vector& key = current_content_key()->value(); + if (static_cast(key.size()) != SHA256_DIGEST_LENGTH) { + LOGE("[Generic_Sign(): CONTENT_KEY has wrong size; %d", key.size()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + OEMCryptoResult result = CheckKeyUse("Generic_Sign", kControlAllowSign, + OEMCrypto_BufferType_Clear); + if (result != OEMCrypto_SUCCESS) return result; + if (algorithm != OEMCrypto_HMAC_SHA256) { + LOGE("[Generic_Sign(): bad algorithm."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + unsigned int md_len = *signature_length; + if (HMAC(EVP_sha256(), &key[0], key.size(), in_buffer, buffer_length, + signature, &md_len)) { + *signature_length = md_len; + return OEMCrypto_SUCCESS; + } + LOGE("[Generic_Sign(): hmac failed."); + dump_openssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; +} + +OEMCryptoResult SessionContext::Generic_Verify(const uint8_t* in_buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + const uint8_t* signature, + size_t signature_length) { + // Check there is a content key + if (current_content_key() == NULL) { + LOGE("[Decrypt_Verify(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (signature_length < SHA256_DIGEST_LENGTH) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const std::vector& key = current_content_key()->value(); + if (static_cast(key.size()) != SHA256_DIGEST_LENGTH) { + LOGE("[Generic_Verify(): CONTENT_KEY has wrong size: %d", key.size()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + OEMCryptoResult result = CheckKeyUse("Generic_Verify", kControlAllowVerify, + OEMCrypto_BufferType_Clear); + if (result != OEMCrypto_SUCCESS) return result; + if (algorithm != OEMCrypto_HMAC_SHA256) { + LOGE("[Generic_Verify(): bad algorithm."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + unsigned int md_len = signature_length; + uint8_t computed_signature[SHA256_DIGEST_LENGTH]; + if (HMAC(EVP_sha256(), &key[0], key.size(), in_buffer, buffer_length, + computed_signature, &md_len)) { + if (0 == memcmp(signature, computed_signature, SHA256_DIGEST_LENGTH)) { + return OEMCrypto_SUCCESS; + } else { + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + } + LOGE("[Generic_Verify(): HMAC failed."); + dump_openssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; +} + +bool SessionContext::UpdateMacKeys(const std::vector& enc_mac_keys, + const std::vector& iv) { + // Decrypt mac key from enc_mac_key using device_keya + std::vector mac_keys; + if (!DecryptMessage(encryption_key_, iv, enc_mac_keys, &mac_keys)) { + return false; + } + mac_key_server_ = std::vector( + mac_keys.begin(), mac_keys.begin() + wvcdm::MAC_KEY_SIZE); + mac_key_client_ = std::vector(mac_keys.begin() + wvcdm::MAC_KEY_SIZE, + mac_keys.end()); + return true; +} + +bool SessionContext::QueryKeyControlBlock(const KeyId& key_id, uint32_t* data) { + const Key* content_key = session_keys_.Find(key_id); + if (LogCategoryEnabled(kLoggingTraceDecryption)) { + LOGI(("Select Key: key_id = " + wvcdm::b2a_hex(key_id)).c_str()); + if (content_key) { + LOGI(("Select Key: key = " + wvcdm::b2a_hex(content_key->value())) + .c_str()); + } else { + LOGI("Select Key: key = null."); + } + } + if (NULL == content_key) { + LOGE("[QueryKeyControlBlock(): No key matches key id]"); + return false; + } + data[0] = 0; // verification optional. + data[1] = htonl(content_key->control().duration()); + data[2] = 0; // nonce optional. + data[3] = htonl(content_key->control().control_bits()); + return true; +} + +OEMCryptoResult SessionContext::SelectContentKey(const KeyId& key_id) { + const Key* content_key = session_keys_.Find(key_id); + + if (LogCategoryEnabled(kLoggingTraceDecryption)) { + LOGI((" Select Key: key_id = " + wvcdm::b2a_hex(key_id)).c_str()); + LOGI((" Select Key: key = " + wvcdm::b2a_hex(content_key->value())) + .c_str()); + } + + if (NULL == content_key) { + LOGE("[SelectContentKey(): No key matches key id]"); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + current_content_key_ = content_key; + const KeyControlBlock& control = current_content_key()->control(); + if (control.duration() > 0) { + if (control.duration() < CurrentTimer()) { + LOGE("[SelectContentKey(): KEY_EXPIRED]"); + return OEMCrypto_ERROR_KEY_EXPIRED; + } + } + return OEMCrypto_SUCCESS; +} + +void SessionContext::AddNonce(uint32_t nonce) { nonce_table_.AddNonce(nonce); } + +bool SessionContext::CheckNonce(uint32_t nonce) { + return nonce_table_.CheckNonce(nonce); +} + +void SessionContext::FlushNonces() { nonce_table_.Flush(); } + +bool SessionContext::CheckUsageEntry() { + if (!usage_entry_) return false; + return usage_entry_->CheckForUse(); +} + +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& 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& pst) { + if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT; + usage_entry_->Deactivate(pst); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::ReportUsage(const std::vector& 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& pst) { + if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT; + return ce_->usage_table().CopyOldUsageEntry(usage_entry_, pst); +} + + +// Internal utility function to decrypt the message +bool SessionContext::DecryptMessage(const std::vector& key, + const std::vector& iv, + const std::vector& message, + std::vector* decrypted) { + if (key.empty() || iv.empty() || message.empty() || !decrypted) { + LOGE("[DecryptMessage(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return false; + } + decrypted->resize(message.size()); + uint8_t iv_buffer[16]; + memcpy(iv_buffer, &iv[0], 16); + AES_KEY aes_key; + AES_set_decrypt_key(&key[0], 128, &aes_key); + AES_cbc_encrypt(&message[0], &(decrypted->front()), message.size(), &aes_key, + iv_buffer, AES_DECRYPT); + return true; +} + +OEMCryptoResult SessionContext::DecryptCENC( + const uint8_t* iv, size_t block_offset, + const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, + size_t cipher_data_length, bool is_encrypted, uint8_t* clear_data, + OEMCryptoBufferType buffer_type) { + // If the data is clear, we do not need a current key selected. + if (!is_encrypted) { + if (buffer_type != OEMCrypto_BufferType_Direct) { + memcpy(reinterpret_cast(clear_data), cipher_data, + cipher_data_length); + return OEMCrypto_SUCCESS; + } + // For reference implementation, we quietly drop the clear direct video. + return OEMCrypto_SUCCESS; + } + + // Check there is a content key + if (current_content_key() == NULL) { + LOGE("[DecryptCTR(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + + OEMCryptoResult result = CheckKeyUse("DecryptCENC", 0, buffer_type); + if (result != OEMCrypto_SUCCESS) return result; + + const std::vector& content_key = current_content_key()->value(); + + // Set the AES key. + if (static_cast(content_key.size()) != AES_BLOCK_SIZE) { + LOGE("[DecryptCTR(): CONTENT_KEY has wrong size: %d", content_key.size()); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + const uint8_t* key_u8 = &content_key[0]; + + if (buffer_type == OEMCrypto_BufferType_Direct) { + // For reference implementation, we quietly drop the decrypted direct video. + return OEMCrypto_SUCCESS; + } + + if (buffer_type == OEMCrypto_BufferType_Secure) { + // For reference implementation, we also quietly drop secure data. + return OEMCrypto_SUCCESS; + } + + if (!current_content_key()->ctr_mode()) { + if (block_offset > 0) return OEMCrypto_ERROR_INVALID_CONTEXT; + return DecryptCBC(key_u8, iv, pattern, cipher_data, cipher_data_length, + clear_data); + } + if (pattern->skip > 0) { + return PatternDecryptCTR(key_u8, iv, block_offset, pattern, cipher_data, + cipher_data_length, clear_data); + } + return DecryptCTR(key_u8, iv, block_offset, cipher_data, cipher_data_length, + clear_data); +} + +OEMCryptoResult SessionContext::DecryptCBC( + const uint8_t* key, const uint8_t* initial_iv, + const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, + size_t cipher_data_length, uint8_t* clear_data) { + AES_KEY aes_key; + AES_set_decrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); + uint8_t iv[AES_BLOCK_SIZE]; + memcpy(iv, &initial_iv[0], AES_BLOCK_SIZE); + + size_t l = 0; + size_t pattern_offset = pattern->offset; + while (l < cipher_data_length) { + size_t size = + std::min(cipher_data_length - l, static_cast(AES_BLOCK_SIZE)); + size_t pattern_length = pattern->encrypt + pattern->skip; + bool skip_block = + (pattern_offset >= pattern->encrypt) && (pattern_length > 0); + if (pattern_length > 0) { + pattern_offset = (pattern_offset + 1) % pattern_length; + } + if (skip_block || (size < AES_BLOCK_SIZE)) { + memcpy(&clear_data[l], &cipher_data[l], size); + } else { + uint8_t aes_output[AES_BLOCK_SIZE]; + AES_decrypt(&cipher_data[l], aes_output, &aes_key); + for (size_t n = 0; n < AES_BLOCK_SIZE; n++) { + clear_data[l + n] = aes_output[n] ^ iv[n]; + } + memcpy(iv, &cipher_data[l], AES_BLOCK_SIZE); + } + l += size; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::PatternDecryptCTR( + const uint8_t* key, const uint8_t* initial_iv, size_t block_offset, + const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, + size_t cipher_data_length, uint8_t* clear_data) { + AES_KEY aes_key; + AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); + uint8_t iv[AES_BLOCK_SIZE]; + memcpy(iv, &initial_iv[0], AES_BLOCK_SIZE); + + size_t l = 0; + size_t pattern_offset = pattern->offset; + while (l < cipher_data_length) { + size_t size = + std::min(cipher_data_length - l, AES_BLOCK_SIZE - block_offset); + size_t pattern_length = pattern->encrypt + pattern->skip; + bool skip_block = + (pattern_offset >= pattern->encrypt) && (pattern_length > 0); + if (pattern_length > 0) { + pattern_offset = (pattern_offset + 1) % pattern_length; + } + if (skip_block) { + memcpy(&clear_data[l], &cipher_data[l], size); + } else { + uint8_t aes_output[AES_BLOCK_SIZE]; + AES_encrypt(iv, aes_output, &aes_key); + for (size_t n = 0; n < size; n++) { + clear_data[l + n] = aes_output[n + block_offset] ^ cipher_data[l + n]; + } + ctr128_inc64(iv); + } + l += size; + block_offset = 0; + } + return OEMCrypto_SUCCESS; +} + +// This is a special case of PatternDecryptCTR with no skip pattern. It uses +// more optimized versions of openssl's implementation of AES CTR mode. +OEMCryptoResult SessionContext::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) { + // Local copy (will be modified). + // Allocated as 64-bit ints to enforce 64-bit alignment for later access as a + // 64-bit value. + uint64_t aes_iv[2]; + assert(sizeof(aes_iv) == AES_BLOCK_SIZE); + // The double-cast is needed to comply with strict aliasing rules. + uint8_t* aes_iv_u8 = + reinterpret_cast(reinterpret_cast(aes_iv)); + memcpy(aes_iv_u8, &iv[0], AES_BLOCK_SIZE); + + // The CENC spec specifies we increment only the low 64 bits of the IV + // counter, and leave the high 64 bits alone. This is different from the + // OpenSSL implementation, which increments the entire 128 bit iv. That is + // why we implement the CTR loop ourselves. + size_t l = 0; + if (block_offset > 0 && l < cipher_data_length) { + // Encrypt the IV. + uint8_t ecount_buf[AES_BLOCK_SIZE]; + + AES_KEY aes_key; + if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) { + LOGE("[DecryptCTR(): FAILURE]"); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + AES_encrypt(aes_iv_u8, ecount_buf, &aes_key); + for (int n = block_offset; n < AES_BLOCK_SIZE && l < cipher_data_length; + ++n, ++l) { + clear_data[l] = cipher_data[l] ^ ecount_buf[n]; + } + ctr128_inc64(aes_iv_u8); + block_offset = 0; + } + + uint64_t remaining = cipher_data_length - l; + int out_len = 0; + + while (remaining) { + EVP_CIPHER_CTX ctx; + EVP_CIPHER_CTX_init(&ctx); + EVP_CIPHER_CTX_set_padding(&ctx, 0); + if (!EVP_DecryptInit_ex(&ctx, EVP_aes_128_ctr(), NULL, key_u8, aes_iv_u8)) { + LOGE("[DecryptCTR(): EVP_INIT ERROR]"); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + + // Test the MSB of the counter portion of the initialization vector. If the + // value is 0xFF the counter is near wrapping. In this case we calculate + // the number of bytes we can safely decrypt before the counter wraps. + uint64_t decrypt_length = 0; + if (aes_iv_u8[8] == 0xFF) { + uint64_t bottom_64_bits = wvcdm::ntohll64(aes_iv[1]); + uint64_t bytes_before_iv_wrap = (~bottom_64_bits + 1) * AES_BLOCK_SIZE; + decrypt_length = + bytes_before_iv_wrap < remaining ? bytes_before_iv_wrap : remaining; + } else { + decrypt_length = remaining; + } + + if (!EVP_DecryptUpdate(&ctx, &clear_data[l], &out_len, &cipher_data[l], + decrypt_length)) { + LOGE("[DecryptCTR(): EVP_UPDATE_ERROR]"); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + l += decrypt_length; + remaining = cipher_data_length - l; + + int final; + if (!EVP_DecryptFinal_ex( + &ctx, &clear_data[cipher_data_length - remaining], & final)) { + LOGE("[DecryptCTR(): EVP_FINAL_ERROR]"); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + EVP_CIPHER_CTX_cleanup(&ctx); + + // If remaining is not zero, reset the iv before the second pass. + if (remaining) { + memcpy(aes_iv_u8, &iv[0], AES_BLOCK_SIZE); + memset(&aes_iv_u8[8], 0, AES_BLOCK_SIZE / 2); + } + } + return OEMCrypto_SUCCESS; +} + +} // namespace wvoec_mock diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.h b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.h new file mode 100644 index 00000000..768d0165 --- /dev/null +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session.h @@ -0,0 +1,210 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Mock implementation of OEMCrypto APIs +// +#ifndef MOCK_OEMCRYPTO_SESSION_H_ +#define MOCK_OEMCRYPTO_SESSION_H_ + +#include +#include +#include +#include + +#include + +#include "OEMCryptoCENC.h" // Needed for enums only. +#include "file_store.h" +#include "lock.h" +#include "oemcrypto_auth_mock.h" +#include "oemcrypto_key_mock.h" +#include "oemcrypto_nonce_table.h" +#include "oemcrypto_rsa_key_shared.h" +#include "oemcrypto_session_key_table.h" +#include "oemcrypto_usage_table_mock.h" +#include "wv_cdm_types.h" + +namespace wvoec_mock { + +class CryptoEngine; +typedef uint32_t SessionId; + +class SessionContext { + private: + SessionContext() {} + + public: + SessionContext(CryptoEngine* ce, SessionId sid, const RSA_shared_ptr& rsa_key) + : valid_(true), + ce_(ce), + id_(sid), + current_content_key_(NULL), + rsa_key_(rsa_key), + allowed_schemes_(kSign_RSASSA_PSS), + usage_entry_(NULL), + usage_entry_status_(kNoUsageEntry) {} + ~SessionContext(); + + bool isValid() { return valid_; } + + bool DeriveKeys(const std::vector& master_key, + const std::vector& mac_context, + const std::vector& enc_context); + bool RSADeriveKeys(const std::vector& enc_session_key, + const std::vector& mac_context, + const std::vector& enc_context); + bool GenerateSignature(const uint8_t* message, size_t message_length, + uint8_t* signature, size_t* signature_length); + size_t RSASignatureSize(); + OEMCryptoResult GenerateRSASignature(const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length, + RSA_Padding_Scheme padding_scheme); + bool ValidateMessage(const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length); + OEMCryptoResult DecryptCENC(const uint8_t* iv, size_t block_offset, + const OEMCrypto_CENCEncryptPatternDesc* pattern, + const uint8_t* cipher_data, + size_t cipher_data_length, bool is_encrypted, + uint8_t* clear_data, + OEMCryptoBufferType buffer_type); + + OEMCryptoResult Generic_Encrypt(const uint8_t* in_buffer, + size_t buffer_length, const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer); + OEMCryptoResult Generic_Decrypt(const uint8_t* in_buffer, + size_t buffer_length, const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer); + OEMCryptoResult Generic_Sign(const uint8_t* in_buffer, size_t buffer_length, + OEMCrypto_Algorithm algorithm, + uint8_t* signature, size_t* signature_length); + OEMCryptoResult Generic_Verify(const uint8_t* in_buffer, size_t buffer_length, + OEMCrypto_Algorithm algorithm, + const uint8_t* signature, + size_t signature_length); + void StartTimer(); + uint32_t CurrentTimer(); // (seconds). + OEMCryptoResult LoadKeys(const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + const uint8_t* enc_mac_key_iv, + const uint8_t* enc_mac_keys, size_t num_keys, + const OEMCrypto_KeyObject* key_array, + const uint8_t* pst, size_t pst_length); + OEMCryptoResult InstallKey(const KeyId& key_id, + const std::vector& key_data, + const std::vector& key_data_iv, + const std::vector& key_control, + const std::vector& key_control_iv, + bool ctr_mode, bool second_license); + 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, + const uint8_t* wrapped_rsa_key_iv, uint8_t* pkcs8_rsa_key); + bool EncryptRSAKey(const uint8_t* pkcs8_rsa_key, size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, uint8_t* enc_rsa_key); + bool LoadRSAKey(const uint8_t* pkcs8_rsa_key, size_t rsa_key_length); + OEMCryptoResult RefreshKey(const KeyId& key_id, + const std::vector& key_control, + const std::vector& key_control_iv); + bool UpdateMacKeys(const std::vector& mac_keys, + const std::vector& iv); + bool QueryKeyControlBlock(const KeyId& key_id, uint32_t* data); + OEMCryptoResult SelectContentKey(const KeyId& key_id); + const Key* current_content_key(void) { return current_content_key_; } + void set_mac_key_server(const std::vector& mac_key_server) { + mac_key_server_ = mac_key_server; + } + const std::vector& mac_key_server() { return mac_key_server_; } + void set_mac_key_client(const std::vector& mac_key_client) { + mac_key_client_ = mac_key_client; + } + const std::vector& mac_key_client() { return mac_key_client_; } + + void set_encryption_key(const std::vector& enc_key) { + encryption_key_ = enc_key; + } + const std::vector& encryption_key() { return encryption_key_; } + uint32_t allowed_schemes() const { return allowed_schemes_; } + + void AddNonce(uint32_t nonce); + bool CheckNonce(uint32_t nonce); + void FlushNonces(); + + OEMCryptoResult CreateNewUsageEntry(uint32_t* usage_entry_number); + OEMCryptoResult LoadUsageEntry(uint32_t index, + const std::vector& 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& pst); + OEMCryptoResult ReportUsage(const std::vector& pst, uint8_t* buffer, + size_t* buffer_length); + OEMCryptoResult MoveEntry(uint32_t new_index); + OEMCryptoResult CopyOldUsageEntry(const std::vector& pst); + + private: + bool DeriveKey(const std::vector& key, + const std::vector& context, int counter, + std::vector* out); + bool DecryptMessage(const std::vector& key, + const std::vector& iv, + const std::vector& message, + std::vector* decrypted); + // 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, + size_t cipher_data_length, uint8_t* clear_data); + OEMCryptoResult PatternDecryptCTR( + const uint8_t* key, const uint8_t* iv, size_t block_offset, + const OEMCrypto_CENCEncryptPatternDesc* pattern, + const uint8_t* cipher_data, size_t cipher_data_length, + uint8_t* clear_data); + 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); + // 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(); } + + bool valid_; + CryptoEngine* ce_; + SessionId id_; + std::vector mac_key_server_; + std::vector mac_key_client_; + std::vector encryption_key_; + std::vector session_key_; + const Key* current_content_key_; + SessionKeyTable session_keys_; + NonceTable nonce_table_; + RSA_shared_ptr rsa_key_; + uint32_t allowed_schemes_; // for RSA signatures. + 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); +}; + +} // namespace wvoec_mock + +#endif // MOCK_OEMCRYPTO_SESSION_H_ diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session_key_table.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session_key_table.cpp new file mode 100644 index 00000000..c313f133 --- /dev/null +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session_key_table.cpp @@ -0,0 +1,46 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Mock implementation of OEMCrypto APIs +// +#include "oemcrypto_session_key_table.h" + +#include "keys.h" +#include "log.h" + +namespace wvoec_mock { + +SessionKeyTable::~SessionKeyTable() { + for (KeyMap::iterator i = keys_.begin(); i != keys_.end(); ++i) { + if (NULL != i->second) { + delete i->second; + } + } +} + +bool SessionKeyTable::Insert(const KeyId key_id, const Key& key_data) { + if (keys_.find(key_id) != keys_.end()) return false; + keys_[key_id] = new Key(key_data); + return true; +} + +Key* SessionKeyTable::Find(const KeyId key_id) { + if (keys_.find(key_id) == keys_.end()) { + return NULL; + } + return keys_[key_id]; +} + +void SessionKeyTable::Remove(const KeyId key_id) { + if (keys_.find(key_id) != keys_.end()) { + delete keys_[key_id]; + keys_.erase(key_id); + } +} + +void SessionKeyTable::UpdateDuration(const KeyControlBlock& control) { + for (KeyMap::iterator it = keys_.begin(); it != keys_.end(); ++it) { + it->second->UpdateDuration(control); + } +} + +} // namespace wvoec_mock diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session_key_table.h b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session_key_table.h new file mode 100644 index 00000000..f3567bdb --- /dev/null +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_session_key_table.h @@ -0,0 +1,45 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Mock implementation of OEMCrypto APIs +// +#ifndef MOCK_OEMCRYPTO_SESSION_KEY_TABLE_H_ +#define MOCK_OEMCRYPTO_SESSION_KEY_TABLE_H_ + +#include +#include +#include + +#include "oemcrypto_key_mock.h" +#include "wv_cdm_types.h" + +namespace wvoec_mock { + +class SessionContext; +class CryptoEngine; +class UsageTable; +class UsageTableEntry; + +typedef std::vector KeyId; +typedef std::map KeyMap; + +// SessionKeyTable holds the keys for the current session +class SessionKeyTable { + public: + SessionKeyTable() {} + ~SessionKeyTable(); + + bool Insert(const KeyId key_id, const Key& key_data); + Key* Find(const KeyId key_id); + void Remove(const KeyId key_id); + void UpdateDuration(const KeyControlBlock& control); + size_t size() const { return keys_.size(); } + + private: + KeyMap keys_; + + CORE_DISALLOW_COPY_AND_ASSIGN(SessionKeyTable); +}; + +} // namespace wvoec_mock + +#endif // MOCK_OEMCRYPTO_SESSION_KEY_TABLE_H_