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"
50 * A mask used internally with the status flags.
52 * When set the flag indicates that the Tracing Key has been correctly
55 #define STATUS_TK (1 << 0)
58 * A mask used internally with the status flags.
60 * When set the flag indicates that the Daily Tracing Key has been correctly
63 #define STATUS_DTK (1 << 1)
66 * A mask used internally with the status flags.
68 * When set the flag indicates that the Rolling Proximity Identifier has been
69 * correctly initialised.
71 #define STATUS_RPI (1 << 2)
74 * A mask used internally with the status flags.
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 * Creates 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 * Deletes 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 * Generates 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 * Sets 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 calculated 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 * @param day_number The day number used to generate the DTK.
181 * @return true if the operation completed successfully, false otherwise.
183 bool contrac_set_day_number(Contrac
* data
, uint32_t day_number
) {
186 result
= ((data
->status
& STATUS_TK
) != 0);
189 result
= dtk_generate_daily_key(data
->dtk
, data
, day_number
);
193 data
->status
|= STATUS_DTK
;
200 * Sets the current time interval number.
202 * This will result in a new Rolling Proximity Idetifier being generated based
203 * on the time interval number. If none of the Tracing Key, day nor time
204 * interval have changed, the RPI will stay the same.
206 * The time interval number is calculated as:
207 * (Seconds Since Start of DayNumber) / (60 * 10)
209 * and must fall in the interval [0, 143].
211 * It can be calculated from the current epoch using the
212 * epoch_to_time_interval_number() function.
214 * The operation may fail if a Tracing Key or Daily Tracing Key have yet to be
217 * @param data The context object to work with.
218 * @param time_interval_number The time interval number to set.
219 * @return true if the operation completed successfully, false otherwise.
221 bool contrac_set_time_interval_number(Contrac
* data
, uint8_t time_interval_number
) {
224 result
= ((data
->status
& STATUS_DTK
) != 0);
227 result
= rpi_generate_proximity_id(data
->rpi
, data
->dtk
, time_interval_number
);
231 data
->status
|= STATUS_RPI
;
238 * Gets whether the internal state has been fully configured or not.
240 * The internal state must be fully configured before a Daily Tracing Key or
241 * Rolling Proximity Identifier can be calculated. This function returns whether
242 * it is in this state or not.
244 * In order to fully configure the structure, a Tracing Key must either be
245 * generated using contrac_generate_tracing_key(), or set using either
246 * contrac_set_tracing_key() or contrac_set_tracing_key_base64().
248 * In addition the day number and time interval number must be set using
249 * contrac_set_day_number() and contrac_set_time_interval_number() respectively.
251 * Alternatively these can be set automatically based on the current time using
252 * contrac_update_current_time().
254 * @param data The context object to work with.
255 * @return true if the state is fully initialised, false otherwise.
257 bool contrac_get_initialised(Contrac
const * data
) {
258 return ((data
->status
& STATUS_INITIALISED
) == STATUS_INITIALISED
);
262 * Gets the current day number.
264 * Gets the current day number used to generate the most recent DTK.
266 * The day number is calculated as:
267 * (Number of Seconds since Epoch) / (60 * 60 * 24)
269 * Which can be caluclated from the current epoch using the
270 * epoch_to_day_number() function.
272 * @param data The context object to work with.
273 * @return The day number most recently used to generate the DTK.
275 uint32_t contrac_get_day_number(Contrac
* data
) {
276 return dtk_get_day_number(data
->dtk
);
280 * Gets the current time interval number.
282 * Gets the current time interval number used to generate the most recent RPI.
284 * The time interval number is calculated as:
285 * (Seconds Since Start of DayNumber) / (60 * 10)
287 * and must fall in the interval [0, 143].
289 * It can be caluclated from the current epoch using the
290 * epoch_to_time_interval_number() function.
292 * @param data The context object to work with.
293 * @return The time interval number most recently used to generate the RPI.
295 uint8_t contrac_get_time_interval_number(Contrac
* data
) {
296 return rpi_get_time_interval_number(data
->rpi
);
300 * Sets the Tracing Key for the device in binary format.
302 * When first configuring a system, the Tracing Key must be generated
303 * randomly, e.g. using contrac_generate_tracing_key().
305 * However, on future runs it's important that the Tracing Key stays the same.
306 * In this case the key can be restored using this function.
308 * The tracing_key buffer passed in must contain exactly TK_SIZE (32) bytes of
309 * data.It doen't have to be null terminated.
311 * @param data The context object to work with.
312 * @param tracing_key The Tracing Key to set in binary format.
314 void contrac_set_tracing_key(Contrac
* data
, unsigned char const * tracing_key
) {
315 memcpy(data
->tk
, tracing_key
, TK_SIZE
);
316 data
->status
|= STATUS_TK
;
320 * Gets the Tracing Key for the device in binary format.
322 * This allows the Tracing Key to be extracted. The Tracing Key should be kept
323 * secret (to maintain privacy), however it still may need to be extracted, for
324 * example so it can be saved in persistent storage between runs.
326 * The buffer returned will contain exactly TK_SIZE (32) bytes of data in binary
327 * format. This may therefore contain null bytes, and the buffer will not
328 * necessarily be null terminated. Future operations may cause the data to
329 * change, so the caller should make a copy of the buffer rather than keeping
332 * @param data The context object to work with.
333 * @return The Tracing Key in binary format, not null terminated.
335 unsigned char const * contrac_get_tracing_key(Contrac
const * data
) {
340 * Gets the Tracing Key for the device in base64 format.
342 * This allows the Tracing Key to be extracted. The Tracing Key should be kept
343 * secret (to maintain privacy), however it still may need to be extracted, for
344 * example so it can be saved in persistent storage between runs.
346 * The buffer provided must be at least TK_SIZE_BAS64 + 1 (45) bytes long and
347 * will be filled out with the Tracing Key in base64 format (TK_SIZE_BAS64
348 * bytes) followed by a null terminator (1 byte).
350 * @param data The context object to work with.
351 * @param base64 A buffer of at least TK_SIZE_BAS64 + 1 bytes for the result.
353 void contrac_get_tracing_key_base64(Contrac
const * data
, char * base64
) {
354 size_t size
= TK_SIZE_BASE64
+ 1;
355 base64_encode_binary_to_base64(data
->tk
, TK_SIZE
, (unsigned char *)base64
, &size
);
357 if (size
!= (TK_SIZE_BASE64
+ 1)) {
358 LOG(LOG_ERR
, "Base64 tracing key has incorrect size of %d bytes.\n", size
);
363 * Sets the Tracing Key for the device in base64 format.
365 * When first configuring a system, the Tracing Key must be generated
366 * randomly, e.g. using contrac_generate_tracing_key().
368 * However, on future runs it's important that the Tracing Key stays the same.
369 * In this case the key can be restored using this function.
371 * The tracing_key buffer passed in must contain exactly TK_SIZE_BASE64 (44)
372 * bytes of base64-encoded data. It can be null terminated, but doesn't need to
375 * @param data The context object to work with.
376 * @param tracing_key The Tracing Key to set in base64 format.
378 bool contrac_set_tracing_key_base64(Contrac
* data
, char const * tracing_key
) {
380 unsigned char tk
[TK_SIZE
];
383 if (strlen(tracing_key
) != TK_SIZE_BASE64
) {
384 LOG(LOG_ERR
, "Base64 tracing key has incorrect size. Should be %d bytes.\n", TK_SIZE_BASE64
);
390 base64_decode_base64_to_binary((unsigned char *)tracing_key
, TK_SIZE_BASE64
, tk
, &size
);
392 if (size
< TK_SIZE
) {
393 LOG(LOG_ERR
, "Base64 tracking key output is too short %d bytes.\n", size
);
399 contrac_set_tracing_key(data
, tk
);
406 * Gets the Daily Tracing Key for the device in binary format.
408 * This allows the Daily Tracing Key to be extracted. The Daily Tracing Key
409 * should be kept secret (to maintain privacy) until a positive test is
410 * confirmed, at which point the user may choose to upload the key to a
411 * Diagnosis Server, so that others can be notified.
413 * The buffer returned will contain exactly DTK_SIZE (16) bytes of data in
414 * binary format. This may therefore contain null bytes, and the buffer will not
415 * necessarily be null terminated. Future operations may cause the data to
416 * change, so the caller should make a copy of the buffer rather than keeping
419 * @param data The context object to work with.
420 * @return The Daily Tracing Key in binary format, not null terminated.
422 unsigned char const * contrac_get_daily_key(Contrac
const * data
) {
423 return dtk_get_daily_key(data
->dtk
);
427 * Gets the Daily Tracing Key for the device in base64 format.
429 * This allows the Daily Tracing Key to be extracted. The Daily Tracing Key
430 * should be kept secret (to maintain privacy) until a positive test is
431 * confirmed, at which point the user may choose to upload the key to a
432 * Diagnosis Server, so that others can be notified.
434 * The buffer provided must be at least DTK_SIZE_BASE64 + 1 (25) bytes long and
435 * will be filled out with the Tracing Key in base64 format (DTK_SIZE_BASE64
436 * bytes) followed by a null terminator (1 byte).
438 * @param data The context object to work with.
439 * @param base64 A buffer of at least DTK_SIZE_BASE64 + 1 bytes for the result.
441 void contrac_get_daily_key_base64(Contrac
const * data
, char * base64
) {
442 size_t size
= DTK_SIZE_BASE64
+ 1;
443 base64_encode_binary_to_base64(dtk_get_daily_key(data
->dtk
), DTK_SIZE
, (unsigned char *)base64
, &size
);
445 if (size
!= (DTK_SIZE_BASE64
+ 1)) {
446 LOG(LOG_ERR
, "Base64 daily key has incorrect size of %d bytes.\n", size
);
451 * Gets the Rolling Proximity Identifier for the device in binary format.
453 * This allows the Rolling Proximity Identifier to be extracted. The Rolling
454 * Proximity Identifier is for broadcast to other devices using BLE and changes
457 * The buffer returned will contain exactly RPI_SIZE (16) bytes of data in
458 * binary format. This may therefore contain null bytes, and the buffer will not
459 * necessarily be null terminated. Future operations may cause the data to
460 * change, so the caller should make a copy of the buffer rather than keeping
463 * @param data The context object to work with.
464 * @return The Rolling Proximity Identifier in binary format, not null
467 unsigned char const * contrac_get_proximity_id(Contrac
const * data
) {
468 return rpi_get_proximity_id(data
->rpi
);
472 * Gets the Rolling Proximity Identifier for the device in base64 format.
474 * This allows the Rolling Proximity Identifier to be extracted. The Rolling
475 * Proximity Identifier is for broadcast to other devices using BLE and changes
478 * The buffer provided must be at least RPI_SIZE_BASE64 + 1 (25) bytes long and
479 * will be filled out with the Tracing Key in base64 format (RPI_SIZE_BASE64
480 * bytes) followed by a null terminator (1 byte).
482 * @param data The context object to work with.
483 * @param base64 A buffer of at least RPI_SIZE_BASE64 + 1 bytes for the result.
485 void contrac_get_proximity_id_base64(Contrac
const * data
, char * base64
) {
486 size_t size
= RPI_SIZE_BASE64
+ 1;
487 base64_encode_binary_to_base64(rpi_get_proximity_id(data
->rpi
), RPI_SIZE
, (unsigned char *)base64
, &size
);
489 if (size
!= (RPI_SIZE_BASE64
+ 1)) {
490 LOG(LOG_ERR
, "Base64 proximity id has incorrect size of %d bytes.\n", size
);
495 * Updates the Daily Tracing Key and Random Proxmity Identifer.
497 * The Daily Tracing Key changes every day, the Random Proximity Identifier
498 * changes every 10 minutes.
500 * Calling this function will update them both based on the current system
503 * Note that getting either the DTK or RPI does not cause an update, so if you
504 * want to get the correct values based on the time, it makes sense to call
505 * this function before getting them.
507 * The operation may fail if the state has not yet been fully initialised (for
508 * example if a Tracing Key has not yet been generated or set).
510 * @param data The context object to work with.
511 * @return true if the operation completed successfully, false otherwise.
513 bool contrac_update_current_time(Contrac
* data
) {
523 if ((data
->status
& STATUS_TK
) == 0) {
524 // No Tracing Key has been set, so generate a random key
525 result
= contrac_generate_tracing_key(data
);
531 dn_now
= epoch_to_day_number(epoch
);
532 dn_stored
= dtk_get_day_number(data
->dtk
);
534 // Only set again if uninitialised or the time has changed
535 if ((dn_now
!= dn_stored
) || ((data
->status
& STATUS_DTK
) == 0)) {
536 result
= contrac_set_day_number(data
, dn_now
);
541 tn_now
= epoch_to_time_interval_number(epoch
);
542 tn_stored
= rpi_get_time_interval_number(data
->rpi
);
544 // Only set again if uninitialised or the time has changed
545 if ((tn_now
!= tn_stored
) || (dn_now
!= dn_stored
) || ((data
->status
& STATUS_RPI
) == 0)) {
546 result
= contrac_set_time_interval_number(data
, tn_now
);
553 /** @} addtogroup KeyGeneration */