diff --git a/libwvdrmengine/oemcrypto/mock/Android.mk b/libwvdrmengine/oemcrypto/mock/Android.mk index 9e27d6c8..bf3f3862 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,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 76e9f44d..a60a6dad 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp @@ -11,985 +11,36 @@ #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_) usage_entry_->set_session(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; -} - -bool SessionContext::CheckNonceOrEntry(const KeyControlBlock& key_control_block, - const std::vector& pst) { - 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); - } - 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; - } - } - return true; -} - -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(); - - // 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; - 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); - 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 = false; - 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); - - 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; - break; - } - } - FlushNonces(); - if (!status) return OEMCrypto_ERROR_UNKNOWN_FAILURE; - - // 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 (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; - } - } - 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) { - // 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; - } - - 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 false; - } - if (key_control_iv.empty()) { - LOGE("[Installkey(): ERROR: No Key Control IV]"); - return false; - } - if (!DecryptMessage(content_key, key_control_iv, key_control, - &key_control_str)) { - LOGE("[Installkey(): ERROR: Could not decrypt content key]"); - return false; - } - - KeyControlBlock key_control_block(key_control_str); - if (!key_control_block.valid()) { - LOGE("Error parsing key control."); - return false; - } - 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; - } - 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 false; - } - - if (!CheckNonceOrEntry(key_control_block, pst)) { - LOGE("Failed Nonce/PST check."); - return false; - } - - Key key(content_key, key_control_block, ctr_mode); - session_keys_.Insert(key_id, key); - return true; -} - -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::AllowKeyUse(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 (!IsUsageEntryValid()) { - 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 = AllowKeyUse("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 = AllowKeyUse("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 = AllowKeyUse("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 = AllowKeyUse("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::IsUsageEntryValid() { - if (!usage_entry_) return false; - return usage_entry_->UpdateTime(); -} - -void SessionContext::ReleaseUsageEntry() { usage_entry_ = NULL; } +// 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()), 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() {} @@ -1025,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 = AllowKeyUse("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 2d34c7d2..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,210 +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); - - 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) {} - ~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); - 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); - 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(); - void ReleaseUsageEntry(); - - 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); - bool CheckNonceOrEntry(const KeyControlBlock& key_control_block, - const std::vector& pst); - bool IsUsageEntryValid(); - 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); - OEMCryptoResult AllowKeyUse(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_; - - CORE_DISALLOW_COPY_AND_ASSIGN(SessionContext); -}; - class CryptoEngine { public: CryptoEngine(wvcdm::FileSystem* file_system); @@ -275,7 +79,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,11 +99,11 @@ 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); }; } // namespace wvoec_mock -#endif // OEMCRYPTO_ENGINE_MOCK_H_ +#endif // MOCK_OEMCRYPTO_ENGINE_MOCK_H_ 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/mock/src/oemcrypto_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp index d7e2aa13..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" @@ -45,7 +46,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 +1408,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 +1590,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 +1608,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 +1635,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 +1657,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 +1678,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 +1697,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_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_ 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..e0d9a89d 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( @@ -5489,49 +5245,86 @@ TEST_F(UsageTableTest, VerifyUsageTimes) { // multiple devices. In order for this to work, a single session must first // load a device specific license, and then a shared content license. 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"; - 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)); + // 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)); - 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. 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); } - // 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()); } +TEST_F(UsageTableTest, LoadSharedLicenseWithNoMaster) { + 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)); + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); + // 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); + } + ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + 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()); +} + 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 +5336,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 +5344,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