Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <fcntl.h>
4 : : #include <sys/mman.h>
5 : :
6 : : #include "fd-util.h"
7 : : #include "fsprg.h"
8 : : #include "gcrypt-util.h"
9 : : #include "hexdecoct.h"
10 : : #include "journal-authenticate.h"
11 : : #include "journal-def.h"
12 : : #include "journal-file.h"
13 : : #include "memory-util.h"
14 : : #include "time-util.h"
15 : :
16 : 0 : static uint64_t journal_file_tag_seqnum(JournalFile *f) {
17 : : uint64_t r;
18 : :
19 [ # # ]: 0 : assert(f);
20 : :
21 : 0 : r = le64toh(f->header->n_tags) + 1;
22 : 0 : f->header->n_tags = htole64(r);
23 : :
24 : 0 : return r;
25 : : }
26 : :
27 : 32 : int journal_file_append_tag(JournalFile *f) {
28 : : Object *o;
29 : : uint64_t p;
30 : : int r;
31 : :
32 [ - + ]: 32 : assert(f);
33 : :
34 [ + - ]: 32 : if (!f->seal)
35 : 32 : return 0;
36 : :
37 [ # # ]: 0 : if (!f->hmac_running)
38 : 0 : return 0;
39 : :
40 [ # # ]: 0 : assert(f->hmac);
41 : :
42 : 0 : r = journal_file_append_object(f, OBJECT_TAG, sizeof(struct TagObject), &o, &p);
43 [ # # ]: 0 : if (r < 0)
44 : 0 : return r;
45 : :
46 : 0 : o->tag.seqnum = htole64(journal_file_tag_seqnum(f));
47 : 0 : o->tag.epoch = htole64(FSPRG_GetEpoch(f->fsprg_state));
48 : :
49 [ # # ]: 0 : log_debug("Writing tag %"PRIu64" for epoch %"PRIu64"",
50 : : le64toh(o->tag.seqnum),
51 : : FSPRG_GetEpoch(f->fsprg_state));
52 : :
53 : : /* Add the tag object itself, so that we can protect its
54 : : * header. This will exclude the actual hash value in it */
55 : 0 : r = journal_file_hmac_put_object(f, OBJECT_TAG, o, p);
56 [ # # ]: 0 : if (r < 0)
57 : 0 : return r;
58 : :
59 : : /* Get the HMAC tag and store it in the object */
60 : 0 : memcpy(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH);
61 : 0 : f->hmac_running = false;
62 : :
63 : 0 : return 0;
64 : : }
65 : :
66 : 0 : int journal_file_hmac_start(JournalFile *f) {
67 : : uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */
68 [ # # ]: 0 : assert(f);
69 : :
70 [ # # ]: 0 : if (!f->seal)
71 : 0 : return 0;
72 : :
73 [ # # ]: 0 : if (f->hmac_running)
74 : 0 : return 0;
75 : :
76 : : /* Prepare HMAC for next cycle */
77 : 0 : gcry_md_reset(f->hmac);
78 : 0 : FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0);
79 : 0 : gcry_md_setkey(f->hmac, key, sizeof(key));
80 : :
81 : 0 : f->hmac_running = true;
82 : :
83 : 0 : return 0;
84 : : }
85 : :
86 : 0 : static int journal_file_get_epoch(JournalFile *f, uint64_t realtime, uint64_t *epoch) {
87 : : uint64_t t;
88 : :
89 [ # # ]: 0 : assert(f);
90 [ # # ]: 0 : assert(epoch);
91 [ # # ]: 0 : assert(f->seal);
92 : :
93 [ # # ]: 0 : if (f->fss_start_usec == 0 ||
94 [ # # ]: 0 : f->fss_interval_usec == 0)
95 : 0 : return -EOPNOTSUPP;
96 : :
97 [ # # ]: 0 : if (realtime < f->fss_start_usec)
98 : 0 : return -ESTALE;
99 : :
100 : 0 : t = realtime - f->fss_start_usec;
101 : 0 : t = t / f->fss_interval_usec;
102 : :
103 : 0 : *epoch = t;
104 : 0 : return 0;
105 : : }
106 : :
107 : 0 : static int journal_file_fsprg_need_evolve(JournalFile *f, uint64_t realtime) {
108 : : uint64_t goal, epoch;
109 : : int r;
110 [ # # ]: 0 : assert(f);
111 : :
112 [ # # ]: 0 : if (!f->seal)
113 : 0 : return 0;
114 : :
115 : 0 : r = journal_file_get_epoch(f, realtime, &goal);
116 [ # # ]: 0 : if (r < 0)
117 : 0 : return r;
118 : :
119 : 0 : epoch = FSPRG_GetEpoch(f->fsprg_state);
120 [ # # ]: 0 : if (epoch > goal)
121 : 0 : return -ESTALE;
122 : :
123 : 0 : return epoch != goal;
124 : : }
125 : :
126 : 0 : int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) {
127 : : uint64_t goal, epoch;
128 : : int r;
129 : :
130 [ # # ]: 0 : assert(f);
131 : :
132 [ # # ]: 0 : if (!f->seal)
133 : 0 : return 0;
134 : :
135 : 0 : r = journal_file_get_epoch(f, realtime, &goal);
136 [ # # ]: 0 : if (r < 0)
137 : 0 : return r;
138 : :
139 : 0 : epoch = FSPRG_GetEpoch(f->fsprg_state);
140 [ # # ]: 0 : if (epoch < goal)
141 [ # # ]: 0 : log_debug("Evolving FSPRG key from epoch %"PRIu64" to %"PRIu64".", epoch, goal);
142 : :
143 : : for (;;) {
144 [ # # ]: 0 : if (epoch > goal)
145 : 0 : return -ESTALE;
146 [ # # ]: 0 : if (epoch == goal)
147 : 0 : return 0;
148 : :
149 : 0 : FSPRG_Evolve(f->fsprg_state);
150 : 0 : epoch = FSPRG_GetEpoch(f->fsprg_state);
151 : : }
152 : : }
153 : :
154 : 0 : int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) {
155 : : void *msk;
156 : : uint64_t epoch;
157 : :
158 [ # # ]: 0 : assert(f);
159 : :
160 [ # # ]: 0 : if (!f->seal)
161 : 0 : return 0;
162 : :
163 [ # # ]: 0 : assert(f->fsprg_seed);
164 : :
165 [ # # ]: 0 : if (f->fsprg_state) {
166 : : /* Cheaper... */
167 : :
168 : 0 : epoch = FSPRG_GetEpoch(f->fsprg_state);
169 [ # # ]: 0 : if (goal == epoch)
170 : 0 : return 0;
171 : :
172 [ # # ]: 0 : if (goal == epoch+1) {
173 : 0 : FSPRG_Evolve(f->fsprg_state);
174 : 0 : return 0;
175 : : }
176 : : } else {
177 : 0 : f->fsprg_state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
178 : 0 : f->fsprg_state = malloc(f->fsprg_state_size);
179 : :
180 [ # # ]: 0 : if (!f->fsprg_state)
181 : 0 : return -ENOMEM;
182 : : }
183 : :
184 [ # # ]: 0 : log_debug("Seeking FSPRG key to %"PRIu64".", goal);
185 : :
186 : 0 : msk = alloca(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR));
187 : 0 : FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR);
188 : 0 : FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size);
189 : 0 : return 0;
190 : : }
191 : :
192 : 25140 : int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) {
193 : : int r;
194 : :
195 [ - + ]: 25140 : assert(f);
196 : :
197 [ + - ]: 25140 : if (!f->seal)
198 : 25140 : return 0;
199 : :
200 [ # # ]: 0 : if (realtime <= 0)
201 : 0 : realtime = now(CLOCK_REALTIME);
202 : :
203 : 0 : r = journal_file_fsprg_need_evolve(f, realtime);
204 [ # # ]: 0 : if (r <= 0)
205 : 0 : return 0;
206 : :
207 : 0 : r = journal_file_append_tag(f);
208 [ # # ]: 0 : if (r < 0)
209 : 0 : return r;
210 : :
211 : 0 : r = journal_file_fsprg_evolve(f, realtime);
212 [ # # ]: 0 : if (r < 0)
213 : 0 : return r;
214 : :
215 : 0 : return 0;
216 : : }
217 : :
218 : 204220 : int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p) {
219 : : int r;
220 : :
221 [ - + ]: 204220 : assert(f);
222 : :
223 [ + - ]: 204220 : if (!f->seal)
224 : 204220 : return 0;
225 : :
226 : 0 : r = journal_file_hmac_start(f);
227 [ # # ]: 0 : if (r < 0)
228 : 0 : return r;
229 : :
230 [ # # ]: 0 : if (!o) {
231 : 0 : r = journal_file_move_to_object(f, type, p, &o);
232 [ # # ]: 0 : if (r < 0)
233 : 0 : return r;
234 : : } else {
235 [ # # # # ]: 0 : if (type > OBJECT_UNUSED && o->object.type != type)
236 : 0 : return -EBADMSG;
237 : : }
238 : :
239 : 0 : gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload));
240 : :
241 [ # # # # : 0 : switch (o->object.type) {
# # ]
242 : :
243 : 0 : case OBJECT_DATA:
244 : : /* All but hash and payload are mutable */
245 : 0 : gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash));
246 : 0 : gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(DataObject, payload));
247 : 0 : break;
248 : :
249 : 0 : case OBJECT_FIELD:
250 : : /* Same here */
251 : 0 : gcry_md_write(f->hmac, &o->field.hash, sizeof(o->field.hash));
252 : 0 : gcry_md_write(f->hmac, o->field.payload, le64toh(o->object.size) - offsetof(FieldObject, payload));
253 : 0 : break;
254 : :
255 : 0 : case OBJECT_ENTRY:
256 : : /* All */
257 : 0 : gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(EntryObject, seqnum));
258 : 0 : break;
259 : :
260 : 0 : case OBJECT_FIELD_HASH_TABLE:
261 : : case OBJECT_DATA_HASH_TABLE:
262 : : case OBJECT_ENTRY_ARRAY:
263 : : /* Nothing: everything is mutable */
264 : 0 : break;
265 : :
266 : 0 : case OBJECT_TAG:
267 : : /* All but the tag itself */
268 : 0 : gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum));
269 : 0 : gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch));
270 : 0 : break;
271 : 0 : default:
272 : 0 : return -EINVAL;
273 : : }
274 : :
275 : 0 : return 0;
276 : : }
277 : :
278 : 0 : int journal_file_hmac_put_header(JournalFile *f) {
279 : : int r;
280 : :
281 [ # # ]: 0 : assert(f);
282 : :
283 [ # # ]: 0 : if (!f->seal)
284 : 0 : return 0;
285 : :
286 : 0 : r = journal_file_hmac_start(f);
287 [ # # ]: 0 : if (r < 0)
288 : 0 : return r;
289 : :
290 : : /* All but state+reserved, boot_id, arena_size,
291 : : * tail_object_offset, n_objects, n_entries,
292 : : * tail_entry_seqnum, head_entry_seqnum, entry_array_offset,
293 : : * head_entry_realtime, tail_entry_realtime,
294 : : * tail_entry_monotonic, n_data, n_fields, n_tags,
295 : : * n_entry_arrays. */
296 : :
297 : 0 : gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature));
298 : 0 : gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, boot_id) - offsetof(Header, file_id));
299 : 0 : gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id));
300 : 0 : gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset));
301 : :
302 : 0 : return 0;
303 : : }
304 : :
305 : 52 : int journal_file_fss_load(JournalFile *f) {
306 : 52 : int r, fd = -1;
307 : 52 : char *p = NULL;
308 : : struct stat st;
309 : 52 : FSSHeader *m = NULL;
310 : : sd_id128_t machine;
311 : :
312 [ - + ]: 52 : assert(f);
313 : :
314 [ + + ]: 52 : if (!f->seal)
315 : 4 : return 0;
316 : :
317 : 48 : r = sd_id128_get_machine(&machine);
318 [ - + ]: 48 : if (r < 0)
319 : 0 : return r;
320 : :
321 [ - + ]: 48 : if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
322 : 48 : SD_ID128_FORMAT_VAL(machine)) < 0)
323 : 0 : return -ENOMEM;
324 : :
325 : 48 : fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
326 [ + - ]: 48 : if (fd < 0) {
327 [ - + ]: 48 : if (errno != ENOENT)
328 [ # # ]: 0 : log_error_errno(errno, "Failed to open %s: %m", p);
329 : :
330 : 48 : r = -errno;
331 : 48 : goto finish;
332 : : }
333 : :
334 [ # # ]: 0 : if (fstat(fd, &st) < 0) {
335 : 0 : r = -errno;
336 : 0 : goto finish;
337 : : }
338 : :
339 [ # # ]: 0 : if (st.st_size < (off_t) sizeof(FSSHeader)) {
340 : 0 : r = -ENODATA;
341 : 0 : goto finish;
342 : : }
343 : :
344 : 0 : m = mmap(NULL, PAGE_ALIGN(sizeof(FSSHeader)), PROT_READ, MAP_SHARED, fd, 0);
345 [ # # ]: 0 : if (m == MAP_FAILED) {
346 : 0 : m = NULL;
347 : 0 : r = -errno;
348 : 0 : goto finish;
349 : : }
350 : :
351 [ # # ]: 0 : if (memcmp(m->signature, FSS_HEADER_SIGNATURE, 8) != 0) {
352 : 0 : r = -EBADMSG;
353 : 0 : goto finish;
354 : : }
355 : :
356 [ # # ]: 0 : if (m->incompatible_flags != 0) {
357 : 0 : r = -EPROTONOSUPPORT;
358 : 0 : goto finish;
359 : : }
360 : :
361 [ # # ]: 0 : if (le64toh(m->header_size) < sizeof(FSSHeader)) {
362 : 0 : r = -EBADMSG;
363 : 0 : goto finish;
364 : : }
365 : :
366 [ # # ]: 0 : if (le64toh(m->fsprg_state_size) != FSPRG_stateinbytes(le16toh(m->fsprg_secpar))) {
367 : 0 : r = -EBADMSG;
368 : 0 : goto finish;
369 : : }
370 : :
371 : 0 : f->fss_file_size = le64toh(m->header_size) + le64toh(m->fsprg_state_size);
372 [ # # ]: 0 : if ((uint64_t) st.st_size < f->fss_file_size) {
373 : 0 : r = -ENODATA;
374 : 0 : goto finish;
375 : : }
376 : :
377 [ # # ]: 0 : if (!sd_id128_equal(machine, m->machine_id)) {
378 : 0 : r = -EHOSTDOWN;
379 : 0 : goto finish;
380 : : }
381 : :
382 [ # # # # ]: 0 : if (le64toh(m->start_usec) <= 0 ||
383 : 0 : le64toh(m->interval_usec) <= 0) {
384 : 0 : r = -EBADMSG;
385 : 0 : goto finish;
386 : : }
387 : :
388 : 0 : f->fss_file = mmap(NULL, PAGE_ALIGN(f->fss_file_size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
389 [ # # ]: 0 : if (f->fss_file == MAP_FAILED) {
390 : 0 : f->fss_file = NULL;
391 : 0 : r = -errno;
392 : 0 : goto finish;
393 : : }
394 : :
395 : 0 : f->fss_start_usec = le64toh(f->fss_file->start_usec);
396 : 0 : f->fss_interval_usec = le64toh(f->fss_file->interval_usec);
397 : :
398 : 0 : f->fsprg_state = (uint8_t*) f->fss_file + le64toh(f->fss_file->header_size);
399 : 0 : f->fsprg_state_size = le64toh(f->fss_file->fsprg_state_size);
400 : :
401 : 0 : r = 0;
402 : :
403 : 48 : finish:
404 [ - + ]: 48 : if (m)
405 : 0 : munmap(m, PAGE_ALIGN(sizeof(FSSHeader)));
406 : :
407 : 48 : safe_close(fd);
408 : 48 : free(p);
409 : :
410 : 48 : return r;
411 : : }
412 : :
413 : 39372 : int journal_file_hmac_setup(JournalFile *f) {
414 : : gcry_error_t e;
415 : :
416 [ + - ]: 39372 : if (!f->seal)
417 : 39372 : return 0;
418 : :
419 : 0 : initialize_libgcrypt(true);
420 : :
421 : 0 : e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
422 [ # # ]: 0 : if (e != 0)
423 : 0 : return -EOPNOTSUPP;
424 : :
425 : 0 : return 0;
426 : : }
427 : :
428 : 100 : int journal_file_append_first_tag(JournalFile *f) {
429 : : int r;
430 : : uint64_t p;
431 : :
432 [ + - ]: 100 : if (!f->seal)
433 : 100 : return 0;
434 : :
435 [ # # ]: 0 : log_debug("Calculating first tag...");
436 : :
437 : 0 : r = journal_file_hmac_put_header(f);
438 [ # # ]: 0 : if (r < 0)
439 : 0 : return r;
440 : :
441 : 0 : p = le64toh(f->header->field_hash_table_offset);
442 [ # # ]: 0 : if (p < offsetof(Object, hash_table.items))
443 : 0 : return -EINVAL;
444 : 0 : p -= offsetof(Object, hash_table.items);
445 : :
446 : 0 : r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, NULL, p);
447 [ # # ]: 0 : if (r < 0)
448 : 0 : return r;
449 : :
450 : 0 : p = le64toh(f->header->data_hash_table_offset);
451 [ # # ]: 0 : if (p < offsetof(Object, hash_table.items))
452 : 0 : return -EINVAL;
453 : 0 : p -= offsetof(Object, hash_table.items);
454 : :
455 : 0 : r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, NULL, p);
456 [ # # ]: 0 : if (r < 0)
457 : 0 : return r;
458 : :
459 : 0 : r = journal_file_append_tag(f);
460 [ # # ]: 0 : if (r < 0)
461 : 0 : return r;
462 : :
463 : 0 : return 0;
464 : : }
465 : :
466 : 0 : int journal_file_parse_verification_key(JournalFile *f, const char *key) {
467 : : uint8_t *seed;
468 : : size_t seed_size, c;
469 : : const char *k;
470 : : int r;
471 : : unsigned long long start, interval;
472 : :
473 : 0 : seed_size = FSPRG_RECOMMENDED_SEEDLEN;
474 : 0 : seed = malloc(seed_size);
475 [ # # ]: 0 : if (!seed)
476 : 0 : return -ENOMEM;
477 : :
478 : 0 : k = key;
479 [ # # ]: 0 : for (c = 0; c < seed_size; c++) {
480 : : int x, y;
481 : :
482 [ # # ]: 0 : while (*k == '-')
483 : 0 : k++;
484 : :
485 : 0 : x = unhexchar(*k);
486 [ # # ]: 0 : if (x < 0) {
487 : 0 : free(seed);
488 : 0 : return -EINVAL;
489 : : }
490 : 0 : k++;
491 : 0 : y = unhexchar(*k);
492 [ # # ]: 0 : if (y < 0) {
493 : 0 : free(seed);
494 : 0 : return -EINVAL;
495 : : }
496 : 0 : k++;
497 : :
498 : 0 : seed[c] = (uint8_t) (x * 16 + y);
499 : : }
500 : :
501 [ # # ]: 0 : if (*k != '/') {
502 : 0 : free(seed);
503 : 0 : return -EINVAL;
504 : : }
505 : 0 : k++;
506 : :
507 : 0 : r = sscanf(k, "%llx-%llx", &start, &interval);
508 [ # # ]: 0 : if (r != 2) {
509 : 0 : free(seed);
510 : 0 : return -EINVAL;
511 : : }
512 : :
513 : 0 : f->fsprg_seed = seed;
514 : 0 : f->fsprg_seed_size = seed_size;
515 : :
516 : 0 : f->fss_start_usec = start * interval;
517 : 0 : f->fss_interval_usec = interval;
518 : :
519 : 0 : return 0;
520 : : }
521 : :
522 : 0 : bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u) {
523 : : uint64_t epoch;
524 : :
525 [ # # ]: 0 : assert(f);
526 [ # # ]: 0 : assert(u);
527 : :
528 [ # # ]: 0 : if (!f->seal)
529 : 0 : return false;
530 : :
531 : 0 : epoch = FSPRG_GetEpoch(f->fsprg_state);
532 : :
533 : 0 : *u = (usec_t) (f->fss_start_usec + f->fss_interval_usec * epoch + f->fss_interval_usec);
534 : :
535 : 0 : return true;
536 : : }
|