1 /** \ingroup KeyGeneration
3 * @author David Llewellyn-Jones <david@flypig.co.uk>
8 * Copyright David Llewellyn-Jones, 2020
9 * Released under the GPLv2.
11 * @brief Core Contact Tracing functionality
12 * @section DESCRIPTION
14 * This class provides the core Contact Tracing functionality. It provides an
16 * 1. Generating a random Tracing Key.
17 * 2. Generating a Daily Tracing Key based on the current day number.
18 * 3. Generating a Rolling Proximity Identifier based on the current time
21 * Values can be extracted and set in binary or base64 format.
25 /** \addtogroup KeyGeneration
37 #include <openssl/crypto.h>
38 #include <openssl/rand.h>
39 #include <openssl/err.h>
41 #include "contrac/log.h"
42 #include "contrac/utils.h"
43 #include "contrac/rpi.h"
45 #include "contrac/contrac.h"
52 * When set the flag indicates that the Tracing Key has been correctly
55 #define STATUS_TK (1 << 0)
60 * When set the flag indicates that the Daily Tracing Key has been correctly
63 #define STATUS_DTK (1 << 1)
68 * When set the flag indicates that the Rolling Proximity Identifier has been
69 * correctly initialised.
71 #define STATUS_RPI (1 << 2)
76 * When all of these flags are set it indicates that the structure is fully
80 #define STATUS_INITIALISED (STATUS_TK | STATUS_DTK | STATUS_RPI)
84 * @brief The core structure for storing Contact Tracing state.
86 * This is an opaque structure that contains the core state for the library.
88 * This must be passed as the first parameter of every non-static function.
90 * The structure typedef is in contrac.h
94 unsigned char tk
[TK_SIZE
];
97 // Rolling proximity identifier
103 // Function prototypes
105 // Function definitions
108 * Create a new instance of the class.
110 * @return The newly created object.
112 Contrac
* contrac_new() {
115 data
= calloc(sizeof(Contrac
), 1);
116 data
->dtk
= dtk_new();
117 data
->rpi
= rpi_new();
123 * Delete an instance of the class, freeing up the memory allocated to it.
125 * @param data The instance to free.
127 void contrac_delete(Contrac
* data
) {
129 dtk_delete(data
->dtk
);
130 rpi_delete(data
->rpi
);
132 // Clear the data for security
133 memset(data
, 0, sizeof(Contrac
));
140 * Generate a random Contact Tracing Key.
142 * The operation may fail under certain circumstances, such as there being
143 * insufficient entropy in the system to guarantee a random result.
145 * @param data The context object to work with.
146 * @return true if the operation completed successfully, false otherwise.
148 bool contrac_generate_tracing_key(Contrac
* data
) {
152 result
= RAND_bytes(data
->tk
, TK_SIZE
);
155 data
->status
|= STATUS_TK
;
158 LOG(LOG_ERR
, "Error generating tracing key: %lu\n", ERR_get_error());
161 return (result
== 1);
165 * Set the current day number.
167 * This will result in a new Daily Tracing Key being generated based on the
168 * day provided. If neither the Tracing Key nor the day have changed, the DTK
169 * will remain the same.
171 * The day number is calculated as:
172 * (Number of Seconds since Epoch) / (60 * 60 * 24)
174 * Which can be caluclated from the current epoch using the
175 * epoch_to_day_number() function.
177 * The operation may fail if a Tracing Key has yet to be configured.
179 * @param data The context object to work with.
180 * @return true if the operation completed successfully, false otherwise.
182 bool contrac_set_day_number(Contrac
* data
, uint32_t day_number
) {
185 result
= ((data
->status
& STATUS_TK
) != 0);
188 result
= dtk_generate_daily_key(data
->dtk
, data
, day_number
);
192 data
->status
|= STATUS_DTK
;
199 * Set the current time interval number.
201 * This will result in a new Rolling Proximity Idetifier being generated based
202 * on the time interval number. If none of the Tracing Key, day nor time
203 * interval have changed, the RPI will stay the same.
205 * The time interval number is calculated as:
206 * (Seconds Since Start of DayNumber) / (60 * 10)
208 * and must fall in the interval [0, 143].
210 * It can be caluclated from the current epoch using the
211 * epoch_to_time_interval_number() function.
213 * The operation may fail if a Tracing Key or Daily Tracing Key have yet to be
216 * @param data The context object to work with.
217 * @param time_interval_number The time interval number to set.
218 * @return true if the operation completed successfully, false otherwise.
220 bool contrac_set_time_interval_number(Contrac
* data
, uint8_t time_interval_number
) {
223 result
= ((data
->status
& STATUS_DTK
) != 0);
226 result
= rpi_generate_proximity_id(data
->rpi
, data
->dtk
, time_interval_number
);
230 data
->status
|= STATUS_RPI
;
237 * Get whether the internal state has been fully configured or not.
239 * The internal state must be fully configured before a Daily Tracing Key or
240 * Rolling Proximity Identifier can be calculated. This function returns whether
241 * it is in this state or not.
243 * In order to fully configure the structure, a Tracing Key must either be
244 * generated using contrac_generate_tracing_key(), or set using either
245 * contrac_set_tracing_key() or contrac_set_tracing_key_base64().
247 * In addition the day number and time interval number must be set using
248 * contrac_set_day_number() and contrac_set_time_interval_number() respectively.
250 * Alternatively these can be set automatically based on the current time using
251 * contrac_update_current_time().
253 * @param data The context object to work with.
254 * @return true if the state is fully initialised, false otherwise.
256 bool contrac_get_initialised(Contrac
const * data
) {
257 return ((data
->status
& STATUS_INITIALISED
) == STATUS_INITIALISED
);
261 * Set the Tracing Key for the device in binary format.
263 * When first configuring a system, the Tracing Key must be generated
264 * randomly, e.g. using contrac_generate_tracing_key().
266 * However, on future runs it's important that the Tracing Key stays the same.
267 * In this case the key can be restored using this function.
269 * The tracing_key buffer passed in must contain exactly TK_SIZE (32) bytes of
272 * @param data The context object to work with.
273 * @param tracing_key The Tracing Key to set in binary format.
275 void contrac_set_tracing_key(Contrac
* data
, unsigned char const * tracing_key
) {
276 memcpy(data
->tk
, tracing_key
, TK_SIZE
);
277 data
->status
|= STATUS_TK
;
281 * Get the Tracing Key for the device in binary format.
283 * This allows the Tracing Key to be extracted. The Tracing Key should be kept
284 * secret (to maintain privacy), however it still may need to be extracted, for
285 * example so it can be saved in persistent storage between runs.
287 * The buffer returned will contain exactly TK_SIZE (32) bytes of data in binary
288 * format. This may therefore contain null bytes, and the buffer will not
289 * necessarily be null terminated.
291 * @param data The context object to work with.
292 * @return The Tracing Key in binary format, not null terminated.
294 unsigned char const * contrac_get_tracing_key(Contrac
const * data
) {
299 * Get the Tracing Key for the device in base64 format.
301 * This allows the Tracing Key to be extracted. The Tracing Key should be kept
302 * secret (to maintain privacy), however it still may need to be extracted, for
303 * example so it can be saved in persistent storage between runs.
305 * The buffer provided must be at least TK_SIZE_BAS64 + 1 (45) bytes long and
306 * will be filled out with the Tracing Key in base64 format (TK_SIZE_BAS64
307 * bytes) followed by a null terminator (1 byte).
309 * @param data The context object to work with.
310 * @param base64 A buffer of at least TK_SIZE_BAS64 + 1 bytes for the result.
312 void contrac_get_tracing_key_base64(Contrac
const * data
, char * base64
) {
313 size_t size
= TK_SIZE_BASE64
+ 1;
314 base64_encode_binary_to_base64(data
->tk
, TK_SIZE
, (unsigned char *)base64
, &size
);
316 if (size
!= (TK_SIZE_BASE64
+ 1)) {
317 LOG(LOG_ERR
, "Base64 tracing key has incorrect size of %d bytes.\n", size
);
322 * Set the Tracing Key for the device in base64 format.
324 * When first configuring a system, the Tracing Key must be generated
325 * randomly, e.g. using contrac_generate_tracing_key().
327 * However, on future runs it's important that the Tracing Key stays the same.
328 * In this case the key can be restored using this function.
330 * The tracing_key buffer passed in must contain exactly TK_SIZE_BASE64 (44)
331 * bytes of base64-encoded data. It can be null terminated, but doesn't need to
334 * @param data The context object to work with.
335 * @param tracing_key The Tracing Key to set in base64 format.
337 bool contrac_set_tracing_key_base64(Contrac
* data
, char const * tracing_key
) {
339 unsigned char tk
[TK_SIZE
];
342 if (strlen(tracing_key
) != TK_SIZE_BASE64
) {
343 LOG(LOG_ERR
, "Base64 tracing key has incorrect size. Should be %d bytes.\n", TK_SIZE_BASE64
);
349 base64_decode_base64_to_binary((unsigned char *)tracing_key
, TK_SIZE_BASE64
, tk
, &size
);
351 if (size
< TK_SIZE
) {
352 LOG(LOG_ERR
, "Base64 tracking key output is too short %d bytes.\n", size
);
358 contrac_set_tracing_key(data
, tk
);
365 * Get the Daily Tracing Key for the device in binary format.
367 * This allows the Daily Tracing Key to be extracted. The Daily Tracing Key
368 * should be kept secret (to maintain privacy) until a positive test is
369 * confirmed, at which point the user may choose to upload the key to a
370 * Diagnosis Server, so that others can be notified.
372 * The buffer returned will contain exactly DTK_SIZE (16) bytes of data in
373 * binary format. This may therefore contain null bytes, and the buffer will not
374 * necessarily be null terminated.
376 * @param data The context object to work with.
377 * @return The Daily Tracing Key in binary format, not null terminated.
379 unsigned char const * contrac_get_daily_key(Contrac
const * data
) {
380 return dtk_get_daily_key(data
->dtk
);
384 * Get the Daily Tracing Key for the device in base64 format.
386 * This allows the Daily Tracing Key to be extracted. The Daily Tracing Key
387 * should be kept secret (to maintain privacy) until a positive test is
388 * confirmed, at which point the user may choose to upload the key to a
389 * Diagnosis Server, so that others can be notified.
391 * The buffer provided must be at least DTK_SIZE_BASE64 + 1 (25) bytes long and
392 * will be filled out with the Tracing Key in base64 format (DTK_SIZE_BASE64
393 * bytes) followed by a null terminator (1 byte).
395 * @param data The context object to work with.
396 * @param base64 A buffer of at least DTK_SIZE_BASE64 + 1 bytes for the result.
398 void contrac_get_daily_key_base64(Contrac
const * data
, char * base64
) {
399 size_t size
= DTK_SIZE_BASE64
+ 1;
400 base64_encode_binary_to_base64(dtk_get_daily_key(data
->dtk
), DTK_SIZE
, (unsigned char *)base64
, &size
);
402 if (size
!= (DTK_SIZE_BASE64
+ 1)) {
403 LOG(LOG_ERR
, "Base64 daily key has incorrect size of %d bytes.\n", size
);
408 * Get the Rolling Proximity Identifier for the device in binary format.
410 * This allows the Rolling Proximity Identifier to be extracted. The Rolling
411 * Proximity Identifier is for broadcast to other devices using BLE and changes
414 * The buffer returned will contain exactly RPI_SIZE (16) bytes of data in
415 * binary format. This may therefore contain null bytes, and the buffer will not
416 * necessarily be null terminated.
418 * @param data The context object to work with.
419 * @return The Rolling Proximity Identifier in binary format, not null
422 unsigned char const * contrac_get_proximity_id(Contrac
const * data
) {
423 return rpi_get_proximity_id(data
->rpi
);
427 * Get the Rolling Proximity Identifier for the device in base64 format.
429 * This allows the Rolling Proximity Identifier to be extracted. The Rolling
430 * Proximity Identifier is for broadcast to other devices using BLE and changes
433 * The buffer provided must be at least RPI_SIZE_BASE64 + 1 (25) bytes long and
434 * will be filled out with the Tracing Key in base64 format (RPI_SIZE_BASE64
435 * bytes) followed by a null terminator (1 byte).
437 * @param data The context object to work with.
438 * @param base64 A buffer of at least RPI_SIZE_BASE64 + 1 bytes for the result.
440 void contrac_get_proximity_id_base64(Contrac
const * data
, char * base64
) {
441 size_t size
= RPI_SIZE_BASE64
+ 1;
442 base64_encode_binary_to_base64(rpi_get_proximity_id(data
->rpi
), RPI_SIZE
, (unsigned char *)base64
, &size
);
444 if (size
!= (RPI_SIZE_BASE64
+ 1)) {
445 LOG(LOG_ERR
, "Base64 proximity id has incorrect size of %d bytes.\n", size
);
450 * Update the Daily Tracing Key and Random Proxmity Identifer.
452 * The Daily Tracing Key changes every day, the Random Proximity Identifier
453 * changes every 10 minutes.
455 * Calling this function will update them both based on the current system
458 * Note that getting either the DTK or RPI does not cause an update, so if you
459 * want to get the correct values based on the time, it makes sense to call
460 * this function before getting them.
462 * The operation may fail if the state has not yet been fully initialised (for
463 * example if a Tracing Key has not yet been generated or set).
465 * @param data The context object to work with.
466 * @return true if the operation completed successfully, false otherwise.
468 bool contrac_update_current_time(Contrac
* data
) {
478 if ((data
->status
& STATUS_TK
) == 0) {
479 // No Tracing Key has been set, so generate a random key
480 result
= contrac_generate_tracing_key(data
);
486 dn_now
= epoch_to_day_number(epoch
);
487 dn_stored
= dtk_get_day_number(data
->dtk
);
489 // Only set again if uninitialised or the time has changed
490 if ((dn_now
!= dn_stored
) || ((data
->status
& STATUS_DTK
) == 0)) {
491 result
= contrac_set_day_number(data
, dn_now
);
496 tn_now
= epoch_to_time_interval_number(epoch
);
497 tn_stored
= rpi_get_time_interval_number(data
->rpi
);
499 // Only set again if uninitialised or the time has changed
500 if ((tn_now
!= tn_stored
) || (dn_now
!= dn_stored
) || ((data
->status
& STATUS_RPI
) == 0)) {
501 result
= contrac_set_time_interval_number(data
, tn_now
);
508 /** @} addtogroup KeyGeneration */