file/log_file_encrypted/main.c

/*******************************************************************************
* # License
* Copyright 2019 Silicon Laboratories Inc. www.silabs.com
*******************************************************************************
*
* The licensor of this software is Silicon Laboratories Inc. Your use of this
* software is governed by the terms of Silicon Labs Master Software License
* Agreement (MSLA) available at
* www.silabs.com/about-us/legal/master-software-license-agreement. This
* software is distributed to you in Source Code format and is governed by the
* sections of the MSLA applicable to Source Code.
*
******************************************************************************/
/*
* Documentation for this app is available online.
* See https://docs.silabs.com/gecko-os/4/standard/latest/sdk/examples/file/log-file-encrypted
*/
#include "gos.h"
#include "crypto.h"
#define BUTTON_DEBOUNCE_TIME_MS 50
#define BUTTON_CLICK_TIME_MS 500
#define BUTTON_PRESS_TIME_MS 1000
#define MAX_LOG_FILE_SIZE (1024 * 5)
#define LOG_FILENAME "test.encrypted.log"
#define LOG_PERIOD 50
#define LOG_COUNT 10
#define APPLICATION_START_LINE "'log_file' demo running ..."
// Random 128 bit hex string used as the encryption key.
// This is typically generated at manufacturing time and stored in a database.
// Ideally the encryption key is unique per device.
#define SECURITY_KEY "f12ca2a9dae8bdb48d959e65ec54f6da"
// An individual log record will contain the following info ...
typedef struct
{
uint32_t log_id;
uint32_t gpio_mask;
} log_data_t;
typedef struct
{
gos_aes128_iv_t iv; // MUST come first
uint8_t mac[AES128_BLOCK_SIZE]; // MUST come before data
uint8_t data[sizeof(log_data_t)];
} log_record_t;
static void log_event_logfile_handler(void *arg);
static void print_log_records(void);
static void partial_read_test(uint32_t log_to_read);
static gos_handle_t logfile_handle;
static uint32_t log_id;
static uint32_t end_id;
static uint8_t security_key[AES128_BLOCK_SIZE];
/*************************************************************************************************/
void gos_app_init(void)
{
uint8_t key[sizeof(SECURITY_KEY)];
gos_result_t result;
strcpy(file_info.name, LOG_FILENAME); // The name of the log file
file_info.size = MAX_LOG_FILE_SIZE; // The maximum size of the log file
logfile_handle = GOS_NULL_HANDLE;
GOS_LOG(APPLICATION_START_LINE);
if(GOS_FAILED(result, initialize_hardware()))
{
GOS_LOG("Failed to initialize hardware");
return;
}
// Convert hex key string to binary
strcpy((char*)key, SECURITY_KEY);
str_hex_to_binary((char*)key);
memcpy(security_key, key, AES128_BLOCK_SIZE);
// Create (or open) the log file
if (GOS_FAILED(result, gos_log_file_open(&file_info, &logfile_handle, false)))
{
GOS_LOG("Failed to create/open log file: %d", result);
return;
}
// Read stats of existing log file
gos_log_file_get_stats(logfile_handle, &stats);
log_id = stats.record_count; // ID of the last log already in the file
end_id = log_id + LOG_COUNT; // ID of the last log that will be appended to the file
// Display stats for existing logs (if any)
GOS_LOG("Log file statistics:");
GOS_LOG(" name: %s", file_info.name);
GOS_LOG(" record count: %d", stats.record_count);
GOS_LOG(" bytes used: %d", stats.bytes_used);
GOS_LOG(" bytes remaining: %d", stats.bytes_remaining);
GOS_LOG(" ");
print_log_records(); // Print all records in the logfile
partial_read_test(3); // Read and print log #3
// Setup a periodic event to start logging to file
GOS_LOG(" ");
GOS_LOG("Taking %d logs...", LOG_COUNT);
gos_event_register_periodic(log_event_logfile_handler, NULL, LOG_PERIOD, GOS_EVENT_FLAGS1(RUN_NOW));
}
/*************************************************************************************************/
void gos_app_deinit(void)
{
if (logfile_handle != GOS_NULL_HANDLE)
{
GOS_LOG("\r\n'log_file' demo shutting down, run the demo again to view saved logs.\r\n");
gos_file_close(logfile_handle);
}
}
/*************************************************************************************************
* Take a log sample and write it to the log file
*/
static void log_event_logfile_handler(void *arg)
{
gos_result_t result;
log_record_t record;
log_data_t *data = (log_data_t*)record.data;
// collect log data
gos_time_get_current_utc_ms(&data->time, false);
data->log_id = log_id;
data->gpio_mask = gos_gpio_mask_get(UINT32_MAX);
// encrypt record
encrypt_log(&record);
if (GOS_FAILED(result, gos_log_file_append(logfile_handle, &record, sizeof(log_record_t))))
{
GOS_LOG("Failed to append new log: %d", result);
}
else
{
GOS_LOG("Saved log: %d", log_id);
++log_id;
}
// Unregister the event to stop logging once we reach the end
if ((log_id >= end_id) || (result != GOS_SUCCESS))
{
gos_event_unregister(log_event_logfile_handler, NULL);
}
}
/*************************************************************************************************
* Sequentially read the log records and print
*/
static void print_log_records(void)
{
log_record_t record;
gos_buffer_t buffer =
{
.data = (uint8_t*)&record,
.size = sizeof(log_record_t)
};
do
{
if (gos_log_file_read(logfile_handle, &buffer, GOS_LOG_FILE_GET_NEXT) != GOS_SUCCESS)
{
break;
}
else
{
const log_data_t *data = decrypt_log(&record);
if (data != NULL)
{
gos_time_utc_ms_to_iso8601_str(data->time, 0, &time_str);
GOS_LOG("Log %d:", data->log_id);
GOS_LOG(" Time: %s", (const char*)&time_str);
GOS_LOG(" GPIO mask: 0x%08X", data->gpio_mask);
}
}
} while(1);
}
/*************************************************************************************************
* Test reading a log record in chunks
*/
static void partial_read_test(uint32_t log_to_read)
{
log_record_t record;
uint8_t *ptr = (uint8_t*)&record;
gos_buffer_t buffer;
do
{
gos_result_t result;
buffer.data = ptr;
buffer.size = 5;
if (GOS_FAILED(result, gos_log_file_read(logfile_handle, &buffer, log_to_read)))
{
if (result != GOS_NOT_FOUND)
{
GOS_LOG("Failed to read log: %d", result);
}
return;
}
ptr += buffer.size;
// When the size is 0 then there's no more log data to read
} while(buffer.size > 0);
const log_data_t *data = decrypt_log(&record);
if (data != NULL)
{
gos_time_utc_ms_to_iso8601_str(data->time, 0, &time_str);
GOS_LOG(" ");
GOS_LOG("Partial read test, Log ID: %d", data->log_id);
GOS_LOG(" Time: %s", (const char*)&time_str);
GOS_LOG(" GPIO mask: 0x%08X", data->gpio_mask);
}
}
/*************************************************************************************************/
static void encrypt_log(log_record_t *record)
{
crypt_context_t context;
// Initialize the AES context with the supplied key
gos_aes_setkey_enc(&context.aes, security_key, AES128_BLOCK_BITS);
// Generate random initialization vector (IV)
gos_get_random_buffer(record->iv.buf, sizeof(record->iv));
memcpy(context.iv.buf, record->iv.buf, sizeof(record->iv));
// Calculate the log's MAC
calculate_mac(security_key, record->data, sizeof(log_data_t), record->mac);
// Now encrypt the MAC AND log data
encrypt_buffer(&context, record->mac, sizeof(record->mac) + sizeof(log_data_t));
}
/*************************************************************************************************/
static log_data_t* decrypt_log(log_record_t *record)
{
crypt_context_t context;
log_data_t *data = (log_data_t*)record->data;
uint8_t calculated_mac[AES128_BLOCK_SIZE];
// Initialize the AES context with the supplied key
gos_aes_setkey_enc(&context.aes, security_key, AES128_BLOCK_BITS);
memcpy(context.iv.buf, record->iv.buf, sizeof(record->iv));
// Decrypt the MAC
decrypt_block(&context, record->mac);
// Decrypt the log data
decrypt_buffer(&context, (void*)data, sizeof(log_data_t));
// Calculate MAC of decrypted data
calculate_mac(security_key, record->data, sizeof(log_data_t), calculated_mac);
// Verify that the decrypted data has been properly decrypt and not modified
if (memcmp(calculated_mac, record->mac, AES128_BLOCK_SIZE) != 0)
{
GOS_LOG("Failed to decrypt log data!");
return NULL;
}
else
{
return data;
}
}
/*************************************************************************************************
* Initialize the buttons.
*/
static gos_result_t initialize_hardware(void)
{
gos_result_t result;
const gos_button_config_t config =
{
.active_level = PLATFORM_BUTTON_ACTIVE_STATE,
.debounce = BUTTON_DEBOUNCE_TIME_MS,
.click_time = BUTTON_CLICK_TIME_MS,
.press_time = BUTTON_PRESS_TIME_MS,
.event_handler.press = NULL,
.event_handler.click = NULL,
.event_handler.toggle = NULL,
.execution_context = GOS_BUTTON_CONTEXT_DEFAULT
};
GOS_LOG("Initializing button 1 ...");
if(GOS_FAILED(result, gos_button_init(PLATFORM_BUTTON1, &config, (void*)1)))
{
GOS_LOG("Failed to init PLATFORM_BUTTON1, err:%d", result);
return result;
}
GOS_LOG("Initializing button 2 ...");
if(GOS_FAILED(result, gos_button_init(PLATFORM_BUTTON2, &config, (void*)2)))
{
GOS_LOG("Failed to init PLATFORM_BUTTON2, err:%d", result);
return result;
}
return GOS_SUCCESS;
}