977 lines
42 KiB
C
977 lines
42 KiB
C
/* Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
|
|
source code may only be used and distributed under the Widevine Master
|
|
License Agreement. */
|
|
|
|
#include "oemcrypto_usage_table.h"
|
|
|
|
#include "stddef.h"
|
|
#include "string.h"
|
|
|
|
#include "OEMCryptoCENC.h"
|
|
#include "assert_interface.h"
|
|
#include "clock_interface.h"
|
|
#include "crypto_interface.h"
|
|
#include "logging_interface.h"
|
|
#include "oemcrypto_serialized_usage_table.h"
|
|
#include "oemcrypto_session.h"
|
|
|
|
/* Porting layer includes: */
|
|
#include "assert_interface.h"
|
|
#include "generation_number_interface.h"
|
|
|
|
/**
|
|
The usage table consists of a short array of active entries, and a large
|
|
array of possible entries. The usage table header is stored in the large
|
|
array. The large array stores the generation number for each entry and an
|
|
index into the short array if the entry is active. The short array holds
|
|
entries that have been loaded and are connected to a session.
|
|
*/
|
|
|
|
/* Global usage table states. Used to tell if we need to load the usage table or
|
|
* not. */
|
|
typedef enum UsageTableState {
|
|
/* Initial state. */
|
|
USAGE_TABLE_NOT_INITIALIZED = (int)0xC887d04,
|
|
/* Error state. Actually, anything but the other states are considered
|
|
* errors, but we can explicitly set the state to this value. */
|
|
USAGE_TABLE_ERROR_STATE = (int)0xd06ca1,
|
|
/* After the usage table has been initialized to an empty table. The usage
|
|
* table cannot be used in this state because the generation number is not yet
|
|
* valid. */
|
|
USAGE_TABLE_INITIALIZED_BUT_NOT_LOADED_STATE = (int)0x1776dcfa,
|
|
/* After the usage table has been loaded or created with
|
|
* OEMCrypto_LoadUsageTableHeader or OEMCrypto_CreateUsageTableHeader. The key
|
|
* distinction between this state and the previous one is that the generation
|
|
* number is valid in this state. The usage table might be empty in this
|
|
* state, but it is ready to have new entries created. */
|
|
USAGE_TABLE_ACTIVE_STATE = (int)0x7331face,
|
|
} UsageTableState;
|
|
|
|
/* Indicates if a usage entry is active or not. */
|
|
typedef enum UsageEntryState {
|
|
USAGE_ENTRY_ACTIVE = (int)0x42fab1c,
|
|
USAGE_ENTRY_NOT_ACTIVE = (int)0x39abcab4,
|
|
} UsageEntryState;
|
|
|
|
/* Data storage for a usage entry in memory. It has the data for a usage entry
|
|
* that is saved to the filesystem, and also all of the transient state, such as
|
|
* the current session it's tied to and the current status. */
|
|
typedef struct ResidentUsageEntry {
|
|
/* The part of the usage entry that is saved to the file system. */
|
|
SavedUsageEntry data;
|
|
OEMCrypto_SESSION session_id; /* Which session this entry is actively in. */
|
|
UsageEntryState state; /* Whether this entry is active or not. */
|
|
bool forbid_report;
|
|
bool recent_decrypt;
|
|
int next_free_active_entry; /* A linked list of unused active entries. */
|
|
} ResidentUsageEntry;
|
|
|
|
/* A data structure holding the current, active usage table -- that means it has
|
|
* a header and all of the resident entries. */
|
|
typedef struct ActiveUsageTable {
|
|
/* The master generation number. */
|
|
uint64_t master_generation_number;
|
|
/* This number of active usage entries. */
|
|
uint32_t active_table_size;
|
|
/* The head of the free list for active entries. */
|
|
uint32_t first_free_active_entry;
|
|
/* Storage for the active entries. */
|
|
ResidentUsageEntry entries[MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES];
|
|
uint32_t table_size; /* Number of entries in the table. */
|
|
/* These are the generation numbers of each entry. */
|
|
uint64_t generation_numbers[MAX_NUMBER_OF_USAGE_ENTRIES];
|
|
/* Map from usage header index into array of active entries. This is updated
|
|
* when an entry is loaded or created and then again when it is released. */
|
|
struct {
|
|
UsageEntryState state;
|
|
uint32_t active_entry_index;
|
|
} active_entry_map[MAX_NUMBER_OF_USAGE_ENTRIES];
|
|
} ActiveUsageTable;
|
|
|
|
/* A file local variable indicating whether the usage table has been initialized
|
|
* or not. */
|
|
static UsageTableState g_usage_table_state = USAGE_TABLE_NOT_INITIALIZED;
|
|
/* The file local variable holding the current active usage table. This is only
|
|
* valid if |g_usage_table_state| is USAGE_TABLE_ACTIVE. */
|
|
static ActiveUsageTable g_usage_table;
|
|
|
|
/* TODO(b/158771223): figure out a way to avoid __attribute__(packed). */
|
|
typedef struct {
|
|
uint8_t signature[20]; // -- HMAC SHA1 of the rest of the report.
|
|
uint8_t status; // current status of entry. (OEMCrypto_Usage_Entry_Status)
|
|
uint8_t clock_security_level;
|
|
uint8_t pst_length;
|
|
uint8_t padding; // make int64's word aligned.
|
|
int64_t seconds_since_license_received; // now - time_of_license_received
|
|
int64_t seconds_since_first_decrypt; // now - time_of_first_decrypt
|
|
int64_t seconds_since_last_decrypt; // now - time_of_last_decrypt
|
|
uint8_t pst[];
|
|
} __attribute__((packed)) OEMCrypto_PST_Report;
|
|
|
|
/* TODO(b/158131747): add htonll64 to common hton functions. */
|
|
int64_t htonll64(int64_t x) {
|
|
uint8_t* bytes = (uint8_t*)(&x);
|
|
uint64_t y = 0;
|
|
for (int i = 0; i < 8; i++) {
|
|
y = (y << 8) | bytes[i];
|
|
}
|
|
return (int64_t)y;
|
|
}
|
|
|
|
static void ClearTable(void) {
|
|
g_usage_table.active_table_size = 0;
|
|
g_usage_table.first_free_active_entry = 0;
|
|
for (int i = 0; i < MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES; i++) {
|
|
memset(&g_usage_table.entries[i].data, 0, sizeof(SavedUsageEntry));
|
|
g_usage_table.entries[i].session_id = MAX_NUMBER_OF_SESSIONS;
|
|
g_usage_table.entries[i].state = USAGE_ENTRY_NOT_ACTIVE;
|
|
g_usage_table.entries[i].forbid_report = true;
|
|
g_usage_table.entries[i].recent_decrypt = false;
|
|
g_usage_table.entries[i].next_free_active_entry = i + 1;
|
|
}
|
|
g_usage_table.table_size = 0;
|
|
memset(&g_usage_table.generation_numbers, 0,
|
|
sizeof(g_usage_table.generation_numbers));
|
|
for (int i = 0; i < MAX_NUMBER_OF_USAGE_ENTRIES; i++) {
|
|
g_usage_table.active_entry_map[i].state = USAGE_ENTRY_NOT_ACTIVE;
|
|
g_usage_table.active_entry_map[i].active_entry_index =
|
|
MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES;
|
|
}
|
|
}
|
|
|
|
/* This serializes, encrypts, and signs the current header into the given
|
|
* buffer. It is an error if the buffer is not big enough. The header fields
|
|
* and the master generation number in the current header will be updated. */
|
|
static OEMCryptoResult EncryptAndSignHeader(uint8_t* header_buffer,
|
|
size_t header_buffer_length) {
|
|
if (!header_buffer) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (header_buffer_length < SignedHeaderSize(g_usage_table.table_size)) {
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
SavedUsageHeader header = {
|
|
.file_type = USAGE_TABLE_HEADER,
|
|
.format_version = CURRENT_FILE_FORMAT_VERSION,
|
|
.master_generation_number = g_usage_table.master_generation_number,
|
|
.table_size = g_usage_table.table_size,
|
|
};
|
|
memcpy(header.generation_numbers, g_usage_table.generation_numbers,
|
|
header.table_size * sizeof(uint64_t));
|
|
SignedSavedUsageHeader signed_header = {
|
|
.file_type = SIGNED_USAGE_TABLE_HEADER,
|
|
.format_version = CURRENT_FILE_FORMAT_VERSION,
|
|
.buffer_size = PADDED_HEADER_BUFFER_SIZE,
|
|
.buffer = {0},
|
|
};
|
|
uint8_t temp_buffer[PADDED_HEADER_BUFFER_SIZE];
|
|
OEMCryptoResult result =
|
|
PackUsageHeader(temp_buffer, PADDED_HEADER_BUFFER_SIZE, &header);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
/* TODO(b/158766099): encrypt header. */
|
|
memcpy(signed_header.buffer, temp_buffer, PADDED_HEADER_BUFFER_SIZE);
|
|
/* TODO(b/158766099): sign header. */
|
|
memset(signed_header.signature, 0x42, SHA256_DIGEST_LENGTH);
|
|
result = PackSignedUsageHeader(header_buffer, header_buffer_length,
|
|
&signed_header);
|
|
return result;
|
|
}
|
|
|
|
/* This decrypts and deserializes usage table header from the given buffer. The
|
|
* signature of the buffer is verified. The generation number is verified by the
|
|
* calling function. It is an error if the buffer is not big enough. */
|
|
static OEMCryptoResult DecryptAndVerifyHeader(const uint8_t* header_buffer,
|
|
size_t header_buffer_length,
|
|
SavedUsageHeader* header) {
|
|
if (!header_buffer || !header) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* TODO(b/158720996): We can't really check the buffer size because we do not
|
|
* yet know the table size. This should be done in the Unpack* functions. */
|
|
if (header_buffer_length < SignedHeaderSize(0)) {
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
SignedSavedUsageHeader signed_header;
|
|
OEMCryptoResult result = UnpackSignedUsageHeader(
|
|
header_buffer, header_buffer_length, &signed_header);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (signed_header.file_type != SIGNED_USAGE_TABLE_HEADER) {
|
|
/* We were given the wrong file. */
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (signed_header.format_version != CURRENT_FILE_FORMAT_VERSION) {
|
|
/* In the future, we might handle backwards compatible versions. */
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (signed_header.buffer_size != PADDED_HEADER_BUFFER_SIZE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* TODO(b/158766099): verify signature. */
|
|
/* TODO(b/158766099): decrypt header. */
|
|
uint8_t temp_buffer[PADDED_HEADER_BUFFER_SIZE];
|
|
memcpy(&temp_buffer, signed_header.buffer, sizeof(SavedUsageHeader));
|
|
result = UnpackUsageHeader(temp_buffer, PADDED_HEADER_BUFFER_SIZE, header);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (header->file_type != USAGE_TABLE_HEADER) {
|
|
/* We were given the wrong file. */
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (header->format_version != CURRENT_FILE_FORMAT_VERSION) {
|
|
/* In the future, we might handle backwards compatible versions. */
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
/* This serializes, encrypts, and signs the specified entry into the given
|
|
* buffer. It is an error if the buffer is not big enough. The header fields in
|
|
* the entry will be updated, but the generation number will not. */
|
|
static OEMCryptoResult EncryptAndSignEntry(SavedUsageEntry* entry,
|
|
uint8_t* entry_buffer,
|
|
size_t entry_buffer_length) {
|
|
if (!entry_buffer || !entry) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (entry_buffer_length < SignedEntrySize()) {
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
entry->file_type = USAGE_TABLE_ENTRY;
|
|
entry->format_version = CURRENT_FILE_FORMAT_VERSION;
|
|
|
|
SignedSavedUsageEntry signed_entry;
|
|
signed_entry.file_type = SIGNED_USAGE_TABLE_ENTRY;
|
|
signed_entry.format_version = CURRENT_FILE_FORMAT_VERSION;
|
|
signed_entry.buffer_size = PADDED_ENTRY_BUFFER_SIZE;
|
|
memset(signed_entry.buffer, 0, sizeof(signed_entry.buffer));
|
|
uint8_t temp_buffer[PADDED_ENTRY_BUFFER_SIZE];
|
|
OEMCryptoResult result =
|
|
PackUsageEntry(temp_buffer, PADDED_ENTRY_BUFFER_SIZE, entry);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
/* TODO: encrypt entry with device specific key. */
|
|
memcpy(signed_entry.buffer, temp_buffer, PADDED_ENTRY_BUFFER_SIZE);
|
|
/* TODO: sign entry with device specific key. */
|
|
memset(signed_entry.signature, 0x42, SHA256_DIGEST_LENGTH);
|
|
result =
|
|
PackSignedUsageEntry(entry_buffer, entry_buffer_length, &signed_entry);
|
|
return result;
|
|
}
|
|
|
|
/* This decrypts and deserializes a usage table entry from the given buffer. The
|
|
* signature of the buffer is verified. The generation number is verified by the
|
|
* calling function. If the buffer is not big enough the error
|
|
* OEMCrypto_ERROR_SHORT_BUFFER is returned. */
|
|
static OEMCryptoResult DecryptAndVerifyEntry(const uint8_t* entry_buffer,
|
|
size_t entry_buffer_length,
|
|
SavedUsageEntry* entry) {
|
|
if (!entry_buffer || !entry) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (entry_buffer_length < SignedEntrySize()) {
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
SignedSavedUsageEntry signed_entry;
|
|
OEMCryptoResult result =
|
|
UnpackSignedUsageEntry(entry_buffer, entry_buffer_length, &signed_entry);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (signed_entry.file_type != SIGNED_USAGE_TABLE_ENTRY) {
|
|
/* We were given the wrong file. */
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (signed_entry.format_version != CURRENT_FILE_FORMAT_VERSION) {
|
|
/* In the future, we might handle backwards compatible versions. */
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (signed_entry.buffer_size != PADDED_ENTRY_BUFFER_SIZE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* TODO: verify signature with device specific key. */
|
|
/* TODO: decrypt entry with device specific key. */
|
|
uint8_t temp_buffer[PADDED_ENTRY_BUFFER_SIZE];
|
|
memcpy(&temp_buffer, signed_entry.buffer, signed_entry.buffer_size);
|
|
result = UnpackUsageEntry(temp_buffer, signed_entry.buffer_size, entry);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (entry->file_type != USAGE_TABLE_ENTRY) {
|
|
/* We were given the wrong file. */
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (entry->format_version != CURRENT_FILE_FORMAT_VERSION) {
|
|
/* In the future, we might handle backwards compatible versions. */
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
static OEMCryptoResult RollGenerationNumber(ResidentUsageEntry* entry) {
|
|
if (!entry || entry->data.index >= MAX_NUMBER_OF_USAGE_ENTRIES) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
uint64_t new_generation_number = g_usage_table.master_generation_number + 1;
|
|
if (!TEE_SaveGenerationNumber(new_generation_number)) {
|
|
g_usage_table_state = USAGE_TABLE_ERROR_STATE;
|
|
return OEMCrypto_ERROR_SYSTEM_INVALIDATED;
|
|
}
|
|
g_usage_table.master_generation_number = new_generation_number;
|
|
entry->data.generation_number++;
|
|
g_usage_table.generation_numbers[entry->data.index] =
|
|
entry->data.generation_number;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
// Clear out memory for the usage table.
|
|
OEMCryptoResult InitializeUsageTable(void) {
|
|
if (!TEE_PrepareGenerationNumber()) {
|
|
LOGE("Could not load generation number.");
|
|
return OEMCrypto_ERROR_INIT_FAILED;
|
|
}
|
|
ClearTable();
|
|
g_usage_table_state = USAGE_TABLE_INITIALIZED_BUT_NOT_LOADED_STATE;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
// Erase data from usage table.
|
|
OEMCryptoResult TerminateUsageTable(void) {
|
|
g_usage_table_state = USAGE_TABLE_NOT_INITIALIZED;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
/* Create a new usage table. */
|
|
OEMCryptoResult CreateUsageTableHeader(uint8_t* header_buffer,
|
|
size_t* header_buffer_length) {
|
|
if (!header_buffer_length) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
size_t size = SignedHeaderSize(0);
|
|
if (*header_buffer_length < size) {
|
|
*header_buffer_length = size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
/* Clear the table before we check the state -- we want to have an empty table
|
|
* after this function whether there was an error or not. */
|
|
ClearTable();
|
|
*header_buffer_length = size;
|
|
if (g_usage_table_state == USAGE_TABLE_ERROR_STATE) {
|
|
/* Something went wrong. The system should give up and re-init. */
|
|
return OEMCrypto_ERROR_SYSTEM_INVALIDATED;
|
|
} else if (g_usage_table_state == USAGE_TABLE_ACTIVE_STATE) {
|
|
/* Creating a new header when one was already active. This is OK but
|
|
* rare. The system would do this to delete all the entries -- maybe on
|
|
* reprovisioning, but there are other reasons. Howver, we want to keep the
|
|
* same generation number. So we don't try to load it. */
|
|
} else if (g_usage_table_state ==
|
|
USAGE_TABLE_INITIALIZED_BUT_NOT_LOADED_STATE) {
|
|
/* We are creating a brand new table. Try to load the generation number. */
|
|
if (!TEE_LoadGenerationNumber(&g_usage_table.master_generation_number)) {
|
|
g_usage_table_state = USAGE_TABLE_ERROR_STATE;
|
|
return OEMCrypto_ERROR_SYSTEM_INVALIDATED;
|
|
}
|
|
} else {
|
|
/* Only other valid state is not initialized, which is not valid for this
|
|
* function. */
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
g_usage_table_state = USAGE_TABLE_ACTIVE_STATE;
|
|
return EncryptAndSignHeader(header_buffer, *header_buffer_length);
|
|
}
|
|
|
|
/* Load a usage table header. */
|
|
OEMCryptoResult LoadUsageTableHeader(const uint8_t* buffer,
|
|
size_t buffer_length) {
|
|
/* Clear the table before we check the state -- we want to have an empty table
|
|
* before we load a new one, or we want an empty one if there is an error. */
|
|
ClearTable();
|
|
if (g_usage_table_state == USAGE_TABLE_ERROR_STATE) {
|
|
/* Something went wrong. The system should give up and re-init. */
|
|
return OEMCrypto_ERROR_SYSTEM_INVALIDATED;
|
|
} else if (g_usage_table_state == USAGE_TABLE_ACTIVE_STATE) {
|
|
/* Loading a header when one was already active is an indication that the
|
|
* system was going to terminate, but changed its mind - e.g. because
|
|
* delayed termination was canceled. We keep the existing generation
|
|
* numbers. */
|
|
} else if (g_usage_table_state ==
|
|
USAGE_TABLE_INITIALIZED_BUT_NOT_LOADED_STATE) {
|
|
/* We are loading a brand new table. Try to load the generation number. */
|
|
if (!TEE_LoadGenerationNumber(&g_usage_table.master_generation_number)) {
|
|
g_usage_table_state = USAGE_TABLE_ERROR_STATE;
|
|
return OEMCrypto_ERROR_SYSTEM_INVALIDATED;
|
|
}
|
|
} else {
|
|
/* Only other valid state is not initialized, which is not valid for this
|
|
* function. */
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
SavedUsageHeader header;
|
|
OEMCryptoResult result =
|
|
DecryptAndVerifyHeader(buffer, buffer_length, &header);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (g_usage_table.master_generation_number + 1 ==
|
|
header.master_generation_number ||
|
|
g_usage_table.master_generation_number - 1 ==
|
|
header.master_generation_number) {
|
|
/* Skew of 1 is a warning, but we continue on. */
|
|
result = OEMCrypto_WARNING_GENERATION_SKEW;
|
|
} else if (g_usage_table.master_generation_number !=
|
|
header.master_generation_number) {
|
|
/* Skew of more than 1 is an error. Clean the table and return an error. */
|
|
ClearTable();
|
|
/* Leave the state as active, so that the generation number is kept. It is
|
|
* not a security risk to leave an empty usage header in memory. */
|
|
g_usage_table_state = USAGE_TABLE_ACTIVE_STATE;
|
|
return OEMCrypto_ERROR_GENERATION_SKEW;
|
|
}
|
|
g_usage_table.table_size = header.table_size;
|
|
memset(g_usage_table.generation_numbers, 0,
|
|
sizeof(g_usage_table.generation_numbers));
|
|
memcpy(g_usage_table.generation_numbers, header.generation_numbers,
|
|
header.table_size * sizeof(uint64_t));
|
|
g_usage_table_state = USAGE_TABLE_ACTIVE_STATE;
|
|
return result;
|
|
}
|
|
|
|
/* Grabs a free active usage entry off of the free list of active entries. It
|
|
* updates the free list, the active_entry_map, and the session for a new
|
|
* entry. It does NOT update the entry's generation number and does NOT update
|
|
* the usage header. It does sanity checks. */
|
|
static OEMCryptoResult GrabEntry(OEMCryptoSession* session,
|
|
uint32_t usage_entry_number,
|
|
ResidentUsageEntry** entry_ptr) {
|
|
if (!session || !entry_ptr ||
|
|
usage_entry_number >= MAX_NUMBER_OF_USAGE_ENTRIES) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* Check that the session doesn't have entry already. */
|
|
if (session->usage_entry_status != SESSION_HAS_NO_ENTRY) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
/* TODO(b/154764983): return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES; */
|
|
}
|
|
/* Check that another session is not already using this entry. */
|
|
if (g_usage_table.active_entry_map[usage_entry_number].state !=
|
|
USAGE_ENTRY_NOT_ACTIVE) {
|
|
return OEMCrypto_ERROR_INVALID_SESSION;
|
|
}
|
|
/* Check that we have some room for a new active entry. */
|
|
if (g_usage_table.active_table_size >= MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES ||
|
|
g_usage_table.first_free_active_entry >=
|
|
MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES) {
|
|
return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES;
|
|
}
|
|
/* Reserve a new entry. */
|
|
ResidentUsageEntry* entry =
|
|
&g_usage_table.entries[g_usage_table.first_free_active_entry];
|
|
/* Make sure the new entry is not already active. */
|
|
if (entry->state != USAGE_ENTRY_NOT_ACTIVE ||
|
|
g_usage_table.active_entry_map[usage_entry_number].state !=
|
|
USAGE_ENTRY_NOT_ACTIVE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* Initialize the entry. */
|
|
memset(&entry->data, 0, sizeof(SavedUsageEntry));
|
|
entry->session_id = session->session_id;
|
|
entry->state = USAGE_ENTRY_ACTIVE;
|
|
/* Update the active entry map. */
|
|
g_usage_table.active_entry_map[usage_entry_number].state = USAGE_ENTRY_ACTIVE;
|
|
g_usage_table.active_entry_map[usage_entry_number].active_entry_index =
|
|
g_usage_table.first_free_active_entry;
|
|
/* Update the linked list of empty active usage entries. */
|
|
g_usage_table.first_free_active_entry = entry->next_free_active_entry;
|
|
/* Update the session. */
|
|
session->usage_entry_status = SESSION_HAS_NEW_ENTRY;
|
|
session->usage_entry_number = usage_entry_number;
|
|
*entry_ptr = entry;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
/** Release an active entry and put it back onto the free list. This does not
|
|
* save any data. It is usually done with the session is closing or when loading
|
|
* an entry generated an error. */
|
|
OEMCryptoResult ReleaseEntry(OEMCryptoSession* session,
|
|
uint32_t usage_entry_number) {
|
|
if (!session || usage_entry_number >= MAX_NUMBER_OF_USAGE_ENTRIES ||
|
|
g_usage_table_state != USAGE_TABLE_ACTIVE_STATE ||
|
|
session->usage_entry_number != usage_entry_number ||
|
|
session->usage_entry_status == SESSION_HAS_NO_ENTRY) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
uint32_t active_entry_index =
|
|
g_usage_table.active_entry_map[usage_entry_number].active_entry_index;
|
|
if (active_entry_index >= MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* Remove from session. */
|
|
session->usage_entry_status = SESSION_HAS_NO_ENTRY;
|
|
session->usage_entry_number = MAX_NUMBER_OF_USAGE_ENTRIES;
|
|
/* Remove from active entry map. */
|
|
g_usage_table.active_entry_map[usage_entry_number].state =
|
|
USAGE_ENTRY_NOT_ACTIVE;
|
|
g_usage_table.active_entry_map[usage_entry_number].active_entry_index =
|
|
MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES;
|
|
/* Clear the entry. */
|
|
memset(&g_usage_table.entries[active_entry_index].session_id, 0,
|
|
sizeof(SavedUsageEntry));
|
|
g_usage_table.entries[active_entry_index].session_id = MAX_NUMBER_OF_SESSIONS;
|
|
g_usage_table.entries[active_entry_index].state = USAGE_ENTRY_NOT_ACTIVE;
|
|
/* Add back to list of free active entries. */
|
|
g_usage_table.entries[active_entry_index].next_free_active_entry =
|
|
g_usage_table.first_free_active_entry;
|
|
g_usage_table.first_free_active_entry = active_entry_index;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
/* Find the active entry with the specified index into the usage table
|
|
* header. Return null if the entry number is bad or not active. */
|
|
ResidentUsageEntry* GetActiveEntry(uint32_t usage_entry_number) {
|
|
if (usage_entry_number > MAX_NUMBER_OF_USAGE_ENTRIES) {
|
|
return NULL;
|
|
}
|
|
if (g_usage_table.active_entry_map[usage_entry_number].state !=
|
|
USAGE_ENTRY_ACTIVE ||
|
|
g_usage_table.active_entry_map[usage_entry_number].active_entry_index >=
|
|
MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES) {
|
|
return NULL;
|
|
}
|
|
uint32_t index =
|
|
g_usage_table.active_entry_map[usage_entry_number].active_entry_index;
|
|
ResidentUsageEntry* entry = &g_usage_table.entries[index];
|
|
if (entry->state != USAGE_ENTRY_ACTIVE ||
|
|
entry->data.index != usage_entry_number) {
|
|
return NULL;
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
/* Create a new usage entry and tie it to |session|. The new entry will have an
|
|
* entry number at the end of the array of all entries in the header, but it
|
|
* could be anywhere in the array of active entries. */
|
|
OEMCryptoResult CreateNewUsageEntry(OEMCryptoSession* session,
|
|
uint32_t* usage_entry_number) {
|
|
if (!session || !usage_entry_number) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* Check that the header can grow. */
|
|
if (g_usage_table.table_size >= MAX_NUMBER_OF_USAGE_ENTRIES) {
|
|
return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES;
|
|
}
|
|
uint32_t new_index = g_usage_table.table_size;
|
|
ResidentUsageEntry* entry = NULL;
|
|
OEMCryptoResult result = GrabEntry(session, new_index, &entry);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
g_usage_table.table_size++;
|
|
entry->data.index = new_index;
|
|
/* mark session as having new entry. */
|
|
session->usage_entry_status = SESSION_HAS_NEW_ENTRY;
|
|
/* Update the generation numbers. Increment the master GN, and then copy to
|
|
* the entry. Also copy to the header's array of entries. */
|
|
g_usage_table.master_generation_number++;
|
|
entry->data.generation_number = g_usage_table.master_generation_number;
|
|
g_usage_table.generation_numbers[new_index] =
|
|
g_usage_table.master_generation_number;
|
|
*usage_entry_number = new_index;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
/* Load a usage entry that had been saved to the file system and tie it to
|
|
* |session|. */
|
|
OEMCryptoResult LoadUsageEntry(OEMCryptoSession* session,
|
|
uint32_t usage_entry_number,
|
|
const uint8_t* buffer, size_t buffer_length) {
|
|
if (!session) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
ResidentUsageEntry* entry = NULL;
|
|
OEMCryptoResult result = GrabEntry(session, usage_entry_number, &entry);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
/* Load the entry. */
|
|
result = DecryptAndVerifyEntry(buffer, buffer_length, &entry->data);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (entry->data.index != usage_entry_number) {
|
|
entry = NULL;
|
|
ReleaseEntry(session, usage_entry_number);
|
|
return OEMCrypto_ERROR_INVALID_SESSION;
|
|
}
|
|
/* check generation number against header's table of generation numbers. */
|
|
uint64_t entry_gn = entry->data.generation_number;
|
|
uint64_t header_gn = g_usage_table.generation_numbers[usage_entry_number];
|
|
if (entry_gn + 1 == header_gn || entry_gn - 1 == header_gn) {
|
|
/* Skew of 1 is a warning, but we continue on. */
|
|
result = OEMCrypto_WARNING_GENERATION_SKEW;
|
|
} else if (entry_gn != header_gn) {
|
|
/* Skew of more than 1 is an error. Clean the table and return an error. */
|
|
entry = NULL;
|
|
ReleaseEntry(session, usage_entry_number);
|
|
return OEMCrypto_ERROR_GENERATION_SKEW;
|
|
}
|
|
/* mark session as having loaded entry or deactivated entry. */
|
|
if (entry->data.status == kActive || entry->data.status == kUnused) {
|
|
session->usage_entry_status = SESSION_HAS_LOADED_ENTRY;
|
|
} else {
|
|
session->usage_entry_status = SESSION_HAS_DEACTIVATED_ENTRY;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult UpdateUsageEntry(OEMCryptoSession* session,
|
|
uint8_t* header_buffer,
|
|
size_t* header_buffer_length,
|
|
uint8_t* entry_buffer,
|
|
size_t* entry_buffer_length) {
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!header_buffer_length || !entry_buffer_length) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* Check the size and let caller know if it's a short buffer. */
|
|
size_t header_size = SignedHeaderSize(g_usage_table.table_size);
|
|
size_t entry_size = SignedEntrySize();
|
|
if (*header_buffer_length < header_size ||
|
|
*entry_buffer_length < entry_size) {
|
|
*header_buffer_length = header_size;
|
|
*entry_buffer_length = entry_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
*header_buffer_length = header_size;
|
|
*entry_buffer_length = entry_size;
|
|
/* Check that the session has an entry, and it's state is valid. */
|
|
uint32_t index = session->usage_entry_number;
|
|
ResidentUsageEntry* entry = GetActiveEntry(index);
|
|
if (!entry ||
|
|
(session->usage_entry_status != SESSION_HAS_NEW_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_LOADED_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_DEACTIVATED_ENTRY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* The new generation numbers. */
|
|
RollGenerationNumber(entry);
|
|
OEMCryptoResult result =
|
|
EncryptAndSignHeader(header_buffer, *header_buffer_length);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result =
|
|
EncryptAndSignEntry(&entry->data, entry_buffer, *entry_buffer_length);
|
|
if (result == OEMCrypto_SUCCESS) {
|
|
entry->forbid_report = false;
|
|
entry->recent_decrypt = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult UpdateLastPlaybackTime(const OEMCryptoSession* session) {
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
ResidentUsageEntry* entry = GetActiveEntry(session->usage_entry_number);
|
|
if (!entry || (session->usage_entry_status != SESSION_HAS_NEW_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_LOADED_ENTRY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
uint64_t now;
|
|
OEMCrypto_Clock_Security_Level clock_type;
|
|
OEMCryptoResult result = GetSystemTime(&now, &clock_type);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
entry->recent_decrypt = true;
|
|
entry->data.time_of_last_decrypt = now;
|
|
if (entry->data.status == kUnused) {
|
|
/* This is the first playback. */
|
|
entry->data.status = kActive;
|
|
entry->forbid_report = true;
|
|
entry->data.time_of_first_decrypt = now;
|
|
} else if (entry->data.status != kActive) {
|
|
/* License is inactive. Playback should be forbidden. */
|
|
return OEMCrypto_ERROR_KEY_EXPIRED;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SetUsageEntryPST(OEMCryptoSession* session, const uint8_t* pst,
|
|
size_t pst_length) {
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
ResidentUsageEntry* entry = GetActiveEntry(session->usage_entry_number);
|
|
if (!entry || (session->usage_entry_status != SESSION_HAS_NEW_ENTRY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!pst || pst_length == 0 || pst_length > MAX_PST_LENGTH) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
memcpy(entry->data.pst, pst, pst_length);
|
|
entry->data.pst_length = pst_length;
|
|
|
|
/* The PST is set when the license is loaded. This will be removed in v16 when
|
|
* we use the time of license signed instead of time of license loaded. */
|
|
uint64_t now;
|
|
OEMCrypto_Clock_Security_Level clock_type;
|
|
OEMCryptoResult result = GetSystemTime(&now, &clock_type);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
entry->data.time_of_license_received = now;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult VerfiyUsageEntryPST(OEMCryptoSession* session,
|
|
const uint8_t* pst, size_t pst_length) {
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
ResidentUsageEntry* entry = GetActiveEntry(session->usage_entry_number);
|
|
if (!entry || (session->usage_entry_status != SESSION_HAS_LOADED_ENTRY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!pst || pst_length == 0 || pst_length > MAX_PST_LENGTH) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (entry->data.pst_length != pst_length ||
|
|
memcmp(entry->data.pst, pst, pst_length)) {
|
|
return OEMCrypto_ERROR_WRONG_PST;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult SetUsageEntryMacKeys(const OEMCryptoSession* session) {
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
ResidentUsageEntry* entry = GetActiveEntry(session->usage_entry_number);
|
|
if (!entry || (session->usage_entry_status != SESSION_HAS_NEW_ENTRY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoResult result;
|
|
uint8_t wrapped_server_mac[WRAPPED_MAC_KEY_SIZE];
|
|
uint8_t wrapped_client_mac[WRAPPED_MAC_KEY_SIZE];
|
|
memset(wrapped_server_mac, 0, WRAPPED_MAC_KEY_SIZE);
|
|
memset(wrapped_client_mac, 0, WRAPPED_MAC_KEY_SIZE);
|
|
result = WrapCryptoKey(session->mac_key_server, wrapped_server_mac,
|
|
WRAPPED_MAC_KEY_SIZE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = WrapCryptoKey(session->mac_key_client, wrapped_client_mac,
|
|
WRAPPED_MAC_KEY_SIZE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
memcpy(entry->data.mac_key_server, wrapped_server_mac, WRAPPED_MAC_KEY_SIZE);
|
|
memcpy(entry->data.mac_key_client, wrapped_client_mac, WRAPPED_MAC_KEY_SIZE);
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult VerifysageEntryMacKeys(const OEMCryptoSession* session) {
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
ResidentUsageEntry* entry = GetActiveEntry(session->usage_entry_number);
|
|
if (!entry || (session->usage_entry_status != SESSION_HAS_LOADED_ENTRY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
OEMCryptoResult result;
|
|
uint8_t wrapped_server_mac[WRAPPED_MAC_KEY_SIZE];
|
|
uint8_t wrapped_client_mac[WRAPPED_MAC_KEY_SIZE];
|
|
memset(wrapped_server_mac, 0, WRAPPED_MAC_KEY_SIZE);
|
|
memset(wrapped_client_mac, 0, WRAPPED_MAC_KEY_SIZE);
|
|
result = WrapCryptoKey(session->mac_key_server, wrapped_server_mac,
|
|
WRAPPED_MAC_KEY_SIZE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = WrapCryptoKey(session->mac_key_client, wrapped_client_mac,
|
|
WRAPPED_MAC_KEY_SIZE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
if (memcmp(entry->data.mac_key_server, wrapped_server_mac,
|
|
WRAPPED_MAC_KEY_SIZE) ||
|
|
memcmp(entry->data.mac_key_client, wrapped_client_mac,
|
|
WRAPPED_MAC_KEY_SIZE)) {
|
|
return OEMCrypto_ERROR_WRONG_PST;
|
|
}
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult DeactivateUsageEntry(OEMCryptoSession* session,
|
|
const uint8_t* pst, size_t pst_length) {
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
/* Check that the session has an entry, and it's state is valid. */
|
|
uint32_t index = session->usage_entry_number;
|
|
ResidentUsageEntry* entry = GetActiveEntry(index);
|
|
if (!entry ||
|
|
(session->usage_entry_status != SESSION_HAS_NEW_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_LOADED_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_DEACTIVATED_ENTRY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
session->usage_entry_status = SESSION_HAS_DEACTIVATED_ENTRY;
|
|
if (entry->data.status == kUnused) {
|
|
entry->data.status = kInactiveUnused;
|
|
} else if (entry->data.status == kActive) {
|
|
entry->data.status = kInactiveUsed;
|
|
}
|
|
RollGenerationNumber(entry);
|
|
entry->forbid_report = true;
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult ReportUsage(OEMCryptoSession* session, const uint8_t* pst,
|
|
size_t pst_length, uint8_t* buffer,
|
|
size_t* buffer_length) {
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
uint32_t index = session->usage_entry_number;
|
|
ResidentUsageEntry* entry = GetActiveEntry(index);
|
|
if (!entry ||
|
|
(session->usage_entry_status != SESSION_HAS_NEW_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_LOADED_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_DEACTIVATED_ENTRY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
size_t length_needed = sizeof(OEMCrypto_PST_Report) + pst_length;
|
|
if (*buffer_length < length_needed) {
|
|
*buffer_length = length_needed;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
*buffer_length = length_needed;
|
|
|
|
if (entry->forbid_report || entry->recent_decrypt) {
|
|
return OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE;
|
|
}
|
|
if (pst_length == 0 || pst_length > MAX_PST_LENGTH ||
|
|
pst_length != entry->data.pst_length) {
|
|
return OEMCrypto_ERROR_WRONG_PST;
|
|
}
|
|
if (memcmp(entry->data.pst, pst, pst_length)) {
|
|
return OEMCrypto_ERROR_WRONG_PST;
|
|
}
|
|
|
|
uint64_t now;
|
|
OEMCrypto_Clock_Security_Level clock_type;
|
|
OEMCryptoResult result = GetSystemTime(&now, &clock_type);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
|
|
OEMCrypto_PST_Report* report = (OEMCrypto_PST_Report*)buffer;
|
|
report->seconds_since_license_received =
|
|
htonll64(now - entry->data.time_of_license_received);
|
|
report->seconds_since_first_decrypt =
|
|
htonll64(now - entry->data.time_of_first_decrypt);
|
|
report->seconds_since_last_decrypt =
|
|
htonll64(now - entry->data.time_of_last_decrypt);
|
|
report->status = entry->data.status;
|
|
report->clock_security_level = clock_type;
|
|
report->pst_length = pst_length;
|
|
memcpy(report->pst, pst, pst_length);
|
|
if (CheckKey(session->mac_key_client, MAC_KEY_CLIENT, MAC_KEY_CLIENT_SIGN)) {
|
|
/* If the session has mac keys, use those. */
|
|
result = HMAC_SHA1(session->mac_key_client->key_handle,
|
|
buffer + SHA_DIGEST_LENGTH,
|
|
length_needed - SHA_DIGEST_LENGTH, report->signature);
|
|
} else {
|
|
/* Otherwise, we use the mac key from the entry. */
|
|
CryptoKey key;
|
|
result = InitializeCryptoKeyFromWrappedKey(
|
|
&key, entry->data.mac_key_client, WRAPPED_MAC_KEY_SIZE, MAC_KEY_CLIENT,
|
|
MAC_KEY_CLIENT_SIGN, MAC_KEY_SIZE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = HMAC_SHA1(key.key_handle, buffer + SHA_DIGEST_LENGTH,
|
|
length_needed - SHA_DIGEST_LENGTH, report->signature);
|
|
if (OEMCrypto_SUCCESS != FreeCryptoKey(&key)) {
|
|
if (OEMCrypto_SUCCESS != result) return result;
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult SignReleaseRequest(OEMCryptoSession* session,
|
|
const uint8_t* message,
|
|
size_t message_length, uint8_t* signature,
|
|
size_t* signature_length) {
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
uint32_t index = session->usage_entry_number;
|
|
ResidentUsageEntry* entry = GetActiveEntry(index);
|
|
if (!entry ||
|
|
(session->usage_entry_status != SESSION_HAS_NEW_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_LOADED_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_DEACTIVATED_ENTRY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
CryptoKey key;
|
|
OEMCryptoResult result = InitializeCryptoKeyFromWrappedKey(
|
|
&key, entry->data.mac_key_client, WRAPPED_MAC_KEY_SIZE, MAC_KEY_CLIENT,
|
|
MAC_KEY_CLIENT_SIGN, MAC_KEY_SIZE);
|
|
if (result != OEMCrypto_SUCCESS) return result;
|
|
result = HMAC_SHA256(key.key_handle, message, message_length, signature);
|
|
if (OEMCrypto_SUCCESS != FreeCryptoKey(&key)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
OEMCryptoResult MoveEntry(OEMCryptoSession* session, uint32_t new_index) {
|
|
if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
uint32_t index = session->usage_entry_number;
|
|
ResidentUsageEntry* entry = GetActiveEntry(index);
|
|
if (!entry ||
|
|
(session->usage_entry_status != SESSION_HAS_NEW_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_LOADED_ENTRY &&
|
|
session->usage_entry_status != SESSION_HAS_DEACTIVATED_ENTRY)) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (g_usage_table.active_entry_map[new_index].state !=
|
|
USAGE_ENTRY_NOT_ACTIVE ||
|
|
g_usage_table.active_entry_map[new_index].active_entry_index !=
|
|
MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES) {
|
|
return OEMCrypto_ERROR_ENTRY_IN_USE;
|
|
}
|
|
/* Copy data from index to new_index. */
|
|
entry->data.index = new_index;
|
|
g_usage_table.active_entry_map[new_index].state = USAGE_ENTRY_ACTIVE;
|
|
g_usage_table.active_entry_map[new_index].active_entry_index =
|
|
g_usage_table.active_entry_map[index].active_entry_index;
|
|
session->usage_entry_number = new_index;
|
|
/* Update entry's generation number to be max generation number. */
|
|
entry->data.generation_number = g_usage_table.master_generation_number;
|
|
g_usage_table.generation_numbers[new_index] =
|
|
g_usage_table.master_generation_number;
|
|
|
|
/* Mark old index as unused. */
|
|
g_usage_table.active_entry_map[index].state = USAGE_ENTRY_NOT_ACTIVE;
|
|
g_usage_table.active_entry_map[index].active_entry_index =
|
|
MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES;
|
|
|
|
return OEMCrypto_SUCCESS;
|
|
}
|
|
|
|
OEMCryptoResult ShrinkUsageTableHeader(uint32_t new_entry_count,
|
|
uint8_t* header_buffer,
|
|
size_t* header_buffer_length) {
|
|
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
if (new_entry_count >= g_usage_table.table_size) {
|
|
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
|
}
|
|
for (uint32_t i = new_entry_count; i < g_usage_table.table_size; i++) {
|
|
if (g_usage_table.active_entry_map[i].state != USAGE_ENTRY_NOT_ACTIVE) {
|
|
return OEMCrypto_ERROR_ENTRY_IN_USE;
|
|
}
|
|
}
|
|
size_t header_size = SignedHeaderSize(new_entry_count);
|
|
if (header_size > *header_buffer_length) {
|
|
*header_buffer_length = header_size;
|
|
return OEMCrypto_ERROR_SHORT_BUFFER;
|
|
}
|
|
for (uint32_t i = new_entry_count; i < g_usage_table.table_size; i++) {
|
|
g_usage_table.active_entry_map[i].state = USAGE_ENTRY_NOT_ACTIVE;
|
|
g_usage_table.active_entry_map[i].active_entry_index =
|
|
MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES;
|
|
g_usage_table.generation_numbers[i] = 0;
|
|
}
|
|
g_usage_table.table_size = new_entry_count;
|
|
return EncryptAndSignHeader(header_buffer, *header_buffer_length);
|
|
}
|