1342 lines
54 KiB
C
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);
|
|
}
|