Merge changes I4e4a6a01,I4e360ea5,I6e08e76f

* changes:
  Refactor OEMCrypto Engine
  Add Shared License bit to key control block
  Reference Code for Big Usage Tables
This commit is contained in:
Fred Gylys-Colwell
2017-01-30 23:24:38 +00:00
committed by Android (Google) Code Review
17 changed files with 2790 additions and 2421 deletions

View File

@@ -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 \

File diff suppressed because it is too large Load Diff

View File

@@ -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 <stdint.h>
#include <time.h>
@@ -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<SessionId, SessionContext*> ActiveSessions;
typedef std::vector<uint8_t> KeyId;
typedef std::map<KeyId, Key*> 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<uint8_t>& master_key,
const std::vector<uint8_t>& mac_context,
const std::vector<uint8_t>& enc_context);
bool RSADeriveKeys(const std::vector<uint8_t>& enc_session_key,
const std::vector<uint8_t>& mac_context,
const std::vector<uint8_t>& 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<uint8_t>& key_data,
const std::vector<uint8_t>& key_data_iv,
const std::vector<uint8_t>& key_control,
const std::vector<uint8_t>& key_control_iv,
const std::vector<uint8_t>& pst, bool ctr_mode);
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<uint8_t>& key_control,
const std::vector<uint8_t>& key_control_iv);
bool UpdateMacKeys(const std::vector<uint8_t>& mac_keys,
const std::vector<uint8_t>& 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<uint8_t>& mac_key_server) {
mac_key_server_ = mac_key_server;
}
const std::vector<uint8_t>& mac_key_server() { return mac_key_server_; }
void set_mac_key_client(const std::vector<uint8_t>& mac_key_client) {
mac_key_client_ = mac_key_client;
}
const std::vector<uint8_t>& mac_key_client() { return mac_key_client_; }
void set_encryption_key(const std::vector<uint8_t>& enc_key) {
encryption_key_ = enc_key;
}
const std::vector<uint8_t>& 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<uint8_t>& key,
const std::vector<uint8_t>& context, int counter,
std::vector<uint8_t>* out);
bool DecryptMessage(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv,
const std::vector<uint8_t>& message,
std::vector<uint8_t>* decrypted);
bool CheckNonceOrEntry(const KeyControlBlock& key_control_block,
const std::vector<uint8_t>& pst);
bool IsUsageEntryValid();
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<uint8_t> mac_key_server_;
std::vector<uint8_t> mac_key_client_;
std::vector<uint8_t> encryption_key_;
std::vector<uint8_t> 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_

View File

@@ -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.",

View File

@@ -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;

View File

