Add documentation for Contrac and Dtk classes.
[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 * Inernal flag mask.
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 * Inernal flag mask.
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 * Inernal flag mask.
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 * Inernal flag mask.
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 * Create 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 * Delete 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 * Generate 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 * Set 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 caluclated 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 * @return true if the operation completed successfully, false otherwise.
181 */
182 bool contrac_set_day_number(Contrac * data, uint32_t day_number) {
183 bool result;
184
185 result = ((data->status & STATUS_TK) != 0);
186
187 if (result) {
188 result = dtk_generate_daily_key(data->dtk, data, day_number);
189 }
190
191 if (result) {
192 data->status |= STATUS_DTK;
193 }
194
195 return result;
196 }
197
198 /**
199 * Set the current time interval number.
200 *
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.
204 *
205 * The time interval number is calculated as:
206 * (Seconds Since Start of DayNumber) / (60 * 10)
207 *
208 * and must fall in the interval [0, 143].
209 *
210 * It can be caluclated from the current epoch using the
211 * epoch_to_time_interval_number() function.
212 *
213 * The operation may fail if a Tracing Key or Daily Tracing Key have yet to be
214 * configured.
215 *
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.
219 */
220 bool contrac_set_time_interval_number(Contrac * data, uint8_t time_interval_number) {
221 bool result;
222
223 result = ((data->status & STATUS_DTK) != 0);
224
225 if (result) {
226 result = rpi_generate_proximity_id(data->rpi, data->dtk, time_interval_number);
227 }
228
229 if (result) {
230 data->status |= STATUS_RPI;
231 }
232
233 return result;
234 }
235
236 /**
237 * Get whether the internal state has been fully configured or not.
238 *
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.
242 *
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().
246 *
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.
249 *
250 * Alternatively these can be set automatically based on the current time using
251 * contrac_update_current_time().
252 *
253 * @param data The context object to work with.
254 * @return true if the state is fully initialised, false otherwise.
255 */
256 bool contrac_get_initialised(Contrac const * data) {
257 return ((data->status & STATUS_INITIALISED) == STATUS_INITIALISED);
258 }
259
260 /**
261 * Set the Tracing Key for the device in binary format.
262 *
263 * When first configuring a system, the Tracing Key must be generated
264 * randomly, e.g. using contrac_generate_tracing_key().
265 *
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.
268 *
269 * The tracing_key buffer passed in must contain exactly TK_SIZE (32) bytes of
270 * data.
271 *
272 * @param data The context object to work with.
273 * @param tracing_key The Tracing Key to set in binary format.
274 */
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;
278 }
279
280 /**
281 * Get the Tracing Key for the device in binary format.
282 *
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.
286 *
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.
290 *
291 * @param data The context object to work with.
292 * @return The Tracing Key in binary format, not null terminated.
293 */
294 unsigned char const * contrac_get_tracing_key(Contrac const * data) {
295 return data->tk;
296 }
297
298 /**
299 * Get the Tracing Key for the device in base64 format.
300 *
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.
304 *
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).
308 *
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.
311 */
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);
315
316 if (size != (TK_SIZE_BASE64 + 1)) {
317 LOG(LOG_ERR, "Base64 tracing key has incorrect size of %d bytes.\n", size);
318 }
319 }
320
321 /**
322 * Set the Tracing Key for the device in base64 format.
323 *
324 * When first configuring a system, the Tracing Key must be generated
325 * randomly, e.g. using contrac_generate_tracing_key().
326 *
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.
329 *
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
332 * be.
333 *
334 * @param data The context object to work with.
335 * @param tracing_key The Tracing Key to set in base64 format.
336 */
337 bool contrac_set_tracing_key_base64(Contrac * data, char const * tracing_key) {
338 bool result = true;
339 unsigned char tk[TK_SIZE];
340 size_t size;
341
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);
344 result = false;
345 }
346
347 if (result) {
348 size = TK_SIZE;
349 base64_decode_base64_to_binary((unsigned char *)tracing_key, TK_SIZE_BASE64, tk, &size);
350
351 if (size < TK_SIZE) {
352 LOG(LOG_ERR, "Base64 tracking key output is too short %d bytes.\n", size);
353 result = false;
354 }
355 }
356
357 if (result) {
358 contrac_set_tracing_key(data, tk);
359 }
360
361 return result;
362 }
363
364 /**
365 * Get the Daily Tracing Key for the device in binary format.
366 *
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.
371 *
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.
375 *
376 * @param data The context object to work with.
377 * @return The Daily Tracing Key in binary format, not null terminated.
378 */
379 unsigned char const * contrac_get_daily_key(Contrac const * data) {
380 return dtk_get_daily_key(data->dtk);
381 }
382
383 /**
384 * Get the Daily Tracing Key for the device in base64 format.
385 *
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.
390 *
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).
394 *
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.
397 */
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);
401
402 if (size != (DTK_SIZE_BASE64 + 1)) {
403 LOG(LOG_ERR, "Base64 daily key has incorrect size of %d bytes.\n", size);
404 }
405 }
406
407 /**
408 * Get the Rolling Proximity Identifier for the device in binary format.
409 *
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
412 * frequently.
413 *
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.
417 *
418 * @param data The context object to work with.
419 * @return The Rolling Proximity Identifier in binary format, not null
420 * terminated.
421 */
422 unsigned char const * contrac_get_proximity_id(Contrac const * data) {
423 return rpi_get_proximity_id(data->rpi);
424 }
425
426 /**
427 * Get the Rolling Proximity Identifier for the device in base64 format.
428 *
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
431 * frequently.
432 *
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).
436 *
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.
439 */
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);
443
444 if (size != (RPI_SIZE_BASE64 + 1)) {
445 LOG(LOG_ERR, "Base64 proximity id has incorrect size of %d bytes.\n", size);
446 }
447 }
448
449 /**
450 * Update the Daily Tracing Key and Random Proxmity Identifer.
451 *
452 * The Daily Tracing Key changes every day, the Random Proximity Identifier
453 * changes every 10 minutes.
454 *
455 * Calling this function will update them both based on the current system
456 * time.
457 *
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.
461 *
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).
464 *
465 * @param data The context object to work with.
466 * @return true if the operation completed successfully, false otherwise.
467 */
468 bool contrac_update_current_time(Contrac * data) {
469 bool result;
470 time_t epoch;
471 uint32_t dn_stored;
472 uint32_t dn_now;
473 uint8_t tn_stored;
474 uint8_t tn_now;
475
476 result = true;
477
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);
481 }
482
483 epoch = time(NULL);
484
485 if (result) {
486 dn_now = epoch_to_day_number(epoch);
487 dn_stored = dtk_get_day_number(data->dtk);
488
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);
492 }
493 }
494
495 if (result) {
496 tn_now = epoch_to_time_interval_number(epoch);
497 tn_stored= rpi_get_time_interval_number(data->rpi);
498
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);
502 }
503 }
504
505 return result;
506 }
507
508 /** @} addtogroup KeyGeneration */
509