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