Files
odkitee/oemcrypto_ta/oemcrypto_usage_table.c
2020-07-24 12:03:58 -07:00

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);
}