X-Git-Url: https://www.flypig.org.uk/git/?a=blobdiff_plain;f=src%2Fcontrac.c;h=953211410fe548f6c9b7b1fa0a77cbab5c2b5531;hb=e48c390a86b448137a87d0d0a5863c202ba66abd;hp=47070d36b14479065bde5cdb26a245a82aa46e47;hpb=8515bbba069347de07a452586d7e49a2e8bdf151;p=libcontrac.git diff --git a/src/contrac.c b/src/contrac.c index 47070d3..9532114 100644 --- a/src/contrac.c +++ b/src/contrac.c @@ -1,25 +1,38 @@ -/** \ingroup contrac +/** \ingroup KeyGeneration * @file - * @author David Llewellyn-Jones + * @author David Llewellyn-Jones * @version $(VERSION) * * @section LICENSE * + * Copyright David Llewellyn-Jones, 2020 + * Released under the GPLv2. * - * - * @brief + * @brief Core Contact Tracing functionality * @section DESCRIPTION * + * This class provides the core Contact Tracing functionality. It provides an + * interfaces for: + * 1. Generating a random Tracing Key. + * 2. Generating a Daily Tracing Key based on the current day number. + * 3. Generating a Rolling Proximity Identifier based on the current time + * interval number. * + * Values can be extracted and set in binary or base64 format. * */ +/** \addtogroup KeyGeneration + * @{ + */ + // Includes #include #include #include #include +#include #include #include @@ -33,13 +46,49 @@ // Defines +/** + * A mask used internally with the status flags. + * + * When set the flag indicates that the Tracing Key has been correctly + * initialised. + */ #define STATUS_TK (1 << 0) + +/** + * A mask used internally with the status flags. + * + * When set the flag indicates that the Daily Tracing Key has been correctly + * initialised. + */ #define STATUS_DTK (1 << 1) + +/** + * A mask used internally with the status flags. + * + * When set the flag indicates that the Rolling Proximity Identifier has been + * correctly initialised. + */ #define STATUS_RPI (1 << 2) +/** + * A mask used internally with the status flags. + * + * When all of these flags are set it indicates that the structure is fully + * initialised. + * . + */ #define STATUS_INITIALISED (STATUS_TK | STATUS_DTK | STATUS_RPI) // Structures +/** + * @brief The core structure for storing Contact Tracing state. + * + * This is an opaque structure that contains the core state for the library. + * + * This must be passed as the first parameter of every non-static function. + * + * The structure typedef is in contrac.h + */ struct _Contrac { // Tracing key unsigned char tk[TK_SIZE]; @@ -48,9 +97,6 @@ struct _Contrac { // Rolling proximity identifier Rpi * rpi; - uint32_t day_number; - uint8_t time_interval_number; - uint32_t status; }; @@ -58,6 +104,11 @@ struct _Contrac { // Function definitions +/** + * Creates a new instance of the class. + * + * @return The newly created object. + */ Contrac * contrac_new() { Contrac * data; @@ -68,6 +119,11 @@ Contrac * contrac_new() { return data; } +/** + * Deletes an instance of the class, freeing up the memory allocated to it. + * + * @param data The instance to free. + */ void contrac_delete(Contrac * data) { if (data) { dtk_delete(data->dtk); @@ -80,6 +136,15 @@ void contrac_delete(Contrac * data) { } } +/** + * Generates a random Contact Tracing Key. + * + * The operation may fail under certain circumstances, such as there being + * insufficient entropy in the system to guarantee a random result. + * + * @param data The context object to work with. + * @return true if the operation completed successfully, false otherwise. + */ bool contrac_generate_tracing_key(Contrac * data) { int result; @@ -96,23 +161,63 @@ bool contrac_generate_tracing_key(Contrac * data) { return (result == 1); } +/** + * Sets the current day number. + * + * This will result in a new Daily Tracing Key being generated based on the + * day provided. If neither the Tracing Key nor the day have changed, the DTK + * will remain the same. + * + * The day number is calculated as: + * (Number of Seconds since Epoch) / (60 * 60 * 24) + * + * Which can be calculated from the current epoch using the + * epoch_to_day_number() function. + * + * The operation may fail if a Tracing Key has yet to be configured. + * + * @param data The context object to work with. + * @param day_number The day number used to generate the DTK. + * @return true if the operation completed successfully, false otherwise. + */ bool contrac_set_day_number(Contrac * data, uint32_t day_number) { bool result; result = ((data->status & STATUS_TK) != 0); if (result) { - result = contrac_generate_daily_key(data->dtk, data, day_number); + result = dtk_generate_daily_key(data->dtk, data, day_number); } if (result) { - data->day_number = day_number; data->status |= STATUS_DTK; } return result; } +/** + * Sets the current time interval number. + * + * This will result in a new Rolling Proximity Idetifier being generated based + * on the time interval number. If none of the Tracing Key, day nor time + * interval have changed, the RPI will stay the same. + * + * The time interval number is calculated as: + * (Seconds Since Start of DayNumber) / (60 * 10) + * + * and must fall in the interval [0, 143]. + * + * It can be calculated from the current epoch using the + * epoch_to_time_interval_number() function. + * + * The operation may fail if a Tracing Key or Daily Tracing Key have yet to be + * configured. + * + * @param data The context object to work with. + * @param time_interval_number The time interval number to set. + * @return true if the operation completed successfully, false otherwise. + */ bool contrac_set_time_interval_number(Contrac * data, uint8_t time_interval_number) { bool result; @@ -123,27 +228,128 @@ bool contrac_set_time_interval_number(Contrac * data, uint8_t time_interval_numb } if (result) { - data->time_interval_number = time_interval_number; data->status |= STATUS_RPI; } return result; } +/** + * Gets whether the internal state has been fully configured or not. + * + * The internal state must be fully configured before a Daily Tracing Key or + * Rolling Proximity Identifier can be calculated. This function returns whether + * it is in this state or not. + * + * In order to fully configure the structure, a Tracing Key must either be + * generated using contrac_generate_tracing_key(), or set using either + * contrac_set_tracing_key() or contrac_set_tracing_key_base64(). + * + * In addition the day number and time interval number must be set using + * contrac_set_day_number() and contrac_set_time_interval_number() respectively. + * + * Alternatively these can be set automatically based on the current time using + * contrac_update_current_time(). + * + * @param data The context object to work with. + * @return true if the state is fully initialised, false otherwise. + */ bool contrac_get_initialised(Contrac const * data) { return ((data->status & STATUS_INITIALISED) == STATUS_INITIALISED); } +/** + * Gets the current day number. + * + * Gets the current day number used to generate the most recent DTK. + * + * The day number is calculated as: + * (Number of Seconds since Epoch) / (60 * 60 * 24) + * + * Which can be caluclated from the current epoch using the + * epoch_to_day_number() function. + * + * @param data The context object to work with. + * @return The day number most recently used to generate the DTK. + */ +uint32_t contrac_get_day_number(Contrac * data) { + return dtk_get_day_number(data->dtk); +} + +/** + * Gets the current time interval number. + * + * Gets the current time interval number used to generate the most recent RPI. + * + * The time interval number is calculated as: + * (Seconds Since Start of DayNumber) / (60 * 10) + * + * and must fall in the interval [0, 143]. + * + * It can be caluclated from the current epoch using the + * epoch_to_time_interval_number() function. + * + * @param data The context object to work with. + * @return The time interval number most recently used to generate the RPI. + */ +uint8_t contrac_get_time_interval_number(Contrac * data) { + return rpi_get_time_interval_number(data->rpi); +} + +/** + * Sets the Tracing Key for the device in binary format. + * + * When first configuring a system, the Tracing Key must be generated + * randomly, e.g. using contrac_generate_tracing_key(). + * + * However, on future runs it's important that the Tracing Key stays the same. + * In this case the key can be restored using this function. + * + * The tracing_key buffer passed in must contain exactly TK_SIZE (32) bytes of + * data.It doen't have to be null terminated. + * + * @param data The context object to work with. + * @param tracing_key The Tracing Key to set in binary format. + */ void contrac_set_tracing_key(Contrac * data, unsigned char const * tracing_key) { memcpy(data->tk, tracing_key, TK_SIZE); data->status |= STATUS_TK; } -const unsigned char * contrac_get_tracing_key(Contrac const * data) { +/** + * Gets the Tracing Key for the device in binary format. + * + * This allows the Tracing Key to be extracted. The Tracing Key should be kept + * secret (to maintain privacy), however it still may need to be extracted, for + * example so it can be saved in persistent storage between runs. + * + * The buffer returned will contain exactly TK_SIZE (32) bytes of data in binary + * format. This may therefore contain null bytes, and the buffer will not + * necessarily be null terminated. Future operations may cause the data to + * change, so the caller should make a copy of the buffer rather than keeping + * the pointer to it. + * + * @param data The context object to work with. + * @return The Tracing Key in binary format, not null terminated. + */ +unsigned char const * contrac_get_tracing_key(Contrac const * data) { return data->tk; } -// base64 buffer must be at least 45 bytes (TK_SIZE_BASE64 + 1) +/** + * Gets the Tracing Key for the device in base64 format. + * + * This allows the Tracing Key to be extracted. The Tracing Key should be kept + * secret (to maintain privacy), however it still may need to be extracted, for + * example so it can be saved in persistent storage between runs. + * + * The buffer provided must be at least TK_SIZE_BAS64 + 1 (45) bytes long and + * will be filled out with the Tracing Key in base64 format (TK_SIZE_BAS64 + * bytes) followed by a null terminator (1 byte). + * + * @param data The context object to work with. + * @param base64 A buffer of at least TK_SIZE_BAS64 + 1 bytes for the result. + */ void contrac_get_tracing_key_base64(Contrac const * data, char * base64) { size_t size = TK_SIZE_BASE64 + 1; base64_encode_binary_to_base64(data->tk, TK_SIZE, (unsigned char *)base64, &size); @@ -153,7 +359,22 @@ void contrac_get_tracing_key_base64(Contrac const * data, char * base64) { } } -// tracing_key input must be 44 bytes long +/** + * Sets the Tracing Key for the device in base64 format. + * + * When first configuring a system, the Tracing Key must be generated + * randomly, e.g. using contrac_generate_tracing_key(). + * + * However, on future runs it's important that the Tracing Key stays the same. + * In this case the key can be restored using this function. + * + * The tracing_key buffer passed in must contain exactly TK_SIZE_BASE64 (44) + * bytes of base64-encoded data. It can be null terminated, but doesn't need to + * be. + * + * @param data The context object to work with. + * @param tracing_key The Tracing Key to set in base64 format. + */ bool contrac_set_tracing_key_base64(Contrac * data, char const * tracing_key) { bool result = true; unsigned char tk[TK_SIZE]; @@ -181,11 +402,42 @@ bool contrac_set_tracing_key_base64(Contrac * data, char const * tracing_key) { return result; } -const unsigned char * contrac_get_daily_key(Contrac const * data) { +/** + * Gets the Daily Tracing Key for the device in binary format. + * + * This allows the Daily Tracing Key to be extracted. The Daily Tracing Key + * should be kept secret (to maintain privacy) until a positive test is + * confirmed, at which point the user may choose to upload the key to a + * Diagnosis Server, so that others can be notified. + * + * The buffer returned will contain exactly DTK_SIZE (16) bytes of data in + * binary format. This may therefore contain null bytes, and the buffer will not + * necessarily be null terminated. Future operations may cause the data to + * change, so the caller should make a copy of the buffer rather than keeping + * the pointer to it. + * + * @param data The context object to work with. + * @return The Daily Tracing Key in binary format, not null terminated. + */ +unsigned char const * contrac_get_daily_key(Contrac const * data) { return dtk_get_daily_key(data->dtk); } -// base64 buffer must be at least 25 bytes (DTK_SIZE_BASE64 + 1) +/** + * Gets the Daily Tracing Key for the device in base64 format. + * + * This allows the Daily Tracing Key to be extracted. The Daily Tracing Key + * should be kept secret (to maintain privacy) until a positive test is + * confirmed, at which point the user may choose to upload the key to a + * Diagnosis Server, so that others can be notified. + * + * The buffer provided must be at least DTK_SIZE_BASE64 + 1 (25) bytes long and + * will be filled out with the Tracing Key in base64 format (DTK_SIZE_BASE64 + * bytes) followed by a null terminator (1 byte). + * + * @param data The context object to work with. + * @param base64 A buffer of at least DTK_SIZE_BASE64 + 1 bytes for the result. + */ void contrac_get_daily_key_base64(Contrac const * data, char * base64) { size_t size = DTK_SIZE_BASE64 + 1; base64_encode_binary_to_base64(dtk_get_daily_key(data->dtk), DTK_SIZE, (unsigned char *)base64, &size); @@ -195,11 +447,41 @@ void contrac_get_daily_key_base64(Contrac const * data, char * base64) { } } -const unsigned char * contrac_get_proximity_id(Contrac const * data) { +/** + * Gets the Rolling Proximity Identifier for the device in binary format. + * + * This allows the Rolling Proximity Identifier to be extracted. The Rolling + * Proximity Identifier is for broadcast to other devices using BLE and changes + * frequently. + * + * The buffer returned will contain exactly RPI_SIZE (16) bytes of data in + * binary format. This may therefore contain null bytes, and the buffer will not + * necessarily be null terminated. Future operations may cause the data to + * change, so the caller should make a copy of the buffer rather than keeping + * the pointer to it. + * + * @param data The context object to work with. + * @return The Rolling Proximity Identifier in binary format, not null + * terminated. + */ +unsigned char const * contrac_get_proximity_id(Contrac const * data) { return rpi_get_proximity_id(data->rpi); } -// base64 buffer must be at least 25 bytes (RPI_SIZE_BASE64 + 1) +/** + * Gets the Rolling Proximity Identifier for the device in base64 format. + * + * This allows the Rolling Proximity Identifier to be extracted. The Rolling + * Proximity Identifier is for broadcast to other devices using BLE and changes + * frequently. + * + * The buffer provided must be at least RPI_SIZE_BASE64 + 1 (25) bytes long and + * will be filled out with the Tracing Key in base64 format (RPI_SIZE_BASE64 + * bytes) followed by a null terminator (1 byte). + * + * @param data The context object to work with. + * @param base64 A buffer of at least RPI_SIZE_BASE64 + 1 bytes for the result. + */ void contrac_get_proximity_id_base64(Contrac const * data, char * base64) { size_t size = RPI_SIZE_BASE64 + 1; base64_encode_binary_to_base64(rpi_get_proximity_id(data->rpi), RPI_SIZE, (unsigned char *)base64, &size); @@ -209,8 +491,64 @@ void contrac_get_proximity_id_base64(Contrac const * data, char * base64) { } } +/** + * Updates the Daily Tracing Key and Random Proxmity Identifer. + * + * The Daily Tracing Key changes every day, the Random Proximity Identifier + * changes every 10 minutes. + * + * Calling this function will update them both based on the current system + * time. + * + * Note that getting either the DTK or RPI does not cause an update, so if you + * want to get the correct values based on the time, it makes sense to call + * this function before getting them. + * + * The operation may fail if the state has not yet been fully initialised (for + * example if a Tracing Key has not yet been generated or set). + * + * @param data The context object to work with. + * @return true if the operation completed successfully, false otherwise. + */ +bool contrac_update_current_time(Contrac * data) { + bool result; + time_t epoch; + uint32_t dn_stored; + uint32_t dn_now; + uint8_t tn_stored; + uint8_t tn_now; + + result = true; + if ((data->status & STATUS_TK) == 0) { + // No Tracing Key has been set, so generate a random key + result = contrac_generate_tracing_key(data); + } + + epoch = time(NULL); + if (result) { + dn_now = epoch_to_day_number(epoch); + dn_stored = dtk_get_day_number(data->dtk); + // Only set again if uninitialised or the time has changed + if ((dn_now != dn_stored) || ((data->status & STATUS_DTK) == 0)) { + result = contrac_set_day_number(data, dn_now); + } + } + + if (result) { + tn_now = epoch_to_time_interval_number(epoch); + tn_stored= rpi_get_time_interval_number(data->rpi); + + // Only set again if uninitialised or the time has changed + if ((tn_now != tn_stored) || (dn_now != dn_stored) || ((data->status & STATUS_RPI) == 0)) { + result = contrac_set_time_interval_number(data, tn_now); + } + } + + return result; +} +/** @} addtogroup KeyGeneration */