Files
oemcrypto/oemcrypto/opk/oemcrypto_ta/oemcrypto_usage_table.c
Googler 98f0721662 OEMCrypto and OPK 18.10.0
GitOrigin-RevId: 987123747bd0be50fc5e4e89ec26eaa6d215bc36
2025-06-09 23:55:51 -07:00

1342 lines
54 KiB
C

/* Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
source code may only be used and distributed under the Widevine
License Agreement. */
#include "oemcrypto_usage_table.h"
#include <inttypes.h>
#include <string.h>
#include "OEMCryptoCENC.h"
#include "odk_endian.h"
#include "odk_util.h"
#include "oemcrypto_check_macros.h"
#include "oemcrypto_compiler_detection.h"
#include "oemcrypto_object_table.h"
#include "oemcrypto_serialized_usage_table.h"
#include "wtpi_clock_interface_layer1.h"
#include "wtpi_device_key_interface.h"
#include "wtpi_generation_number_interface.h"
#include "wtpi_logging_interface.h"
#include "wtpi_ref_compat_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)0xec887d04,
/* 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)0x7bd06ca1,
/* 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;
/* 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 UsageEntry {
/* 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. */
bool forbid_report;
bool recent_decrypt;
bool is_loaded;
} UsageEntry;
DEFINE_OBJECT_TABLE(usage_entry_table, UsageEntry,
MAX_NUMBER_OF_ACTIVE_USAGE_ENTRIES, NULL);
/* A data structure holding the current, active usage table -- that means it has
* a header and all of the resident entries. */
typedef struct UsageTable {
/* The master generation number. */
uint64_t master_generation_number;
/* Storage for the active entries. */
UsageEntry* entries[MAX_NUMBER_OF_USAGE_ENTRIES];
/* Number of entries in the |generation_numbers| table. */
uint32_t table_size;
/* These are the generation numbers of each entry. */
uint64_t generation_numbers[MAX_NUMBER_OF_USAGE_ENTRIES];
} UsageTable;
/* 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 UsageTable g_usage_table;
#if __has_attribute(packed) || COMPATIBLE_WITH_GCC(3) || \
COMPATIBLE_WITH_CLANG(3)
# define PACKED __attribute__((packed))
#elif defined(_WIN32)
# define PACKED
# pragma pack(push)
# pragma pack(1)
#else
# define PACKED
#endif
/* TODO(b/158720996): 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[];
} PACKED OEMCrypto_PST_Report;
#if defined(_WIN32)
# pragma pack(pop)
#endif
static void ClearTable(void) {
g_usage_table.table_size = 0;
memset(&g_usage_table.generation_numbers, 0,
sizeof(g_usage_table.generation_numbers));
for (size_t i = 0; i < MAX_NUMBER_OF_USAGE_ENTRIES; i++) {
OPKI_FreeFromObjectTable(&usage_entry_table, g_usage_table.entries[i]);
g_usage_table.entries[i] = NULL;
}
}
static UsageEntry* FindUsageEntry(OEMCrypto_SESSION session_id) {
for (size_t i = 0; i < MAX_NUMBER_OF_USAGE_ENTRIES; i++) {
if (g_usage_table.entries[i] &&
g_usage_table.entries[i]->session_id == session_id) {
return g_usage_table.entries[i];
}
}
return NULL;
}
static OEMCryptoResult InitHeader(SavedUsageHeader* header,
const UsageTable* usage_table) {
if (!header || !usage_table) return OEMCrypto_ERROR_INVALID_CONTEXT;
*header = (SavedUsageHeader){
.common_info =
{
.file_type = USAGE_TABLE_HEADER,
.format_version = CURRENT_FILE_FORMAT_VERSION,
},
.master_generation_number = usage_table->master_generation_number,
.table_size = usage_table->table_size,
};
memcpy(header->generation_numbers, usage_table->generation_numbers,
header->table_size * sizeof(uint64_t));
return OEMCrypto_SUCCESS;
}
static OEMCryptoResult InitSignedHeader(SignedSavedUsageHeader* signed_header) {
if (!signed_header) return OEMCrypto_ERROR_INVALID_CONTEXT;
*signed_header = (SignedSavedUsageHeader){
.common_info =
{
.file_type = SIGNED_USAGE_TABLE_HEADER,
.format_version = CURRENT_FILE_FORMAT_VERSION,
},
.buffer_size = sizeof(signed_header->buffer),
.buffer = {0},
};
return OEMCrypto_SUCCESS;
}
static OEMCryptoResult WrapHeader(SavedUsageHeader* header, uint8_t* out_buffer,
size_t out_buffer_length) {
if (!header) return OEMCrypto_ERROR_INVALID_CONTEXT;
uint8_t temp_buffer[PADDED_HEADER_BUFFER_SIZE] = {0};
OEMCryptoResult result =
OPKI_PackUsageHeader(temp_buffer, sizeof(temp_buffer), header);
if (result != OEMCrypto_SUCCESS) return result;
SignedSavedUsageHeader signed_header;
result = InitSignedHeader(&signed_header);
if (result != OEMCrypto_SUCCESS) return result;
result = WTPI_EncryptAndSign(DEVICE_KEY_WRAP_USAGE_TABLE, temp_buffer,
sizeof(temp_buffer), signed_header.buffer,
&signed_header.buffer_size);
if (result != OEMCrypto_SUCCESS) return result;
result =
OPKI_PackSignedUsageHeader(out_buffer, out_buffer_length, &signed_header);
return result;
}
/* This serializes, encrypts, and signs the current header into the given
* buffer. It is an error if the buffer is not big enough.
* Copies the generation numbers from the supplied usage_table parameter into
* the serialized header fields.
*/
NO_IGNORE_RESULT static OEMCryptoResult EncryptAndSignHeader(
uint8_t* header_buffer, size_t header_buffer_length,
const UsageTable* usage_table) {
SavedUsageHeader header;
OEMCryptoResult result = InitHeader(&header, usage_table);
if (result != OEMCrypto_SUCCESS) return result;
result = WrapHeader(&header, header_buffer, header_buffer_length);
return result;
}
/* This decrypts and deserializes usage table header from the given buffer in
* the legacy format. 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. */
NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyHeader_Legacy(
const uint8_t* header_buffer, size_t header_buffer_length,
SavedUsageHeader* header) {
if (!header_buffer || !header) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (header_buffer_length < sizeof(SignedSavedUsageHeaderLegacy)) {
return OEMCrypto_ERROR_SHORT_BUFFER;
}
SignedSavedUsageHeaderLegacy signed_header;
OEMCryptoResult result = OPKI_UnpackSignedUsageHeaderLegacy(
header_buffer, header_buffer_length, &signed_header);
if (result != OEMCrypto_SUCCESS) return result;
if (signed_header.common_info.file_type != SIGNED_USAGE_TABLE_HEADER) {
/* We were given the wrong file. */
LOGE("Unknown signed usage header type: %u",
signed_header.common_info.file_type);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (signed_header.common_info.format_version != LEGACY_FILE_FORMAT_VERSION) {
LOGE("Bad signed usage header format version: %u. Expected version: %u",
signed_header.common_info.format_version, LEGACY_FILE_FORMAT_VERSION);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (signed_header.buffer_size != LEGACY_PADDED_HEADER_BUFFER_SIZE) {
LOGE("Invalid signed usage header buffer size: %u",
signed_header.buffer_size);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
uint8_t temp_buffer[LEGACY_PADDED_HEADER_BUFFER_SIZE];
size_t temp_buffer_size = sizeof(temp_buffer);
result = WTPI_VerifyAndDecryptUsageData_Legacy(
signed_header.buffer, signed_header.buffer_size, signed_header.signature,
signed_header.iv, temp_buffer);
if (result != OEMCrypto_SUCCESS) return result;
result = OPKI_UnpackUsageHeader(temp_buffer, temp_buffer_size, header);
if (result != OEMCrypto_SUCCESS) return result;
if (header->common_info.file_type != USAGE_TABLE_HEADER) {
/* We were given the wrong file. */
LOGE("Unknown usage header type: %u", header->common_info.file_type);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (header->common_info.format_version != LEGACY_FILE_FORMAT_VERSION) {
LOGE("Bad usage header format version: %u",
header->common_info.format_version);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (header->table_size > MAX_NUMBER_OF_USAGE_ENTRIES) {
LOGE("Too many usage entries: %u", header->table_size);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
/* 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. */
NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyHeader(
const uint8_t* header_buffer, size_t header_buffer_length,
SavedUsageHeader* header) {
if (!header_buffer || !header) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (header_buffer_length < sizeof(SavedCommonInfo)) {
LOGE("Invalid header buffer size: %zu", header_buffer_length);
return OEMCrypto_ERROR_SHORT_BUFFER;
}
const uint8_t* buffer = header_buffer;
size_t buffer_length = header_buffer_length;
SavedCommonInfo common_info;
OEMCryptoResult result = OEMCrypto_ERROR_UNKNOWN_FAILURE;
#ifdef USE_REF_BACK_COMPAT
// To deal with reference-wrapped v16 usage tables, extract the info and
// rewrap. Then continue as usual with the rewrapped blob.
// Reference usage table doesn't have any kind of common_info struct at the
// top. Previously, fail immediatley if the common_info doesn't match
// expectations. Now, check if none of the common_info data makes sense, then
// try v16.
result = OPKI_UnpackSavedCommonInfo(buffer, buffer_length, &common_info);
if (result != OEMCrypto_SUCCESS) return result;
bool should_try_ref_compat =
(common_info.file_type != SIGNED_USAGE_TABLE_HEADER &&
common_info.file_type != SIGNED_USAGE_TABLE_ENTRY &&
common_info.file_type != USAGE_TABLE_HEADER &&
common_info.file_type != USAGE_TABLE_ENTRY);
SavedUsageHeader temp_header = {0};
uint8_t rewrapped_header[sizeof(SignedSavedUsageHeader)] = {0};
size_t rewrapped_header_size = sizeof(rewrapped_header);
if (should_try_ref_compat) {
// Unwrap into current OPK struct
result =
WTPI_RefCompat_UnwrapUsageHeader(buffer, buffer_length, &temp_header);
if (result != OEMCrypto_SUCCESS) return result;
// Rewrap struct with current wrapping scheme
result = WrapHeader(&temp_header, rewrapped_header, rewrapped_header_size);
if (result != OEMCrypto_SUCCESS) return result;
// Point variables to use new rewrapped data, start the process again
buffer = rewrapped_header;
buffer_length = rewrapped_header_size;
}
#endif
result = OPKI_UnpackSavedCommonInfo(buffer, buffer_length, &common_info);
if (result != OEMCrypto_SUCCESS) return result;
if (common_info.file_type != SIGNED_USAGE_TABLE_HEADER) {
/* We were given the wrong file. */
LOGE("Unknown signed usage header type: %u", common_info.file_type);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (common_info.format_version == LEGACY_FILE_FORMAT_VERSION) {
LOGD("Legacy usage header format version %u detected.",
LEGACY_FILE_FORMAT_VERSION);
return DecryptAndVerifyHeader_Legacy(buffer, buffer_length, header);
}
if (common_info.format_version != CURRENT_FILE_FORMAT_VERSION) {
LOGE("Bad signed usage header format version: %u",
common_info.format_version);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
SignedSavedUsageHeader signed_header;
result = OPKI_UnpackSignedUsageHeader(buffer, buffer_length, &signed_header);
if (result != OEMCrypto_SUCCESS) return result;
if (signed_header.buffer_size > sizeof(signed_header.buffer)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
uint8_t temp_buffer[PADDED_HEADER_BUFFER_SIZE];
size_t temp_buffer_size = sizeof(temp_buffer);
result = WTPI_VerifyAndDecrypt(
DEVICE_KEY_WRAP_USAGE_TABLE, signed_header.buffer,
signed_header.buffer_size, temp_buffer, &temp_buffer_size);
if (result != OEMCrypto_SUCCESS) return result;
result = OPKI_UnpackUsageHeader(temp_buffer, temp_buffer_size, header);
if (result != OEMCrypto_SUCCESS) return result;
if (header->common_info.file_type != USAGE_TABLE_HEADER) {
/* We were given the wrong file. */
LOGE("Unknown usage header type: %u", header->common_info.file_type);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (header->common_info.format_version != CURRENT_FILE_FORMAT_VERSION) {
/* In the future, we might handle backwards compatible versions. */
LOGE("Bad usage header format version: %u",
header->common_info.format_version);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (header->table_size > MAX_NUMBER_OF_USAGE_ENTRIES) {
LOGE("Too many usage entries: %u", header->table_size);
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.
*/
NO_IGNORE_RESULT static OEMCryptoResult EncryptAndSignEntry(
SavedUsageEntry* entry, uint8_t* entry_buffer, size_t entry_buffer_length) {
entry->common_info.file_type = USAGE_TABLE_ENTRY;
entry->common_info.format_version = CURRENT_FILE_FORMAT_VERSION;
SignedSavedUsageEntry signed_entry = {
.common_info =
{
.file_type = SIGNED_USAGE_TABLE_ENTRY,
.format_version = CURRENT_FILE_FORMAT_VERSION,
},
.buffer_size = sizeof(signed_entry.buffer),
.buffer = {0},
};
uint8_t temp_buffer[PADDED_ENTRY_BUFFER_SIZE] = {0};
OEMCryptoResult result =
OPKI_PackUsageEntry(temp_buffer, sizeof(temp_buffer), entry);
if (result != OEMCrypto_SUCCESS) return result;
result = WTPI_EncryptAndSign(DEVICE_KEY_WRAP_USAGE_TABLE, temp_buffer,
sizeof(temp_buffer), signed_entry.buffer,
&signed_entry.buffer_size);
if (result != OEMCrypto_SUCCESS) return result;
result = OPKI_PackSignedUsageEntry(entry_buffer, entry_buffer_length,
&signed_entry);
return result;
}
/* This decrypts and deserializes a usage table entry from the given buffer
* in the legacy format. 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. */
NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyEntry_Legacy(
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 < sizeof(SignedSavedUsageEntryLegacy)) {
return OEMCrypto_ERROR_SHORT_BUFFER;
}
SignedSavedUsageEntryLegacy signed_entry;
OEMCryptoResult result = OPKI_UnpackSignedUsageEntryLegacy(
entry_buffer, entry_buffer_length, &signed_entry);
if (result != OEMCrypto_SUCCESS) return result;
if (signed_entry.common_info.file_type != SIGNED_USAGE_TABLE_ENTRY) {
/* We were given the wrong file. */
LOGE("Unknown signed entry type: %u", signed_entry.common_info.file_type);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (signed_entry.common_info.format_version != LEGACY_FILE_FORMAT_VERSION) {
LOGE("Bad signed entry format version: %u. Expected version: %u",
signed_entry.common_info.format_version, LEGACY_FILE_FORMAT_VERSION);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (signed_entry.buffer_size > sizeof(signed_entry.buffer)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
uint8_t temp_buffer[LEGACY_PADDED_ENTRY_BUFFER_SIZE];
size_t temp_buffer_size = sizeof(temp_buffer);
result = WTPI_VerifyAndDecryptUsageData_Legacy(
signed_entry.buffer, signed_entry.buffer_size, signed_entry.signature,
signed_entry.iv, temp_buffer);
if (result != OEMCrypto_SUCCESS) return result;
result = OPKI_UnpackUsageEntry(temp_buffer, temp_buffer_size, entry);
if (result != OEMCrypto_SUCCESS) return result;
if (entry->common_info.file_type != USAGE_TABLE_ENTRY) {
/* We were given the wrong file. */
LOGE("Bad entry type: %u", entry->common_info.file_type);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (entry->common_info.format_version != LEGACY_FILE_FORMAT_VERSION) {
LOGE("Bad entry format version: %u", entry->common_info.format_version);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
/* 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. */
NO_IGNORE_RESULT 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 < sizeof(SavedCommonInfo)) {
LOGE("Invalid entry buffer size: %zu", entry_buffer_length);
return OEMCrypto_ERROR_SHORT_BUFFER;
}
const uint8_t* buffer = entry_buffer;
size_t buffer_length = entry_buffer_length;
SavedCommonInfo common_info;
OEMCryptoResult result = OEMCrypto_ERROR_UNKNOWN_FAILURE;
#ifdef USE_REF_BACK_COMPAT
// To deal with reference-wrapped v16 usage tables, extract the info and
// rewrap. Then continue as usual with the rewrapped blob.
// Reference usage table doesn't have any kind of common_info struct at the
// top. Previously, fail immediatley if the common_info doesn't match
// expectations. Now, check if none of the common_info data makes sense, then
// try v16.
result = OPKI_UnpackSavedCommonInfo(buffer, buffer_length, &common_info);
if (result != OEMCrypto_SUCCESS) return result;
bool should_try_ref_compat =
(common_info.file_type != SIGNED_USAGE_TABLE_HEADER &&
common_info.file_type != SIGNED_USAGE_TABLE_ENTRY &&
common_info.file_type != USAGE_TABLE_HEADER &&
common_info.file_type != USAGE_TABLE_ENTRY);
SavedUsageEntry temp_entry = {0};
uint8_t rewrapped_entry[sizeof(SignedSavedUsageEntry)] = {0};
size_t rewrapped_entry_size = sizeof(rewrapped_entry);
if (should_try_ref_compat) {
// Unwrap into current OPK struct
result =
WTPI_RefCompat_UnwrapUsageEntry(buffer, buffer_length, &temp_entry);
if (result != OEMCrypto_SUCCESS) return result;
// Rewrap struct with current wrapping scheme
result =
EncryptAndSignEntry(&temp_entry, rewrapped_entry, rewrapped_entry_size);
if (result != OEMCrypto_SUCCESS) return result;
// Point variables to use new rewrapped data, start the process again
buffer = rewrapped_entry;
buffer_length = rewrapped_entry_size;
}
#endif
result = OPKI_UnpackSavedCommonInfo(buffer, buffer_length, &common_info);
if (result != OEMCrypto_SUCCESS) return result;
if (common_info.file_type != SIGNED_USAGE_TABLE_ENTRY) {
/* We were given the wrong file. */
LOGE("Unknown signed entry type: %u", common_info.file_type);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (common_info.format_version == LEGACY_FILE_FORMAT_VERSION) {
LOGD("Legacy usage entry format version %u detected.",
LEGACY_FILE_FORMAT_VERSION);
return DecryptAndVerifyEntry_Legacy(buffer, buffer_length, entry);
}
if (common_info.format_version != CURRENT_FILE_FORMAT_VERSION) {
LOGE("Bad signed entry format version: %u", common_info.format_version);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (buffer_length < OPKI_SignedEntrySize()) {
return OEMCrypto_ERROR_SHORT_BUFFER;
}
SignedSavedUsageEntry signed_entry;
result =
OPKI_UnpackSignedUsageEntry(buffer, buffer_length, &signed_entry);
if (result != OEMCrypto_SUCCESS) return result;
if (signed_entry.buffer_size > sizeof(signed_entry.buffer)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
uint8_t temp_buffer[PADDED_ENTRY_BUFFER_SIZE];
size_t temp_buffer_size = sizeof(temp_buffer);
result = WTPI_VerifyAndDecrypt(DEVICE_KEY_WRAP_USAGE_TABLE,
signed_entry.buffer, signed_entry.buffer_size,
temp_buffer, &temp_buffer_size);
if (result != OEMCrypto_SUCCESS) return result;
result = OPKI_UnpackUsageEntry(temp_buffer, temp_buffer_size, entry);
if (result != OEMCrypto_SUCCESS) return result;
if (entry->common_info.file_type != USAGE_TABLE_ENTRY) {
/* We were given the wrong file. */
LOGE("Bad entry type: %u", entry->common_info.file_type);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (entry->common_info.format_version != CURRENT_FILE_FORMAT_VERSION) {
/* In the future, we might handle backwards compatible versions. */
LOGE("Bad entry format version: %u", entry->common_info.format_version);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
NO_IGNORE_RESULT static OEMCryptoResult RollGenerationNumber(
UsageEntry* 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 (WTPI_SaveGenerationNumber(new_generation_number) != OEMCrypto_SUCCESS) {
LOGE("Failed to save generation number: %" PRIu64, 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;
g_counter_info.master_generation_number =
g_usage_table.master_generation_number;
entry->data.generation_number++;
g_usage_table.generation_numbers[entry->data.index] =
entry->data.generation_number;
return OEMCrypto_SUCCESS;
}
NO_IGNORE_RESULT static OEMCryptoResult UpdateClockValues(
UsageEntry* entry, ODK_ClockValues* clock_values) {
if (!entry || entry->data.index >= MAX_NUMBER_OF_USAGE_ENTRIES ||
!clock_values) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (entry->recent_decrypt) {
uint64_t now;
OEMCryptoResult result = WTPI_GetTrustedTime(&now);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to get trusted time with result: %u", result);
return result;
}
entry->data.time_of_last_decrypt = now;
entry->recent_decrypt = false;
}
entry->data.time_of_license_received =
clock_values->time_of_license_request_signed;
entry->data.time_of_first_decrypt = clock_values->time_of_first_decrypt;
/* Use the most recent time_of_last_decrypt. */
if ((uint64_t)(entry->data.time_of_last_decrypt) <
clock_values->time_of_last_decrypt) {
entry->data.time_of_last_decrypt = clock_values->time_of_last_decrypt;
} else {
clock_values->time_of_last_decrypt = entry->data.time_of_last_decrypt;
}
entry->data.status = clock_values->status;
return OEMCrypto_SUCCESS;
}
// Clear out memory for the usage table.
OEMCryptoResult OPKI_InitializeUsageTable(void) {
if (WTPI_PrepareGenerationNumber() != OEMCrypto_SUCCESS) {
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 OPKI_TerminateUsageTable(void) {
g_usage_table_state = USAGE_TABLE_NOT_INITIALIZED;
return OEMCrypto_SUCCESS;
}
static NO_IGNORE_RESULT UsageEntryStatus
GetUsageEntryStatusFromEntry(UsageEntry* entry) {
if (entry->data.status == kInactive || entry->data.status == kInactiveUsed ||
entry->data.status == kInactiveUnused) {
return USAGE_ENTRY_DEACTIVATED;
}
if (entry->is_loaded) {
return USAGE_ENTRY_LOADED;
}
return USAGE_ENTRY_NEW;
}
UsageEntryStatus OPKI_GetUsageEntryStatus(OEMCrypto_SESSION session_id) {
UsageEntry* entry = FindUsageEntry(session_id);
if (!entry) return USAGE_ENTRY_NONE;
return GetUsageEntryStatusFromEntry(entry);
}
/* Create a new usage table. */
OEMCryptoResult OPKI_CreateUsageTableHeader(uint8_t* header_buffer,
size_t* header_buffer_length) {
if (!header_buffer_length) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
size_t size = OPKI_SignedHeaderSize(0);
if (*header_buffer_length < size) {
*header_buffer_length = size;
return OEMCrypto_ERROR_SHORT_BUFFER;
}
*header_buffer_length = size;
if (!header_buffer) return OEMCrypto_ERROR_INVALID_CONTEXT;
/* Make sure there are no entries that are currently tied to an open
* session.
*/
for (size_t i = 0; i < MAX_NUMBER_OF_USAGE_ENTRIES; i++) {
if (g_usage_table.entries[i]) {
LOGE("OPKI_CreateUsageTableHeader: entry %zu used by session %u.", i,
g_usage_table.entries[i]->session_id);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
}
/* 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();
if (g_usage_table_state == USAGE_TABLE_ERROR_STATE) {
/* Something went wrong. The system should give up and re-init. */
LOGE("Usage table is in error state");
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. However, 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 (WTPI_LoadGenerationNumber(&g_usage_table.master_generation_number) !=
OEMCrypto_SUCCESS) {
LOGE("Failed to load 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. */
LOGE("Usage table is not initialized");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
g_usage_table_state = USAGE_TABLE_ACTIVE_STATE;
g_counter_info.master_generation_number =
g_usage_table.master_generation_number;
return EncryptAndSignHeader(header_buffer, *header_buffer_length,
&g_usage_table);
}
/* Load a usage table header. */
OEMCryptoResult OPKI_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. */
LOGE("Usage table is in error state");
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 (WTPI_LoadGenerationNumber(&g_usage_table.master_generation_number) !=
OEMCrypto_SUCCESS) {
LOGE("Failed to load 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. */
LOGE("Usage table is not initialized");
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. */
NO_IGNORE_RESULT static OEMCryptoResult GrabEntry(OEMCrypto_SESSION session_id,
uint32_t usage_entry_number,
UsageEntry** entry_ptr) {
if (!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 (FindUsageEntry(session_id) != NULL) {
return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES;
}
/* Check that another session is not already using this entry. */
if (g_usage_table.entries[usage_entry_number]) {
return OEMCrypto_ERROR_INVALID_SESSION;
}
UsageEntry* entry = OPKI_AllocFromObjectTable(&usage_entry_table, NULL);
if (!entry) return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES;
/* Initialize the entry. */
memset(entry, 0, sizeof(UsageEntry));
entry->data.index = usage_entry_number;
entry->session_id = session_id;
/* Update the active entry map. */
g_usage_table.entries[usage_entry_number] = entry;
*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. */
void OPKI_ReleaseEntry(OEMCrypto_SESSION session_id) {
if (g_usage_table_state == USAGE_TABLE_ACTIVE_STATE) {
/* Remove from active entry map. */
UsageEntry* entry = FindUsageEntry(session_id);
if (entry) {
uint32_t index = entry->data.index;
OPKI_FreeFromObjectTable(&usage_entry_table, entry);
g_usage_table.entries[index] = NULL;
}
}
}
/* 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 OPKI_CreateNewUsageEntry(OEMCrypto_SESSION session_id,
uint32_t* usage_entry_number) {
if (!usage_entry_number) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
/* Usage table header must be loaded. */
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (FindUsageEntry(session_id) != NULL) {
/* Can only have one entry per session. */
return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES;
}
/* 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;
UsageEntry* entry = NULL;
OEMCryptoResult result = GrabEntry(session_id, new_index, &entry);
if (result != OEMCrypto_SUCCESS) return result;
g_usage_table.table_size++;
/* 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;
}
/* Tie a |session| to an existing usage entry. The new entry will use the
* argument |usage_entry_number| for the entry number. */
OEMCryptoResult OPKI_ReuseUsageEntry(OEMCrypto_SESSION session_id,
uint32_t usage_entry_number) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
/* Usage table header must be loaded. */
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (FindUsageEntry(session_id) != NULL) {
/* Can only have one entry per session. */
return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES;
}
/* Check that the |usage_entry_number| is valid. */
if (usage_entry_number >= MAX_NUMBER_OF_USAGE_ENTRIES) {
return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES;
}
if (usage_entry_number >= g_usage_table.table_size) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
UsageEntry* entry = NULL;
OEMCryptoResult result = GrabEntry(session_id, usage_entry_number, &entry);
if (result != OEMCrypto_SUCCESS) return result;
/* Update the generation numbers. */
entry->data.generation_number = g_usage_table.master_generation_number;
g_usage_table.generation_numbers[usage_entry_number] =
g_usage_table.master_generation_number;
g_usage_table.master_generation_number++;
return OEMCrypto_SUCCESS;
}
/* Load a usage entry that had been saved to the file system and tie it to
* |session|. */
OEMCryptoResult OPKI_LoadUsageEntry(OEMCrypto_SESSION session_id,
ODK_ClockValues* clock_values,
uint32_t usage_entry_number,
const uint8_t* buffer,
size_t buffer_length) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
/* Usage table header must be loaded. */
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (FindUsageEntry(session_id) != NULL) {
/* Can only load one entry per session. */
return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES;
}
if (usage_entry_number >= MAX_NUMBER_OF_USAGE_ENTRIES) {
LOGE("Too many usage entries: %u/%u", usage_entry_number,
(unsigned int)MAX_NUMBER_OF_USAGE_ENTRIES);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (usage_entry_number >= g_usage_table.table_size) {
LOGE("Invalid usage entry number: %u", usage_entry_number);
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
UsageEntry* entry = NULL;
OEMCryptoResult result = GrabEntry(session_id, usage_entry_number, &entry);
if (result != OEMCrypto_SUCCESS) return result;
/* Load the entry. */
result = DecryptAndVerifyEntry(buffer, buffer_length, &entry->data);
if (result != OEMCrypto_SUCCESS) {
OPKI_ReleaseEntry(session_id);
return result;
}
if (entry->data.index != usage_entry_number) {
OPKI_ReleaseEntry(session_id);
return OEMCrypto_ERROR_INVALID_SESSION;
}
uint64_t now;
result = WTPI_GetTrustedTime(&now);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to get trusted time with result: %u", result);
OPKI_ReleaseEntry(session_id);
return result;
}
result = ODK_ReloadClockValues(
clock_values, (uint64_t)(entry->data.time_of_license_received),
(uint64_t)(entry->data.time_of_first_decrypt),
(uint64_t)(entry->data.time_of_last_decrypt), entry->data.status,
(uint64_t)now);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to reload clock values with result: %u", result);
OPKI_ReleaseEntry(session_id);
return result;
}
/* 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.
*/
OPKI_ReleaseEntry(session_id);
return OEMCrypto_ERROR_GENERATION_SKEW;
}
entry->is_loaded = true;
return result;
}
OEMCryptoResult OPKI_UpdateUsageEntry(OEMCrypto_SESSION session_id,
ODK_ClockValues* clock_values,
uint8_t* header_buffer,
size_t* header_buffer_length,
uint8_t* entry_buffer,
size_t* entry_buffer_length) {
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 = OPKI_SignedHeaderSize(g_usage_table.table_size);
size_t entry_size = OPKI_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. */
UsageEntry* entry = FindUsageEntry(session_id);
if (!header_buffer || !entry_buffer || !entry) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
/* New clock values in the usage entry. */
OEMCryptoResult result = UpdateClockValues(entry, clock_values);
if (result != OEMCrypto_SUCCESS) return result;
/* The new generation numbers. */
result = RollGenerationNumber(entry);
if (result != OEMCrypto_SUCCESS) return result;
result = EncryptAndSignHeader(header_buffer, *header_buffer_length,
&g_usage_table);
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 OPKI_ForbidReportUsage(OEMCrypto_SESSION session_id) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
UsageEntry* entry = FindUsageEntry(session_id);
if (!entry) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
const OEMCryptoResult result = RollGenerationNumber(entry);
if (result == OEMCrypto_SUCCESS) entry->forbid_report = true;
return result;
}
OEMCryptoResult OPKI_SetUsageEntryPST(OEMCrypto_SESSION session_id,
const uint8_t* pst, size_t pst_length) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
UsageEntry* entry = FindUsageEntry(session_id);
if (!entry || GetUsageEntryStatusFromEntry(entry) != USAGE_ENTRY_NEW) {
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;
OEMCryptoResult result = WTPI_GetTrustedTime(&now);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to get trusted time with result: %u", result);
return result;
}
entry->data.time_of_license_received = now;
return OEMCrypto_SUCCESS;
}
OEMCryptoResult OPKI_VerfiyUsageEntryPST(OEMCrypto_SESSION session_id,
const uint8_t* pst,
size_t pst_length) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
UsageEntry* entry = FindUsageEntry(session_id);
if (!entry || GetUsageEntryStatusFromEntry(entry) != USAGE_ENTRY_LOADED) {
LOGE("Invalid usage entry status");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (!pst || pst_length == 0 || pst_length > MAX_PST_LENGTH) {
LOGE("Invalid PST");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (entry->data.pst_length != pst_length ||
crypto_memcmp(entry->data.pst, pst, pst_length) != 0) {
return OEMCrypto_ERROR_WRONG_PST;
}
return OEMCrypto_SUCCESS;
}
OEMCryptoResult OPKI_SetUsageEntryMacKeys(
OEMCrypto_SESSION session_id, WTPI_K1_SymmetricKey_Handle mac_key_server,
WTPI_K1_SymmetricKey_Handle mac_key_client) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
UsageEntry* entry = FindUsageEntry(session_id);
if (!entry || GetUsageEntryStatusFromEntry(entry) != USAGE_ENTRY_NEW) {
LOGE("Invalid usage entry status");
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 =
WTPI_K1_WrapKey(DEVICE_KEY_WRAP_MAC_KEY, mac_key_server, MAC_KEY_SERVER,
wrapped_server_mac, WRAPPED_MAC_KEY_SIZE);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to wrap mac key server");
return result;
}
result =
WTPI_K1_WrapKey(DEVICE_KEY_WRAP_MAC_KEY, mac_key_client, MAC_KEY_CLIENT,
wrapped_client_mac, WRAPPED_MAC_KEY_SIZE);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to wrap mac key client");
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 OPKI_VerifyUsageEntryMacKeys(
OEMCrypto_SESSION session_id, WTPI_K1_SymmetricKey_Handle mac_key_server,
WTPI_K1_SymmetricKey_Handle mac_key_client) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
UsageEntry* entry = FindUsageEntry(session_id);
if (!entry || GetUsageEntryStatusFromEntry(entry) != USAGE_ENTRY_LOADED) {
LOGE("Invalid usage entry status");
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 =
WTPI_K1_WrapKey(DEVICE_KEY_WRAP_MAC_KEY, mac_key_server, MAC_KEY_SERVER,
wrapped_server_mac, WRAPPED_MAC_KEY_SIZE);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to wrap mac key server");
return result;
}
result =
WTPI_K1_WrapKey(DEVICE_KEY_WRAP_MAC_KEY, mac_key_client, MAC_KEY_CLIENT,
wrapped_client_mac, WRAPPED_MAC_KEY_SIZE);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to wrap mac key client");
return result;
}
if (crypto_memcmp(entry->data.mac_key_server, wrapped_server_mac,
WRAPPED_MAC_KEY_SIZE) != 0 ||
crypto_memcmp(entry->data.mac_key_client, wrapped_client_mac,
WRAPPED_MAC_KEY_SIZE) != 0) {
return OEMCrypto_ERROR_WRONG_PST;
}
return OEMCrypto_SUCCESS;
}
OEMCryptoResult OPKI_SetUsageEntryRecentDecrypt(OEMCrypto_SESSION session_id) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
UsageEntry* entry = FindUsageEntry(session_id);
if (!entry ||
GetUsageEntryStatusFromEntry(entry) == USAGE_ENTRY_DEACTIVATED) {
LOGE("Invalid usage entry status");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
entry->recent_decrypt = true;
return OEMCrypto_SUCCESS;
}
OEMCryptoResult OPKI_DeactivateUsageEntry(OEMCrypto_SESSION session_id,
ODK_ClockValues* clock_values,
const uint8_t* pst UNUSED,
size_t pst_length UNUSED) {
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. */
UsageEntry* entry = FindUsageEntry(session_id);
if (!entry) {
LOGE("Failed to deactivate usage entry. Invalid entry status.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
OEMCryptoResult result = OPKI_ForbidReportUsage(session_id);
if (result != OEMCrypto_SUCCESS) return result;
result = ODK_DeactivateUsageEntry(clock_values);
return result;
}
OEMCryptoResult OPKI_ReportUsage(OEMCrypto_SESSION session_id,
WTPI_K1_SymmetricKey_Handle mac_key_client,
const uint8_t* pst, size_t pst_length,
uint8_t* buffer, size_t* buffer_length) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
RETURN_INVALID_CONTEXT_IF_NULL(buffer_length);
UsageEntry* entry = FindUsageEntry(session_id);
if (!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;
// We delay checking these to allow the above length-returning code to
// work without passing in these parameters.
RETURN_INVALID_CONTEXT_IF_NULL(pst);
RETURN_INVALID_CONTEXT_IF_NULL(buffer);
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 (crypto_memcmp(entry->data.pst, pst, pst_length) != 0) {
return OEMCrypto_ERROR_WRONG_PST;
}
uint64_t now;
OEMCryptoResult result = WTPI_GetTrustedTime(&now);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to get trusted time with result: %u", result);
return result;
}
OEMCrypto_PST_Report* report = (OEMCrypto_PST_Report*)buffer;
report->seconds_since_license_received =
oemcrypto_htobe64(now - entry->data.time_of_license_received);
report->seconds_since_first_decrypt =
oemcrypto_htobe64(now - entry->data.time_of_first_decrypt);
report->seconds_since_last_decrypt =
oemcrypto_htobe64(now - entry->data.time_of_last_decrypt);
report->status = entry->data.status;
report->clock_security_level = WTPI_GetClockType();
report->pst_length = pst_length;
memcpy(report->pst, pst, pst_length);
if (mac_key_client) {
/* If the session has mac keys, use those. */
result =
WTPI_C1_HMAC_SHA1(mac_key_client, buffer + SHA_DIGEST_LENGTH,
length_needed - SHA_DIGEST_LENGTH, report->signature);
if (OEMCrypto_SUCCESS != result) {
LOGE(
"Failed to compute HMAC-SHA1 signature with mac key client from "
"session, error: %u",
result);
}
} else {
/* Otherwise, we use the mac key from the entry. */
WTPI_K1_SymmetricKey_Handle key = NULL;
result = WTPI_K1_UnwrapIntoKeyHandle(
DEVICE_KEY_WRAP_MAC_KEY, entry->data.mac_key_client,
WRAPPED_MAC_KEY_SIZE, MAC_KEY_CLIENT, &key);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to unwrap mac key client with result: %u", result);
return result;
}
result =
WTPI_C1_HMAC_SHA1(key, buffer + SHA_DIGEST_LENGTH,
length_needed - SHA_DIGEST_LENGTH, report->signature);
if (OEMCrypto_SUCCESS != result) {
LOGE(
"Failed to compute HMAC-SHA1 signature with mac key client from "
"entry, error: %u",
result);
}
if (OEMCrypto_SUCCESS != WTPI_K1_FreeKeyHandle(key)) {
if (OEMCrypto_SUCCESS != result) return result;
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
}
return result;
}
OEMCryptoResult OPKI_SignReleaseRequest(OEMCrypto_SESSION session_id,
const uint8_t* message,
size_t message_length,
uint8_t* signature,
size_t* signature_length UNUSED) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
UsageEntry* entry = FindUsageEntry(session_id);
if (!entry) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
WTPI_K1_SymmetricKey_Handle key = NULL;
OEMCryptoResult result = WTPI_K1_UnwrapIntoKeyHandle(
DEVICE_KEY_WRAP_MAC_KEY, entry->data.mac_key_client, WRAPPED_MAC_KEY_SIZE,
MAC_KEY_CLIENT, &key);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to unwrap mac key client with result: %u", result);
return result;
}
result = WTPI_C1_HMAC_SHA256(key, message, message_length, signature);
if (OEMCrypto_SUCCESS != result) {
LOGE("Failed to compute HMAC-SHA256 signature with result: %u", result);
}
if (OEMCrypto_SUCCESS != WTPI_K1_FreeKeyHandle(key)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return result;
}
OEMCryptoResult OPKI_MoveEntry(OEMCrypto_SESSION session_id,
uint32_t new_index) {
if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (new_index >= MAX_NUMBER_OF_USAGE_ENTRIES) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
UsageEntry* entry = FindUsageEntry(session_id);
if (!entry) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
uint32_t index = entry->data.index;
if (g_usage_table.entries[new_index]) {
return OEMCrypto_ERROR_ENTRY_IN_USE;
}
/* Copy data from index to new_index. */
entry->data.index = new_index;
g_usage_table.entries[new_index] = g_usage_table.entries[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.entries[index] = NULL;
return OEMCrypto_SUCCESS;
}
OEMCryptoResult OPKI_ShrinkUsageTableHeader(uint32_t new_entry_count,
uint8_t* header_buffer,
size_t* header_buffer_length) {
RETURN_INVALID_CONTEXT_IF_NULL(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 (size_t i = new_entry_count; i < g_usage_table.table_size; i++) {
if (g_usage_table.entries[i]) {
return OEMCrypto_ERROR_ENTRY_IN_USE;
}
}
size_t header_size = OPKI_SignedHeaderSize(new_entry_count);
if (header_size > *header_buffer_length) {
*header_buffer_length = header_size;
return OEMCrypto_ERROR_SHORT_BUFFER;
}
*header_buffer_length = header_size;
RETURN_INVALID_CONTEXT_IF_NULL(header_buffer);
for (size_t i = new_entry_count; i < g_usage_table.table_size; i++) {
g_usage_table.generation_numbers[i] = 0;
}
g_usage_table.table_size = new_entry_count;
return EncryptAndSignHeader(header_buffer, *header_buffer_length,
&g_usage_table);
}