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