Add remaining documentation
[libcontrac.git] / src / contrac.c
1 /** \ingroup KeyGeneration
2 * @file
3 * @author David Llewellyn-Jones <david@flypig.co.uk>
4 * @version $(VERSION)
5 *
6 * @section LICENSE
7 *
8 * Copyright David Llewellyn-Jones, 2020
9 * Released under the GPLv2.
10 *
11 * @brief Core Contact Tracing functionality
12 * @section DESCRIPTION
13 *
14 * This class provides the core Contact Tracing functionality. It provides an
15 * interfaces for:
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
19 * interval number.
20 *
21 * Values can be extracted and set in binary or base64 format.
22 *
23 */
24
25 /** \addtogroup KeyGeneration
26 * @{
27 */
28
29 // Includes
30
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <stddef.h>
35 #include <time.h>
36
37 #include <openssl/crypto.h>
38 #include <openssl/rand.h>
39 #include <openssl/err.h>
40
41 #include "contrac/log.h"
42 #include "contrac/utils.h"
43 #include "contrac/rpi.h"
44
45 #include "contrac/contrac.h"
46
47 // Defines
48
49 /**
50 * A mask used internally with the status flags.
51 *
52 * When set the flag indicates that the Tracing Key has been correctly
53 * initialised.
54 */
55 #define STATUS_TK (1 << 0)
56
57 /**
58 * A mask used internally with the status flags.
59 *
60 * When set the flag indicates that the Daily Tracing Key has been correctly
61 * initialised.
62 */
63 #define STATUS_DTK (1 << 1)
64
65 /**
66 * A mask used internally with the status flags.
67 *
68 * When set the flag indicates that the Rolling Proximity Identifier has been
69 * correctly initialised.
70 */
71 #define STATUS_RPI (1 << 2)
72
73 /**
74 * A mask used internally with the status flags.
75 *
76 * When all of these flags are set it indicates that the structure is fully
77 * initialised.
78 * .
79 */
80 #define STATUS_INITIALISED (STATUS_TK | STATUS_DTK | STATUS_RPI)
81 // Structures
82
83 /**
84 * @brief The core structure for storing Contact Tracing state.
85 *
86 * This is an opaque structure that contains the core state for the library.
87 *
88 * This must be passed as the first parameter of every non-static function.
89 *
90 * The structure typedef is in contrac.h
91 */
92 struct _Contrac {
93 // Tracing key
94 unsigned char tk[TK_SIZE];
95 // Daily key
96 Dtk * dtk;
97 // Rolling proximity identifier
98 Rpi * rpi;
99
100 uint32_t status;
101 };
102
103 // Function prototypes
104
105 // Function definitions
106
107 /**
108 * Creates a new instance of the class.
109 *
110 * @return The newly created object.
111 */
112 Contrac * contrac_new() {
113 Contrac * data;
114
115 data = calloc(sizeof(Contrac), 1);
116 data->dtk = dtk_new();
117 data->rpi = rpi_new();
118
119 return data;
120 }
121
122 /**
123 * Deletes an instance of the class, freeing up the memory allocated to it.
124 *
125 * @param data The instance to free.
126 */
127 void contrac_delete(Contrac * data) {
128 if (data) {
129 dtk_delete(data->dtk);
130 rpi_delete(data->rpi);
131
132 // Clear the data for security
133 memset(data, 0, sizeof(Contrac));
134
135 free(data);
136 }
137 }
138
139 /**
140 * Generates a random Contact Tracing Key.
141 *
142 * The operation may fail under certain circumstances, such as there being
143 * insufficient entropy in the system to guarantee a random result.
144 *
145 * @param data The context object to work with.
146 * @return true if the operation completed successfully, false otherwise.
147 */
148 bool contrac_generate_tracing_key(Contrac * data) {
149 int result;
150
151 // tk <- CRNG(32)
152 result = RAND_bytes(data->tk, TK_SIZE);
153
154 if (result == 1) {
155 data->status |= STATUS_TK;
156 }
157 else {
158 LOG(LOG_ERR, "Error generating tracing key: %lu\n", ERR_get_error());
159 }
160
161 return (result == 1);
162 }
163
164 /**
165 * Sets the current day number.
166 *
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.
170 *
171 * The day number is calculated as:
172 * (Number of Seconds since Epoch) / (60 * 60 * 24)
173 *
174 * Which can be calculated from the current epoch using the
175 * epoch_to_day_number() function.
176 *
177 * The operation may fail if a Tracing Key has yet to be configured.
178 *
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.
182 */
183 bool contrac_set_day_number(Contrac * data, uint32_t day_number) {
184 bool result;
185
186 result = ((data->status & STATUS_TK) != 0);
187
188 if (result) {
189 result = dtk_generate_daily_key(data->dtk, data, day_number);
190 }
191
192 if (result) {
193 data->status |= STATUS_DTK;
194 }
195
196 return result;
197 }
198
199 /**
200 * Sets the current time interval number.
201 *
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.
205 *
206 * The time interval number is calculated as:
207 * (Seconds Since Start of DayNumber) / (60 * 10)
208 *
209 * and must fall in the interval [0, 143].
210 *
211 * It can be calculated from the current epoch using the
212 * epoch_to_time_interval_number() function.
213 *
214 * The operation may fail if a Tracing Key or Daily Tracing Key have yet to be
215 * configured.
216 *
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.
220 */
221 bool contrac_set_time_interval_number(Contrac * data, uint8_t time_interval_number) {
222 bool result;
223
224 result = ((data->status & STATUS_DTK) != 0);
225
226 if (result) {
227 result = rpi_generate_proximity_id(data->rpi, data->dtk, time_interval_number);
228 }
229
230 if (result) {
231 data->status |= STATUS_RPI;
232 }
233
234 return result;
235 }
236
237 /**
238 * Gets whether the internal state has been fully configured or not.
239 *
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.
243 *
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().
247 *
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.
250 *
251 * Alternatively these can be set automatically based on the current time using
252 * contrac_update_current_time().
253 *
254 * @param data The context object to work with.
255 * @return true if the state is fully initialised, false otherwise.
256 */
257 bool contrac_get_initialised(Contrac const * data) {
258 return ((data->status & STATUS_INITIALISED) == STATUS_INITIALISED);
259 }
260
261 /**
262 * Gets the current day number.
263 *
264 * Gets the current day number used to generate the most recent DTK.
265 *
266 * The day number is calculated as:
267 * (Number of Seconds since Epoch) / (60 * 60 * 24)
268 *
269 * Which can be caluclated from the current epoch using the
270 * epoch_to_day_number() function.
271 *
272 * @param data The context object to work with.
273 * @return The day number most recently used to generate the DTK.
274 */
275 uint32_t contrac_get_day_number(Contrac * data) {
276 return dtk_get_day_number(data->dtk);
277 }
278
279 /**
280 * Gets the current time interval number.
281 *
282 * Gets the current time interval number used to generate the most recent RPI.
283 *
284 * The time interval number is calculated as:
285 * (Seconds Since Start of DayNumber) / (60 * 10)
286 *
287 * and must fall in the interval [0, 143].
288 *
289 * It can be caluclated from the current epoch using the
290 * epoch_to_time_interval_number() function.
291 *
292 * @param data The context object to work with.
293 * @return The time interval number most recently used to generate the RPI.
294 */
295 uint8_t contrac_get_time_interval_number(Contrac * data) {
296 return rpi_get_time_interval_number(data->rpi);
297 }
298
299 /**
300 * Sets the Tracing Key for the device in binary format.
301 *
302 * When first configuring a system, the Tracing Key must be generated
303 * randomly, e.g. using contrac_generate_tracing_key().
304 *
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.
307 *
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.
310 *
311 * @param data The context object to work with.
312 * @param tracing_key The Tracing Key to set in binary format.
313 */
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;
317 }
318
319 /**
320 * Gets the Tracing Key for the device in binary format.
321 *
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.
325 *
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
330 * the pointer to it.
331 *
332 * @param data The context object to work with.
333 * @return The Tracing Key in binary format, not null terminated.
334 */
335 unsigned char const * contrac_get_tracing_key(Contrac const * data) {
336 return data->tk;
337 }
338
339 /**
340 * Gets the Tracing Key for the device in base64 format.
341 *
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.
345 *
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).
349 *
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.
352 */
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);
356
357 if (size != (TK_SIZE_BASE64 + 1)) {
358 LOG(LOG_ERR, "Base64 tracing key has incorrect size of %d bytes.\n", size);
359 }
360 }
361
362 /**
363 * Sets the Tracing Key for the device in base64 format.
364 *
365 * When first configuring a system, the Tracing Key must be generated
366 * randomly, e.g. using contrac_generate_tracing_key().
367 *
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.
370 *
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
373 * be.
374 *
375 * @param data The context object to work with.
376 * @param tracing_key The Tracing Key to set in base64 format.
377 */
378 bool contrac_set_tracing_key_base64(Contrac * data, char const * tracing_key) {
379 bool result = true;
380 unsigned char tk[TK_SIZE];
381 size_t size;
382
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);
385 result = false;
386 }
387
388 if (result) {
389 size = TK_SIZE;
390 base64_decode_base64_to_binary((unsigned char *)tracing_key, TK_SIZE_BASE64, tk, &size);
391
392 if (size < TK_SIZE) {
393 LOG(LOG_ERR, "Base64 tracking key output is too short %d bytes.\n", size);
394 result = false;
395 }
396 }
397
398 if (result) {
399 contrac_set_tracing_key(data, tk);
400 }
401
402 return result;
403 }
404
405 /**
406 * Gets the Daily Tracing Key for the device in binary format.
407 *
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.
412 *
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
417 * the pointer to it.
418 *
419 * @param data The context object to work with.
420 * @return The Daily Tracing Key in binary format, not null terminated.
421 */
422 unsigned char const * contrac_get_daily_key(Contrac const * data) {
423 return dtk_get_daily_key(data->dtk);
424 }
425
426 /**
427 * Gets the Daily Tracing Key for the device in base64 format.
428 *
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.
433 *
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).
437 *
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.
440 */
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);
444
445 if (size != (DTK_SIZE_BASE64 + 1)) {
446 LOG(LOG_ERR, "Base64 daily key has incorrect size of %d bytes.\n", size);
447 }
448 }
449
450 /**
451 * Gets the Rolling Proximity Identifier for the device in binary format.
452 *
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
455 * frequently.
456 *
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
461 * the pointer to it.
462 *
463 * @param data The context object to work with.
464 * @return The Rolling Proximity Identifier in binary format, not null
465 * terminated.
466 */
467 unsigned char const * contrac_get_proximity_id(Contrac const * data) {
468 return rpi_get_proximity_id(data->rpi);
469 }
470
471 /**
472 * Gets the Rolling Proximity Identifier for the device in base64 format.
473 *
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
476 * frequently.
477 *
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).
481 *
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.
484 */
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);
488
489 if (size != (RPI_SIZE_BASE64 + 1)) {
490 LOG(LOG_ERR, "Base64 proximity id has incorrect size of %d bytes.\n", size);
491 }
492 }
493
494 /**
495 * Updates the Daily Tracing Key and Random Proxmity Identifer.
496 *
497 * The Daily Tracing Key changes every day, the Random Proximity Identifier
498 * changes every 10 minutes.
499 *
500 * Calling this function will update them both based on the current system
501 * time.
502 *
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.
506 *
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).
509 *
510 * @param data The context object to work with.
511 * @return true if the operation completed successfully, false otherwise.
512 */
513 bool contrac_update_current_time(Contrac * data) {
514 bool result;
515 time_t epoch;
516 uint32_t dn_stored;
517 uint32_t dn_now;
518 uint8_t tn_stored;
519 uint8_t tn_now;
520
521 result = true;
522
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);
526 }
527
528 epoch = time(NULL);
529
530 if (result) {
531 dn_now = epoch_to_day_number(epoch);
532 dn_stored = dtk_get_day_number(data->dtk);
533
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);
537 }
538 }
539
540 if (result) {
541 tn_now = epoch_to_time_interval_number(epoch);
542 tn_stored= rpi_get_time_interval_number(data->rpi);
543
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);
547 }
548 }
549
550 return result;
551 }
552
553 /** @} addtogroup KeyGeneration */
554