@@ -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<uint8_t> pstv(pst, pst + pst_length);
return crypto_engine->usage_table()->DeactivateEntry(pstv);
return session_ctx->DeactivateUsageEntry(pstv);
}
extern "C" OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session,
@@ -1622,20 +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<uint8_t> pstv(pst, pst + pst_length);
UsageTableEntry* entry = crypto_engine->usage_table()->FindEntry(pstv);
if (!entry) {
LOGE("[OEMCrypto_ReportUsage(): Usage Table Entry not found]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
OEMCryptoResult sts =
entry->ReportUsage(session_ctx, pstv, buffer, buffer_length);
crypto_engine->usage_table()->UpdateTable();
OEMCryptoResult sts = session_ctx->ReportUsage(pstv, buffer, buffer_length);
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
if (wvcdm::g_cutoff >= wvcdm::LOG_VERBOSE) {
dump_hex("usage buffer", buffer, *buffer_length);
@@ -1648,68 +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<uint8_t> pstv(pst, pst + pst_length);
if (crypto_engine->usage_table()->DeleteEntry(pstv)) {
return OEMCrypto_SUCCESS;
}
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
// TODO(fredgc): delete this.
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
extern "C" OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry(const uint8_t* pst,
size_t pst_length) {
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
LOGI("-- OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry()\n");
if (wvcdm::g_cutoff >= wvcdm::LOG_VERBOSE) {
dump_hex("pst", pst, pst_length);
}
}
if (!crypto_engine) {
LOGE("OEMCrypto_ForceDeleteUsageEntry: OEMCrypto Not Initialized.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (!crypto_engine->config_supports_usage_table()) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
std::vector<uint8_t> pstv(pst, pst + pst_length);
if (crypto_engine->usage_table()->DeleteEntry(pstv)) {
return OEMCrypto_SUCCESS;
}
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
// TODO(fredgc): delete this.
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
extern "C" OEMCryptoResult OEMCrypto_DeleteOldUsageTable() {
@@ -1723,9 +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<uint8_t> bufferv(buffer, buffer + buffer_length);
return crypto_engine->usage_table().LoadUsageTableHeader(bufferv);
}
extern "C" OEMCryptoResult OEMCrypto_CreateNewUsageEntry(
OEMCrypto_SESSION session, uint32_t* usage_entry_number) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
LOGI("-- OEMCryptoResult OEMCrypto_CreateNewUsageEntry(\n");
}
if (!crypto_engine) {
LOGE("OEMCrypto_CreateNewUsageEntry: OEMCrypto Not Initialized.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (!crypto_engine->config_supports_usage_table()) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_CreateNewUsageEntry(): ERROR_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (!usage_entry_number) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
OEMCryptoResult sts = session_ctx->CreateNewUsageEntry(usage_entry_number);
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
LOGI("-- usage_entry_number = %d", *usage_entry_number);
}
return sts;
}
extern "C" OEMCryptoResult OEMCrypto_LoadUsageEntry(OEMCrypto_SESSION session,
uint32_t index,
const uint8_t* buffer,
size_t buffer_size) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
LOGI("-- OEMCryptoResult OEMCrypto_LoadUsageEntry(\n");
}
if (!crypto_engine) {
LOGE("OEMCrypto_LoadUsageEntry: OEMCrypto Not Initialized.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (!crypto_engine->config_supports_usage_table()) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_LoadUsageEntry(): ERROR_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (!buffer) {
LOGE("[OEMCrypto_LoadUsageEntry(): buffer null]");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
std::vector<uint8_t> bufferv(buffer, buffer + buffer_size);
return session_ctx->LoadUsageEntry(index, bufferv);
}
extern "C" OEMCryptoResult OEMCrypto_UpdateUsageEntry(
OEMCrypto_SESSION session, uint8_t* header_buffer,
size_t* header_buffer_length, uint8_t* entry_buffer,
size_t* entry_buffer_length) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
LOGI("-- OEMCryptoResult OEMCrypto_UpdateUsageEntry(\n");
}
if (!crypto_engine) {
LOGE("OEMCrypto_UpdateUsageEntry: OEMCrypto Not Initialized.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (!crypto_engine->config_supports_usage_table()) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
if (!header_buffer_length || !entry_buffer_length) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_UpdateUsageEntry(): ERROR_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
return session_ctx->UpdateUsageEntry(header_buffer, header_buffer_length,
entry_buffer, entry_buffer_length);
}
extern "C" OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(
uint32_t new_table_size, uint8_t* header_buffer,
size_t* header_buffer_length) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
LOGI("-- OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader()\n");
}
if (!crypto_engine) {
LOGE("OEMCrypto_ShrinkUsageTableHeader: OEMCrypto Not Initialized.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (!crypto_engine->config_supports_usage_table()) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
if (!header_buffer) {
LOGE("OEMCrypto_ShrinkUsageTableHeader: buffer null.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return crypto_engine->usage_table().ShrinkUsageTableHeader(
new_table_size, header_buffer, header_buffer_length);
}
extern "C" OEMCryptoResult OEMCrypto_MoveEntry(OEMCrypto_SESSION session,
uint32_t new_index) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
LOGI("-- OEMCryptoResult OEMCrypto_MoveEntry(\n");
}
if (!crypto_engine) {
LOGE("OEMCrypto_MoveEntry: OEMCrypto Not Initialized.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (!crypto_engine->config_supports_usage_table()) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_MoveEntry(): ERROR_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
return session_ctx->MoveEntry(new_index);
}
extern "C" OEMCryptoResult OEMCrypto_CopyOldUsageEntry(
OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (LogCategoryEnabled(kLoggingTraceOEMCryptoCalls)) {
LOGI("-- OEMCryptoResult OEMCrypto_CopyOldUsageEntry(\n");
}
if (!crypto_engine) {
LOGE("OEMCrypto_CopyOldUsageEntry: OEMCrypto Not Initialized.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (!crypto_engine->config_supports_usage_table()) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_CopyOldUsageEntry(): ERROR_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
std::vector<uint8_t> pstv(pst, pst + pst_length);
return session_ctx->CopyOldUsageEntry(pstv);
}
} // namespace wvoec_mock

View File

@@ -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

View File

@@ -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 <stdint.h>
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_

File diff suppressed because it is too large Load Diff

View File

@@ -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 <stdint.h>
#include <time.h>
#include <map>
#include <vector>
#include <openssl/rsa.h>
#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<uint8_t>& master_key,
const std::vector<uint8_t>& mac_context,
const std::vector<uint8_t>& enc_context);
bool RSADeriveKeys(const std::vector<uint8_t>& enc_session_key,
const std::vector<uint8_t>& mac_context,
const std::vector<uint8_t>& 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<uint8_t>& key_data,
const std::vector<uint8_t>& key_data_iv,
const std::vector<uint8_t>& key_control,
const std::vector<uint8_t>& key_control_iv,
bool ctr_mode, bool 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<uint8_t>& key_control,
const std::vector<uint8_t>& key_control_iv);
bool UpdateMacKeys(const std::vector<uint8_t>& mac_keys,
const std::vector<uint8_t>& 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<uint8_t>& mac_key_server) {
mac_key_server_ = mac_key_server;
}
const std::vector<uint8_t>& mac_key_server() { return mac_key_server_; }
void set_mac_key_client(const std::vector<uint8_t>& mac_key_client) {
mac_key_client_ = mac_key_client;
}
const std::vector<uint8_t>& mac_key_client() { return mac_key_client_; }
void set_encryption_key(const std::vector<uint8_t>& enc_key) {
encryption_key_ = enc_key;
}
const std::vector<uint8_t>& 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<uint8_t>& buffer);
OEMCryptoResult UpdateUsageEntry(uint8_t* header_buffer,
size_t* header_buffer_length,
uint8_t* entry_buffer,
size_t* entry_buffer_length);
OEMCryptoResult DeactivateUsageEntry(const std::vector<uint8_t>& pst);
OEMCryptoResult ReportUsage(const std::vector<uint8_t>& pst, uint8_t* buffer,
size_t* buffer_length);
OEMCryptoResult MoveEntry(uint32_t new_index);
OEMCryptoResult CopyOldUsageEntry(const std::vector<uint8_t>& pst);
private:
bool DeriveKey(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& context, int counter,
std::vector<uint8_t>* out);
bool DecryptMessage(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv,
const std::vector<uint8_t>& message,
std::vector<uint8_t>* 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<uint8_t> mac_key_server_;
std::vector<uint8_t> mac_key_client_;
std::vector<uint8_t> encryption_key_;
std::vector<uint8_t> 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_

View File

@@ -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

View File

@@ -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 <stdint.h>
#include <map>
#include <vector>
#include "oemcrypto_key_mock.h"
#include "wv_cdm_types.h"
namespace wvoec_mock {
class SessionContext;
class CryptoEngine;
class UsageTable;
class UsageTableEntry;
typedef std::vector<uint8_t> KeyId;
typedef std::map<KeyId, Key*> 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_

View File

@@ -24,83 +24,118 @@
#include "string_conversions.h"
#include "wv_cdm_constants.h"
namespace {
const size_t kMagicLength = 8;
const char* kEntryVerification = "USEENTRY";
const char* kHeaderVerification = "USEHEADR";
// Offset into a signed block where we start encrypting. We need to
// skip the signature and the iv.
const size_t kEncryptionOffset = SHA256_DIGEST_LENGTH + SHA256_DIGEST_LENGTH;
// A structure that holds an usage entry and its signature.
struct SignedEntryBlock {
uint8_t signature[SHA256_DIGEST_LENGTH];
uint8_t iv[SHA256_DIGEST_LENGTH];
uint8_t verification[kMagicLength];
wvoec_mock::StoredUsageEntry data;
};
// This has the data in the header of constant size. There is also an array
// of generation numbers.
struct SignedHeaderBlock {
uint8_t signature[SHA256_DIGEST_LENGTH];
uint8_t iv[SHA256_DIGEST_LENGTH];
uint8_t verification[kMagicLength];
int64_t master_generation;
uint64_t count;
};
} // namespace
namespace wvoec_mock {
UsageTableEntry::UsageTableEntry(const std::vector<uint8_t> &pst_hash,
SessionContext *ctx)
: pst_hash_(pst_hash),
time_of_license_received_(time(NULL)),
time_of_first_decrypt_(0),
time_of_last_decrypt_(0),
status_(kUnused),
session_(ctx) {}
UsageTableEntry::~UsageTableEntry() {
if (session_) session_->ReleaseUsageEntry();
UsageTableEntry::UsageTableEntry(UsageTable* table, uint32_t index,
int64_t generation)
: usage_table_(table), recent_decrypt_(false), forbid_report_(true) {
memset(&data_, 0, sizeof(data_));
data_.generation_number = generation;
data_.index = index;
}
UsageTableEntry::UsageTableEntry(const StoredUsageEntry *buffer) {
pst_hash_.assign(buffer->pst_hash, buffer->pst_hash + SHA256_DIGEST_LENGTH);
time_of_license_received_ = buffer->time_of_license_received;
time_of_first_decrypt_ = buffer->time_of_first_decrypt;
time_of_last_decrypt_ = buffer->time_of_last_decrypt;
status_ = buffer->status;
mac_key_server_.assign(buffer->mac_key_server,
buffer->mac_key_server + wvcdm::MAC_KEY_SIZE);
mac_key_client_.assign(buffer->mac_key_client,
buffer->mac_key_client + wvcdm::MAC_KEY_SIZE);
session_ = NULL;
UsageTableEntry::~UsageTableEntry() { usage_table_->ReleaseEntry(data_.index); }
OEMCryptoResult UsageTableEntry::SetPST(const uint8_t* pst, size_t pst_length) {
if (pst_length > kMaxPSTLength) return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
data_.pst_length = pst_length;
if (!pst) return OEMCrypto_ERROR_INVALID_CONTEXT;
memcpy(data_.pst, pst, pst_length);
data_.time_of_license_received = time(NULL);
return OEMCrypto_SUCCESS;
}
void UsageTableEntry::SaveToBuffer(StoredUsageEntry *buffer) {
if (pst_hash_.size() != sizeof(buffer->pst_hash)) {
LOGE("Coding Error: pst hash has wrong size.");
return;
bool UsageTableEntry::VerifyPST(const uint8_t* pst, size_t pst_length) {
if (pst_length > kMaxPSTLength) return false;
if (data_.pst_length != pst_length) return false;
if (!pst) return false;
return 0 == memcmp(pst, data_.pst, pst_length);
}
bool UsageTableEntry::VerifyMacKeys(const std::vector<uint8_t>& server,
const std::vector<uint8_t>& client) {
return (server.size() == wvcdm::MAC_KEY_SIZE) &&
(client.size() == wvcdm::MAC_KEY_SIZE) &&
(0 == memcmp(&server[0], data_.mac_key_server, wvcdm::MAC_KEY_SIZE)) &&
(0 == memcmp(&client[0], data_.mac_key_client, wvcdm::MAC_KEY_SIZE));
}
bool UsageTableEntry::SetMacKeys(const std::vector<uint8_t>& server,
const std::vector<uint8_t>& client) {
if ((server.size() != wvcdm::MAC_KEY_SIZE) ||
(client.size() != wvcdm::MAC_KEY_SIZE))
return false;
memcpy(data_.mac_key_server, &server[0], wvcdm::MAC_KEY_SIZE);
memcpy(data_.mac_key_client, &client[0], wvcdm::MAC_KEY_SIZE);
return true;
}
bool UsageTableEntry::CheckForUse() {
if (Inactive()) return false;
recent_decrypt_ = true;
if (data_.status == kUnused) {
data_.status = kActive;
data_.time_of_first_decrypt = time(NULL);
data_.generation_number++;
usage_table_->IncrementGeneration();
}
memcpy(buffer->pst_hash, &pst_hash_[0], pst_hash_.size());
buffer->time_of_license_received = time_of_license_received_;
buffer->time_of_first_decrypt = time_of_first_decrypt_;
buffer->time_of_last_decrypt = time_of_last_decrypt_;
buffer->status = status_;
memcpy(buffer->mac_key_server, &mac_key_server_[0], wvcdm::MAC_KEY_SIZE);
memcpy(buffer->mac_key_client, &mac_key_client_[0], wvcdm::MAC_KEY_SIZE);
return true;
}
void UsageTableEntry::Deactivate() {
if (status_ == kUnused) {
status_ = kInactiveUnused;
} else if (status_ == kActive) {
status_ = kInactiveUsed;
}
if (session_) {
session_->ReleaseUsageEntry();
session_ = NULL;
void UsageTableEntry::Deactivate(const std::vector<uint8_t>& pst) {
if (data_.status == kUnused) {
data_.status = kInactiveUnused;
} else if (data_.status == kActive) {
data_.status = kInactiveUsed;
}
forbid_report_ = false;
data_.generation_number++;
usage_table_->IncrementGeneration();
}
bool UsageTableEntry::UpdateTime() {
int64_t now = time(NULL);
switch (status_) {
case kUnused:
status_ = kActive;
time_of_first_decrypt_ = now;
time_of_last_decrypt_ = now;
return true;
case kActive:
time_of_last_decrypt_ = now;
return true;
case kInactive:
case kInactiveUsed:
case kInactiveUnused:
return false;
OEMCryptoResult UsageTableEntry::ReportUsage(const std::vector<uint8_t>& pst,
uint8_t* buffer,
size_t* buffer_length) {
if (forbid_report_) return OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE;
if (recent_decrypt_) return OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE;
if (pst.size() == 0 || pst.size() > kMaxPSTLength ||
pst.size() != data_.pst_length) {
LOGE("ReportUsage: bad pst length = %d, should be %d.",
pst.size(), data_.pst_length);
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (memcmp(&pst[0], data_.pst, data_.pst_length)) {
LOGE("ReportUsage: wrong pst %s, should be %s.",
wvcdm::b2a_hex(pst).c_str(),
wvcdm::HexEncode(data_.pst, data_.pst_length).c_str());
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
return false;
}
OEMCryptoResult UsageTableEntry::ReportUsage(SessionContext *session,
const std::vector<uint8_t> &pst,
uint8_t *buffer,
size_t *buffer_length) {
size_t length_needed = wvcdm::Unpacked_PST_Report::report_size(pst.size());
if (*buffer_length < length_needed) {
*buffer_length = length_needed;
@@ -112,206 +147,426 @@ OEMCryptoResult UsageTableEntry::ReportUsage(SessionContext *session,
}
wvcdm::Unpacked_PST_Report pst_report(buffer);
int64_t now = time(NULL);
pst_report.set_seconds_since_license_received(now - time_of_license_received_);
pst_report.set_seconds_since_first_decrypt(now - time_of_first_decrypt_);
pst_report.set_seconds_since_last_decrypt(now - time_of_last_decrypt_);
pst_report.set_status(status_);
pst_report.set_seconds_since_license_received(now -
data_.time_of_license_received);
pst_report.set_seconds_since_first_decrypt(now - data_.time_of_first_decrypt);
pst_report.set_seconds_since_last_decrypt(now - data_.time_of_last_decrypt);
pst_report.set_status(data_.status);
pst_report.set_clock_security_level(kSecureTimer);
pst_report.set_pst_length(static_cast<uint8_t>(pst.size()));
memcpy(pst_report.pst(), &pst[0], pst.size());
pst_report.set_pst_length(data_.pst_length);
memcpy(pst_report.pst(), data_.pst, data_.pst_length);
unsigned int md_len = SHA_DIGEST_LENGTH;
if (!HMAC(EVP_sha1(), &mac_key_client_[0], mac_key_client_.size(),
if (!HMAC(EVP_sha1(), data_.mac_key_client, wvcdm::MAC_KEY_SIZE,
buffer + SHA_DIGEST_LENGTH, length_needed - SHA_DIGEST_LENGTH,
pst_report.signature(), &md_len)) {
LOGE("UsageTableEntry: could not compute signature.");
LOGE("ReportUsage: could not compute signature.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
session->set_mac_key_server(mac_key_server_);
session->set_mac_key_client(mac_key_client_);
return OEMCrypto_SUCCESS;
}
bool UsageTableEntry::VerifyOrSetMacKeys(const std::vector<uint8_t> &server,
const std::vector<uint8_t> &client) {
if (mac_key_server_.size() == 0) { // Not set yet, so set it now.
mac_key_server_ = server;
mac_key_client_ = client;
return true;
} else {
return (mac_key_server_ == server && mac_key_client_ == client);
void UsageTableEntry::UpdateAndIncrement() {
if (recent_decrypt_) {
data_.time_of_last_decrypt = time(NULL);
recent_decrypt_ = false;
}
data_.generation_number++;
usage_table_->IncrementGeneration();
forbid_report_ = false;
}
UsageTable::UsageTable(CryptoEngine *ce) {
ce_ = ce;
generation_ = 0;
table_.clear();
// Load saved table.
wvcdm::FileSystem *file_system = ce->file_system();
wvcdm::File *file;
std::string path;
// Note: this path is OK for a real implementation, but using security level 1
// would be better.
if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3,
&path)) {
LOGE("UsageTable: Unable to get base path");
return;
}
std::string filename = path + "UsageTable.dat";
if (!file_system->Exists(filename)) {
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
LOGI("UsageTable: No saved usage table. Creating new table.");
}
return;
}
size_t file_size = file_system->FileSize(filename);
std::vector<uint8_t> encrypted_buffer(file_size);
std::vector<uint8_t> buffer(file_size);
StoredUsageTable *stored_table =
reinterpret_cast<StoredUsageTable *>(&buffer[0]);
StoredUsageTable *encrypted_table =
reinterpret_cast<StoredUsageTable *>(&encrypted_buffer[0]);
file = file_system->Open(filename, wvcdm::FileSystem::kReadOnly);
if (!file) {
LOGE("UsageTable: File open failed: %s", path.c_str());
return;
}
file->Read(reinterpret_cast<char *>(&encrypted_buffer[0]), file_size);
file->Close();
// Verify the signature of the usage table file.
OEMCryptoResult UsageTableEntry::SaveData(CryptoEngine* ce,
SessionContext* session,
uint8_t* signed_buffer,
size_t buffer_size) {
// buffer_size was determined by calling function.
if (buffer_size != SignedEntrySize()) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
std::vector<uint8_t> clear_buffer(buffer_size);
memset(&clear_buffer[0], 0, buffer_size);
memset(signed_buffer, 0, buffer_size);
SignedEntryBlock* clear =
reinterpret_cast<SignedEntryBlock*>(&clear_buffer[0]);
SignedEntryBlock* encrypted =
reinterpret_cast<SignedEntryBlock*>(signed_buffer);
clear->data = this->data_; // Copy the current data.
memcpy(clear->verification, kEntryVerification, kMagicLength);
// This should be encrypted and signed with a device specific key.
// For the reference implementation, I'm just going to use the keybox key.
const bool override_to_real = true;
const std::vector<uint8_t> &key = ce_->DeviceRootKey(override_to_real);
const std::vector<uint8_t>& key = ce->DeviceRootKey(override_to_real);
uint8_t computed_signature[SHA256_DIGEST_LENGTH];
unsigned int sig_length = sizeof(computed_signature);
if (!HMAC(EVP_sha256(), &key[0], key.size(),
&encrypted_buffer[SHA256_DIGEST_LENGTH],
file_size - SHA256_DIGEST_LENGTH, computed_signature,
&sig_length)) {
LOGE("UsageTable: Could not recreate signature.");
table_.clear();
return;
}
if (memcmp(encrypted_table->signature, computed_signature, sig_length)) {
LOGE("UsageTable: Invalid signature given: %s",
wvcdm::HexEncode(&encrypted_buffer[0], sig_length).c_str());
LOGE("UsageTable: Invalid signature computed: %s",
wvcdm::HexEncode(computed_signature, sig_length).c_str());
table_.clear();
return;
}
// Next, decrypt the table.
uint8_t iv_buffer[wvcdm::KEY_IV_SIZE];
memcpy(iv_buffer, encrypted_table->iv, wvcdm::KEY_IV_SIZE);
AES_KEY aes_key;
AES_set_decrypt_key(&key[0], 128, &aes_key);
AES_cbc_encrypt(&encrypted_buffer[SHA256_DIGEST_LENGTH + wvcdm::KEY_IV_SIZE],
&buffer[SHA256_DIGEST_LENGTH + wvcdm::KEY_IV_SIZE],
file_size - SHA256_DIGEST_LENGTH - wvcdm::KEY_IV_SIZE,
&aes_key, iv_buffer, AES_DECRYPT);
// Next, read the generation number from a different location.
// On a real implementation, you should NOT put the generation number in
// a file in user space. It should be stored in secure memory. For the
// reference implementation, we'll just pretend this is secure.
std::string filename2 = path + "GenerationNumber.dat";
file = file_system->Open(filename2, wvcdm::FileSystem::kReadOnly);
if (!file) {
LOGE("UsageTable: File open failed: %s (clearing table)", path.c_str());
generation_ = 0;
table_.clear();
return;
}
file->Read(reinterpret_cast<char *>(&generation_), sizeof(int64_t));
file->Close();
if (stored_table->generation == generation_ + 1) {
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
LOGW("UsageTable: File is one generation old. Acceptable rollback.");
}
} else if (stored_table->generation == generation_ - 1) {
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
LOGW("UsageTable: File is one generation new. Acceptable rollback.");
}
// This might happen if the generation number was rolled back?
} else if (stored_table->generation != generation_) {
LOGE("UsageTable: Rollback detected. Clearing Usage Table. %lx -> %lx",
generation_, stored_table->generation);
table_.clear();
generation_ = 0;
return;
}
// At this point, the stored table looks valid. We can load in all the
// entries.
for (uint64_t i = 0; i < stored_table->count; i++) {
UsageTableEntry *entry =
new UsageTableEntry(&stored_table->entries[i].entry);
table_[entry->pst_hash()] = entry;
}
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
LOGI("UsageTable: loaded %d entries.", stored_table->count);
}
}
bool UsageTable::SaveToFile() {
// This is always called by a locking function.
// Update the generation number so we can detect rollback.
generation_++;
// Now save data to the file as seen in the constructor, above.
size_t file_size = sizeof(StoredUsageTable) +
table_.size() * sizeof(AlignedStoredUsageEntry);
std::vector<uint8_t> buffer(file_size);
std::vector<uint8_t> encrypted_buffer(file_size);
StoredUsageTable *stored_table =
reinterpret_cast<StoredUsageTable *>(&buffer[0]);
StoredUsageTable *encrypted_table =
reinterpret_cast<StoredUsageTable *>(&encrypted_buffer[0]);
stored_table->generation = generation_;
stored_table->count = 0;
for (EntryMap::iterator i = table_.begin(); i != table_.end(); ++i) {
UsageTableEntry *entry = i->second;
entry->SaveToBuffer(&stored_table->entries[stored_table->count].entry);
stored_table->count++;
}
// This should be encrypted and signed with a device specific key.
// For the reference implementation, I'm just going to use the keybox key.
const bool override_to_real = true;
const std::vector<uint8_t> &key = ce_->DeviceRootKey(override_to_real);
// Encrypt the table.
RAND_bytes(encrypted_table->iv, wvcdm::KEY_IV_SIZE);
uint8_t iv_buffer[wvcdm::KEY_IV_SIZE];
memcpy(iv_buffer, encrypted_table->iv, wvcdm::KEY_IV_SIZE);
// Encrypt the entry.
RAND_bytes(encrypted->iv, wvcdm::KEY_IV_SIZE);
uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; // working iv buffer.
memcpy(iv_buffer, encrypted->iv, wvcdm::KEY_IV_SIZE);
AES_KEY aes_key;
AES_set_encrypt_key(&key[0], 128, &aes_key);
AES_cbc_encrypt(&buffer[SHA256_DIGEST_LENGTH + wvcdm::KEY_IV_SIZE],
&encrypted_buffer[SHA256_DIGEST_LENGTH + wvcdm::KEY_IV_SIZE],
file_size - SHA256_DIGEST_LENGTH - wvcdm::KEY_IV_SIZE,
&aes_key, iv_buffer, AES_ENCRYPT);
AES_cbc_encrypt(
&clear_buffer[kEncryptionOffset], &signed_buffer[kEncryptionOffset],
buffer_size - kEncryptionOffset, &aes_key, iv_buffer, AES_ENCRYPT);
// Sign the table.
unsigned int sig_length = sizeof(stored_table->signature);
// Sign the entry.
unsigned int sig_length = SHA256_DIGEST_LENGTH;
if (!HMAC(EVP_sha256(), &key[0], key.size(),
&encrypted_buffer[SHA256_DIGEST_LENGTH],
file_size - SHA256_DIGEST_LENGTH, encrypted_table->signature,
&signed_buffer[SHA256_DIGEST_LENGTH],
buffer_size - SHA256_DIGEST_LENGTH, encrypted->signature,
&sig_length)) {
LOGE("UsageTable: Could not sign table.");
return false;
LOGE("SaveUsageEntry: Could not sign entry.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
OEMCryptoResult UsageTableEntry::LoadData(CryptoEngine* ce, uint32_t index,
const std::vector<uint8_t>& buffer) {
if (buffer.size() < SignedEntrySize()) return OEMCrypto_ERROR_SHORT_BUFFER;
if (buffer.size() > SignedEntrySize())
LOGW("LoadUsageTableEntry: buffer is large. %d > %d", buffer.size(),
SignedEntrySize());
std::vector<uint8_t> clear_buffer(buffer.size());
SignedEntryBlock* clear =
reinterpret_cast<SignedEntryBlock*>(&clear_buffer[0]);
const SignedEntryBlock* encrypted =
reinterpret_cast<const SignedEntryBlock*>(&buffer[0]);
// This should be encrypted and signed with a device specific key.
// For the reference implementation, I'm just going to use the keybox key.
const bool override_to_real = true;
const std::vector<uint8_t>& key = ce->DeviceRootKey(override_to_real);
// Verify the signature of the usage entry. Sign encrypted into clear buffer.
unsigned int sig_length = SHA256_DIGEST_LENGTH;
if (!HMAC(EVP_sha256(), &key[0], key.size(), &buffer[SHA256_DIGEST_LENGTH],
buffer.size() - SHA256_DIGEST_LENGTH, clear->signature,
&sig_length)) {
LOGE("LoadUsageEntry: Could not sign entry.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (memcmp(clear->signature, encrypted->signature, SHA256_DIGEST_LENGTH)) {
LOGE("LoadUsageEntry: Signature did not match.");
LOGE("LoadUsageEntry: Invalid signature given: %s",
wvcdm::HexEncode(encrypted->signature, sig_length).c_str());
LOGE("LoadUsageEntry: Invalid signature computed: %s",
wvcdm::HexEncode(clear->signature, sig_length).c_str());
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
}
wvcdm::FileSystem *file_system = ce_->file_system();
wvcdm::File *file;
// Next, decrypt the entry.
uint8_t iv_buffer[wvcdm::KEY_IV_SIZE];
memcpy(iv_buffer, encrypted->iv, wvcdm::KEY_IV_SIZE);
AES_KEY aes_key;
AES_set_decrypt_key(&key[0], 128, &aes_key);
AES_cbc_encrypt(&buffer[kEncryptionOffset], &clear_buffer[kEncryptionOffset],
buffer.size() - kEncryptionOffset, &aes_key, iv_buffer,
AES_DECRYPT);
// Check the verification string is correct.
if (memcmp(kEntryVerification, clear->verification, kMagicLength)) {
LOGE("LoadUsageEntry: Invalid magic: %s=%8.8s expected: %s=%8.8s",
wvcdm::HexEncode(clear->verification, kMagicLength).c_str(),
clear->verification,
wvcdm::HexEncode(reinterpret_cast<const uint8_t*>(kEntryVerification),
kMagicLength).c_str(),
reinterpret_cast<const uint8_t*>(kEntryVerification));
return OEMCrypto_ERROR_BAD_MAGIC;
}
// Check that the index is correct.
if (index != clear->data.index) {
LOGE("LoadUsageEntry: entry says index is %d, not %d", clear->data.index,
index);
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (clear->data.status > kInactiveUnused) {
LOGE("LoadUsageEntry: entry has bad status %d", clear->data.status);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
this->data_ = clear->data;
return OEMCrypto_SUCCESS;
}
size_t UsageTableEntry::SignedEntrySize() {
size_t base = sizeof(SignedEntryBlock);
// round up to make even number of blocks:
size_t blocks = (base - 1) / wvcdm::KEY_IV_SIZE + 1;
return blocks * wvcdm::KEY_IV_SIZE;
}
size_t UsageTable::SignedHeaderSize(size_t count) {
size_t base = sizeof(SignedHeaderBlock) + count * sizeof(int64_t);
// round up to make even number of blocks:
size_t blocks = (base - 1) / wvcdm::KEY_IV_SIZE + 1;
return blocks * wvcdm::KEY_IV_SIZE;
}
OEMCryptoResult UsageTable::UpdateUsageEntry(SessionContext* session,
UsageTableEntry* entry,
uint8_t* header_buffer,
size_t* header_buffer_length,
uint8_t* entry_buffer,
size_t* entry_buffer_length) {
size_t signed_header_size = SignedHeaderSize(generation_numbers_.size());
if (*entry_buffer_length < UsageTableEntry::SignedEntrySize() ||
*header_buffer_length < signed_header_size) {
*entry_buffer_length = UsageTableEntry::SignedEntrySize();
*header_buffer_length = signed_header_size;
return OEMCrypto_ERROR_SHORT_BUFFER;
}
*entry_buffer_length = UsageTableEntry::SignedEntrySize();
*header_buffer_length = signed_header_size;
if ((!header_buffer) || (!entry_buffer))
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
entry->UpdateAndIncrement();
generation_numbers_[entry->index()] = entry->generation_number();
OEMCryptoResult result =
entry->SaveData(ce_, session, entry_buffer, *entry_buffer_length);
if (result != OEMCrypto_SUCCESS) return result;
result = SaveUsageTableHeader(header_buffer, *header_buffer_length);
return result;
}
OEMCryptoResult UsageTable::CreateNewUsageEntry(SessionContext* session,
UsageTableEntry** entry,
uint32_t* usage_entry_number) {
if (!header_loaded_) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
if (!entry) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
if (!usage_entry_number) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
uint32_t index = generation_numbers_.size();
UsageTableEntry* new_entry =
new UsageTableEntry(this, index, master_generation_number_);
generation_numbers_.push_back(master_generation_number_);
sessions_.push_back(session);
master_generation_number_++;
*entry = new_entry;
*usage_entry_number = index;
return OEMCrypto_SUCCESS;
}
OEMCryptoResult UsageTable::LoadUsageEntry(SessionContext* session,
UsageTableEntry** entry,
uint32_t index,
const std::vector<uint8_t>& buffer) {
if (!header_loaded_) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
if (!entry) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
if (index >= generation_numbers_.size())
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
if (sessions_[index]) {
LOGE("LoadUsageEntry: index %d used by other session.", index);
return OEMCrypto_ERROR_INVALID_SESSION;
}
UsageTableEntry* new_entry =
new UsageTableEntry(this, index, master_generation_number_);
OEMCryptoResult status = new_entry->LoadData(ce_, index, buffer);
if (status != OEMCrypto_SUCCESS) {
delete new_entry;
return status;
}
if (new_entry->generation_number() != generation_numbers_[index]) {
LOGE("Generation SKEW: %ld -> %ld", new_entry->generation_number(),
generation_numbers_[index]);
if ((new_entry->generation_number() + 1 < generation_numbers_[index]) ||
(new_entry->generation_number() - 1 > generation_numbers_[index])) {
delete new_entry;
return OEMCrypto_ERROR_GENERATION_SKEW;
}
status = OEMCrypto_WARNING_GENERATION_SKEW;
}
sessions_[index] = session;
*entry = new_entry;
return status;
}
OEMCryptoResult UsageTable::ShrinkUsageTableHeader(
uint32_t new_table_size, uint8_t* header_buffer,
size_t* header_buffer_length) {
if (new_table_size > generation_numbers_.size())
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
size_t signed_header_size = SignedHeaderSize(new_table_size);
if (*header_buffer_length < signed_header_size) {
*header_buffer_length = signed_header_size;
return OEMCrypto_ERROR_SHORT_BUFFER;
}
*header_buffer_length = signed_header_size;
for (size_t i = new_table_size; i < sessions_.size(); i++) {
if (sessions_[i]) {
LOGE("ShrinkUsageTableHeader: session open for %d", i);
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
}
generation_numbers_.resize(new_table_size);
sessions_.resize(new_table_size);
master_generation_number_++;
return SaveUsageTableHeader(header_buffer, *header_buffer_length);
}
OEMCryptoResult UsageTable::SaveUsageTableHeader(uint8_t* signed_buffer,
size_t buffer_size) {
if (!SaveGenerationNumber()) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
size_t count = generation_numbers_.size();
// buffer_size was determined by calling function.
if (buffer_size != SignedHeaderSize(count))
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
std::vector<uint8_t> clear_buffer(buffer_size);
memset(&clear_buffer[0], 0, buffer_size);
memset(signed_buffer, 0, buffer_size);
SignedHeaderBlock* clear =
reinterpret_cast<SignedHeaderBlock*>(&clear_buffer[0]);
SignedHeaderBlock* encrypted =
reinterpret_cast<SignedHeaderBlock*>(signed_buffer);
// Pack the clear data into the clear buffer.
memcpy(clear->verification, kHeaderVerification, kMagicLength);
clear->master_generation = master_generation_number_;
clear->count = count;
// This points to the variable size part of the buffer.
int64_t* stored_generations =
reinterpret_cast<int64_t*>(&clear_buffer[sizeof(SignedHeaderBlock)]);
std::copy(generation_numbers_.begin(), generation_numbers_.begin() + count,
stored_generations);
// This should be encrypted and signed with a device specific key.
// For the reference implementation, I'm just going to use the keybox key.
const bool override_to_real = true;
const std::vector<uint8_t>& key = ce_->DeviceRootKey(override_to_real);
// Encrypt the entry.
RAND_bytes(encrypted->iv, wvcdm::KEY_IV_SIZE);
uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; // working iv buffer.
memcpy(iv_buffer, encrypted->iv, wvcdm::KEY_IV_SIZE);
AES_KEY aes_key;
AES_set_encrypt_key(&key[0], 128, &aes_key);
AES_cbc_encrypt(
&clear_buffer[kEncryptionOffset], &signed_buffer[kEncryptionOffset],
buffer_size - kEncryptionOffset, &aes_key, iv_buffer, AES_ENCRYPT);
// Sign the entry.
unsigned int sig_length = SHA256_DIGEST_LENGTH;
if (!HMAC(EVP_sha256(), &key[0], key.size(),
&signed_buffer[SHA256_DIGEST_LENGTH],
buffer_size - SHA256_DIGEST_LENGTH, encrypted->signature,
&sig_length)) {
LOGE("SaveUsageHeader: Could not sign entry.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
OEMCryptoResult UsageTable::LoadUsageTableHeader(
const std::vector<uint8_t>& buffer) {
if (!LoadGenerationNumber(false)) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
if (buffer.size() < SignedHeaderSize(0)) return OEMCrypto_ERROR_SHORT_BUFFER;
std::vector<uint8_t> clear_buffer(buffer.size());
SignedHeaderBlock* clear =
reinterpret_cast<SignedHeaderBlock*>(&clear_buffer[0]);
const SignedHeaderBlock* encrypted =
reinterpret_cast<const SignedHeaderBlock*>(&buffer[0]);
// This should be encrypted and signed with a device specific key.
// For the reference implementation, I'm just going to use the keybox key.
const bool override_to_real = true;
const std::vector<uint8_t>& key = ce_->DeviceRootKey(override_to_real);
// Verify the signature of the usage entry. Sign encrypted into clear buffer.
unsigned int sig_length = SHA256_DIGEST_LENGTH;
if (!HMAC(EVP_sha256(), &key[0], key.size(), &buffer[SHA256_DIGEST_LENGTH],
buffer.size() - SHA256_DIGEST_LENGTH, clear->signature,
&sig_length)) {
LOGE("LoadUsageTableHeader: Could not sign entry.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (memcmp(clear->signature, encrypted->signature, SHA256_DIGEST_LENGTH)) {
LOGE("LoadUsageTableHeader: Signature did not match.");
LOGE("LoadUsageTableHeader: Invalid signature given: %s",
wvcdm::HexEncode(encrypted->signature, sig_length).c_str());
LOGE("LoadUsageTableHeader: Invalid signature computed: %s",
wvcdm::HexEncode(clear->signature, sig_length).c_str());
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
}
// Next, decrypt the entry.
uint8_t iv_buffer[wvcdm::KEY_IV_SIZE];
memcpy(iv_buffer, encrypted->iv, wvcdm::KEY_IV_SIZE);
AES_KEY aes_key;
AES_set_decrypt_key(&key[0], 128, &aes_key);
AES_cbc_encrypt(&buffer[kEncryptionOffset], &clear_buffer[kEncryptionOffset],
buffer.size() - kEncryptionOffset, &aes_key, iv_buffer,
AES_DECRYPT);
// Check the verification string is correct.
if (memcmp(kHeaderVerification, clear->verification, kMagicLength)) {
LOGE("LoadUsageTableHeader: Invalid magic: %s=%8.8s expected: %s=%8.8s",
wvcdm::HexEncode(clear->verification, kMagicLength).c_str(),
clear->verification,
wvcdm::HexEncode(reinterpret_cast<const uint8_t*>(kHeaderVerification),
kMagicLength).c_str(),
reinterpret_cast<const uint8_t*>(kHeaderVerification));
return OEMCrypto_ERROR_BAD_MAGIC;
}
// Check that size is correct, now that we know what it should be.
if (buffer.size() < SignedHeaderSize(clear->count)) {
return OEMCrypto_ERROR_SHORT_BUFFER;
}
if (buffer.size() > SignedHeaderSize(clear->count)) {
LOGW("LoadUsageTableHeader: buffer is large. %d > %d", buffer.size(),
SignedHeaderSize(clear->count));
}
OEMCryptoResult status = OEMCrypto_SUCCESS;
if (clear->master_generation != master_generation_number_) {
LOGE("Generation SKEW: %ld -> %ld", clear->master_generation,
master_generation_number_);
if ((clear->master_generation + 1 < master_generation_number_) ||
(clear->master_generation - 1 > master_generation_number_)) {
return OEMCrypto_ERROR_GENERATION_SKEW;
}
status = OEMCrypto_WARNING_GENERATION_SKEW;
}
int64_t* stored_generations =
reinterpret_cast<int64_t*>(&clear_buffer[0] + sizeof(SignedHeaderBlock));
generation_numbers_.assign(stored_generations,
stored_generations + clear->count);
sessions_.clear();
sessions_.resize(clear->count);
header_loaded_ = true;
return status;
}
OEMCryptoResult UsageTable::MoveEntry(UsageTableEntry* entry,
uint32_t new_index) {
if (new_index >= generation_numbers_.size()) {
LOGE("MoveEntry: index beyond end of usage table %d >= %d", new_index,
generation_numbers_.size());
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (sessions_[new_index]) {
LOGE("MoveEntry: session open for %d", new_index);
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (!entry) {
LOGE("MoveEntry: null entry");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
sessions_[new_index] = sessions_[entry->index()];
sessions_[entry->index()] = 0;
entry->set_index(new_index);
generation_numbers_[new_index] = master_generation_number_;
entry->set_generation_number(master_generation_number_);
master_generation_number_++;
return OEMCrypto_SUCCESS;
}
void UsageTable::IncrementGeneration() {
master_generation_number_++;
SaveGenerationNumber();
}
bool UsageTable::SaveGenerationNumber() {
wvcdm::FileSystem* file_system = ce_->file_system();
std::string path;
// Note: this path is OK for a real implementation, but using security level 1
// would be better.
@@ -320,115 +575,76 @@ bool UsageTable::SaveToFile() {
LOGE("UsageTable: Unable to get base path");
return false;
}
std::string filename = path + "UsageTable.dat";
if (!file_system->Exists(filename)) {
if (LogCategoryEnabled(kLoggingTraceUsageTable)) {
LOGI("UsageTable: No saved usage table. Creating new table.");
}
}
file = file_system->Open(
filename, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate);
if (!file) {
LOGE("UsageTable: Could not save usage table: %s", path.c_str());
return false;
}
file->Write(reinterpret_cast<char *>(&encrypted_buffer[0]), file_size);
file->Close();
// On a real implementation, you should NOT put the generation number in
// a file in user space. It should be stored in secure memory.
std::string filename2 = path + "GenerationNumber.dat";
file = file_system->Open(
filename2, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate);
std::string filename = path + "GenerationNumber.dat";
wvcdm::File* file = file_system->Open(
filename, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate);
if (!file) {
LOGE("UsageTable: File open failed: %s", path.c_str());
return false;
}
file->Write(reinterpret_cast<char *>(&generation_), sizeof(int64_t));
file->Write(reinterpret_cast<char*>(&master_generation_number_),
sizeof(int64_t));
file->Close();
return true;
}
UsageTableEntry *UsageTable::FindEntry(const std::vector<uint8_t> &pst) {
wvcdm::AutoLock lock(lock_);
return FindEntryLocked(pst);
}
UsageTableEntry *UsageTable::FindEntryLocked(const std::vector<uint8_t> &pst) {
std::vector<uint8_t> pst_hash;
if (!ComputeHash(pst, pst_hash)) {
LOGE("UsageTable: Could not compute hash of pst.");
return NULL;
}
EntryMap::iterator it = table_.find(pst_hash);
if (it == table_.end()) {
return NULL;
}
return it->second;
}
UsageTableEntry *UsageTable::CreateEntry(const std::vector<uint8_t> &pst,
SessionContext *ctx) {
std::vector<uint8_t> pst_hash;
if (!ComputeHash(pst, pst_hash)) {
LOGE("UsageTable: Could not compute hash of pst.");
return NULL;
}
UsageTableEntry *entry = new UsageTableEntry(pst_hash, ctx);
wvcdm::AutoLock lock(lock_);
table_[pst_hash] = entry;
return entry;
}
OEMCryptoResult UsageTable::UpdateTable() {
wvcdm::AutoLock lock(lock_);
if (SaveToFile()) return OEMCrypto_SUCCESS;
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
OEMCryptoResult UsageTable::DeactivateEntry(const std::vector<uint8_t> &pst) {
wvcdm::AutoLock lock(lock_);
UsageTableEntry *entry = FindEntryLocked(pst);
if (!entry) return OEMCrypto_ERROR_INVALID_CONTEXT;
entry->Deactivate();
if (SaveToFile()) return OEMCrypto_SUCCESS;
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
bool UsageTable::DeleteEntry(const std::vector<uint8_t> &pst) {
std::vector<uint8_t> pst_hash;
if (!ComputeHash(pst, pst_hash)) {
LOGE("UsageTable: Could not compute hash of pst.");
bool UsageTable::LoadGenerationNumber(bool or_make_new_one) {
wvcdm::FileSystem* file_system = ce_->file_system();
std::string path;
// Note: this path is OK for a real implementation, but using security level 1
// would be better.
if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3,
&path)) {
LOGE("UsageTable: Unable to get base path");
return false;
}
wvcdm::AutoLock lock(lock_);
EntryMap::iterator it = table_.find(pst_hash);
if (it == table_.end()) return false;
if (it->second) delete it->second;
table_.erase(it);
return SaveToFile();
}
void UsageTable::Clear() {
wvcdm::AutoLock lock(lock_);
for (EntryMap::iterator i = table_.begin(); i != table_.end(); ++i) {
if (i->second) delete i->second;
// On a real implementation, you should NOT put the generation number in
// a file in user space. It should be stored in secure memory.
std::string filename = path + "GenerationNumber.dat";
wvcdm::File* file = file_system->Open(filename, wvcdm::FileSystem::kReadOnly);
if (!file) {
if (or_make_new_one) {
RAND_bytes(reinterpret_cast<uint8_t*>(&master_generation_number_),
sizeof(int64_t));
master_generation_number_ = 0; // TODO(fredgc): remove after debugging.
return true;
}
LOGE("UsageTable: File open failed: %s (clearing table)", path.c_str());
master_generation_number_ = 0;
return false;
}
table_.clear();
file->Read(reinterpret_cast<char*>(&master_generation_number_),
sizeof(int64_t));
file->Close();
return true;
}
bool UsageTable::ComputeHash(const std::vector<uint8_t> &pst,
std::vector<uint8_t> &pst_hash) {
// The PST is not fixed size, and we have no promises that it is reasonbly
// sized, so we compute a hash of it, and store that instead.
pst_hash.resize(SHA256_DIGEST_LENGTH);
SHA256_CTX context;
if (!SHA256_Init(&context)) return false;
if (!SHA256_Update(&context, &pst[0], pst.size())) return false;
if (!SHA256_Final(&pst_hash[0], &context)) return false;
return true;
OEMCryptoResult UsageTable::CreateUsageTableHeader(
uint8_t* header_buffer, size_t* header_buffer_length) {
size_t signed_header_size = SignedHeaderSize(0);
if (*header_buffer_length < signed_header_size) {
*header_buffer_length = signed_header_size;
return OEMCrypto_ERROR_SHORT_BUFFER;
}
*header_buffer_length = signed_header_size;
if (!LoadGenerationNumber(true)) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
sessions_.clear();
generation_numbers_.clear();
header_loaded_ = true;
return SaveUsageTableHeader(header_buffer, *header_buffer_length);
}
OEMCryptoResult UsageTable::CopyOldUsageEntry(UsageTableEntry* entry,
const std::vector<uint8_t>& pst) {
// TODO(fredgc): add this.
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
OEMCryptoResult UsageTable::DeleteOldUsageTable() {
// TODO(fredgc): add this.
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
} // namespace wvoec_mock

View File

@@ -10,8 +10,9 @@
#include <string>
#include <vector>
#include "lock.h"
#include "OEMCryptoCENC.h"
#include "file_store.h"
#include "lock.h"
#include "openssl/sha.h"
#include "wv_cdm_constants.h"
@@ -19,85 +20,104 @@ namespace wvoec_mock {
class SessionContext;
class CryptoEngine;
class UsageTable;
const size_t kMaxPSTLength = 255;
// This is the data we store offline.
struct StoredUsageEntry {
// To save disk space, we only store a hash of the pst.
uint8_t pst_hash[SHA256_DIGEST_LENGTH];
int64_t generation_number;
int64_t time_of_license_received;
int64_t time_of_first_decrypt;
int64_t time_of_last_decrypt;
enum OEMCrypto_Usage_Entry_Status status;
uint8_t mac_key_server[wvcdm::MAC_KEY_SIZE];
uint8_t mac_key_client[wvcdm::MAC_KEY_SIZE];
};
typedef union {
struct StoredUsageEntry entry;
uint8_t padding[128]; // multiple of block size and bigger than entry size.
} AlignedStoredUsageEntry;
struct StoredUsageTable {
uint8_t signature[SHA256_DIGEST_LENGTH];
uint8_t iv[wvcdm::KEY_IV_SIZE];
int64_t generation;
uint64_t count;
AlignedStoredUsageEntry entries[];
uint32_t index;
uint8_t pst[kMaxPSTLength+1]; // add 1 for padding.
uint8_t pst_length;
};
class UsageTableEntry {
public:
UsageTableEntry(const std::vector<uint8_t> &pst_hash, SessionContext *ctx);
UsageTableEntry(const StoredUsageEntry *buffer);
~UsageTableEntry();
void SaveToBuffer(StoredUsageEntry *buffer);
OEMCrypto_Usage_Entry_Status status() const { return status_; }
bool inactive() const { return status_ >= kInactive; }
void Deactivate();
bool UpdateTime();
OEMCryptoResult ReportUsage(SessionContext *session,
const std::vector<uint8_t> &pst,
uint8_t *buffer,
size_t *buffer_length);
// Set them if not set, verify if already set.
bool VerifyOrSetMacKeys(const std::vector<uint8_t> &server,
const std::vector<uint8_t> &client);
const std::vector<uint8_t> &pst_hash() const { return pst_hash_; }
void set_session(SessionContext *session) { session_ = session; }
UsageTableEntry(UsageTable* table, uint32_t index, int64_t generation);
// owner_(owner), session_(session), loaded_(false) {}
~UsageTableEntry(); // Free memory, remove reference in header.
bool Inactive() { return data_.status >= kInactive; }
OEMCryptoResult SetPST(const uint8_t* pst, size_t pst_length);
bool VerifyPST(const uint8_t* pst, size_t pst_length);
bool VerifyMacKeys(const std::vector<uint8_t>& server,
const std::vector<uint8_t>& client);
bool SetMacKeys(const std::vector<uint8_t>& server,
const std::vector<uint8_t>& client);
// Returns false if the entry is inactive. Otherwise, returns true.
// If the status was unused, it is updated, and decrypt times are flaged
// for update.
bool CheckForUse();
void Deactivate(const std::vector<uint8_t>& pst);
OEMCryptoResult ReportUsage(const std::vector<uint8_t>& pst, uint8_t* buffer,
size_t* buffer_length);
void UpdateAndIncrement();
OEMCryptoResult SaveData(CryptoEngine* ce, SessionContext* session,
uint8_t* signed_buffer, size_t buffer_size);
OEMCryptoResult LoadData(CryptoEngine* ce, uint32_t index,
const std::vector<uint8_t>& buffer);
int64_t generation_number() { return data_.generation_number; }
void set_generation_number(int64_t value) { data_.generation_number = value; }
void set_index(int32_t index) { data_.index = index; }
uint32_t index() { return data_.index; }
static size_t SignedEntrySize();
private:
std::vector<uint8_t> pst_hash_;
int64_t time_of_license_received_;
int64_t time_of_first_decrypt_;
int64_t time_of_last_decrypt_;
enum OEMCrypto_Usage_Entry_Status status_;
std::vector<uint8_t> mac_key_server_;
std::vector<uint8_t> mac_key_client_;
SessionContext *session_;
UsageTable* usage_table_; // Owner of this object.
bool recent_decrypt_;
bool forbid_report_;
StoredUsageEntry data_;
};
class UsageTable {
public:
UsageTable(CryptoEngine *ce);
~UsageTable() { Clear(); }
UsageTableEntry *FindEntry(const std::vector<uint8_t> &pst);
UsageTableEntry *CreateEntry(const std::vector<uint8_t> &pst,
SessionContext *ctx);
OEMCryptoResult UpdateTable();
OEMCryptoResult DeactivateEntry(const std::vector<uint8_t> &pst);
bool DeleteEntry(const std::vector<uint8_t> &pst);
void Clear();
UsageTable(CryptoEngine* ce, wvcdm::FileSystem* file_system)
: ce_(ce), file_system_(file_system), header_loaded_(false){};
OEMCryptoResult CreateNewUsageEntry(SessionContext* session,
UsageTableEntry** entry,
uint32_t* usage_entry_number);
OEMCryptoResult LoadUsageEntry(SessionContext* session,
UsageTableEntry** entry, uint32_t index,
const std::vector<uint8_t>& buffer);
OEMCryptoResult UpdateUsageEntry(SessionContext* session,
UsageTableEntry* entry,
uint8_t* header_buffer,
size_t* header_buffer_length,
uint8_t* entry_buffer,
size_t* entry_buffer_length);
OEMCryptoResult MoveEntry(UsageTableEntry* entry, uint32_t new_index);
OEMCryptoResult CopyOldUsageEntry(UsageTableEntry* entry,
const std::vector<uint8_t>& pst);
OEMCryptoResult DeleteOldUsageTable();
OEMCryptoResult CreateUsageTableHeader(uint8_t* header_buffer,
size_t* header_buffer_length);
OEMCryptoResult LoadUsageTableHeader(const std::vector<uint8_t>& buffer);
OEMCryptoResult ShrinkUsageTableHeader(uint32_t new_table_size,
uint8_t* header_buffer,
size_t* header_buffer_length);
// Diassociates this entry with it's session.
void ReleaseEntry(uint32_t index) { sessions_[index] = 0; }
void IncrementGeneration();
static size_t SignedHeaderSize(size_t count);
private:
UsageTableEntry *FindEntryLocked(const std::vector<uint8_t> &pst);
bool SaveToFile();
bool ComputeHash(const std::vector<uint8_t> &pst,
std::vector<uint8_t> &pst_hash);
OEMCryptoResult SaveUsageTableHeader(uint8_t* signed_buffer,
size_t buffer_size);
bool SaveGenerationNumber();
bool LoadGenerationNumber(bool or_make_new_one);
typedef std::map<std::vector<uint8_t>, UsageTableEntry *> EntryMap;
EntryMap table_;
wvcdm::Lock lock_;
int64_t generation_;
CryptoEngine *ce_;
CryptoEngine* ce_;
wvcdm::FileSystem* file_system_;
bool header_loaded_;
int64_t master_generation_number_;
std::vector<int64_t> generation_numbers_;
std::vector<SessionContext*> sessions_;
};
} // namespace wvoec_mock