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