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 8 : int journal_file_append_tag(JournalFile *f) {
28 : Object *o;
29 : uint64_t p;
30 : int r;
31 :
32 8 : assert(f);
33 :
34 8 : if (!f->seal)
35 8 : 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 6285 : int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) {
193 : int r;
194 :
195 6285 : assert(f);
196 :
197 6285 : if (!f->seal)
198 6285 : 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 51055 : int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p) {
219 : int r;
220 :
221 51055 : assert(f);
222 :
223 51055 : if (!f->seal)
224 51055 : 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 13 : int journal_file_fss_load(JournalFile *f) {
306 13 : int r, fd = -1;
307 13 : char *p = NULL;
308 : struct stat st;
309 13 : FSSHeader *m = NULL;
310 : sd_id128_t machine;
311 :
312 13 : assert(f);
313 :
314 13 : if (!f->seal)
315 1 : return 0;
316 :
317 12 : r = sd_id128_get_machine(&machine);
318 12 : if (r < 0)
319 0 : return r;
320 :
321 12 : if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
322 12 : SD_ID128_FORMAT_VAL(machine)) < 0)
323 0 : return -ENOMEM;
324 :
325 12 : fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
326 12 : if (fd < 0) {
327 12 : if (errno != ENOENT)
328 0 : log_error_errno(errno, "Failed to open %s: %m", p);
329 :
330 12 : r = -errno;
331 12 : 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 12 : finish:
404 12 : if (m)
405 0 : munmap(m, PAGE_ALIGN(sizeof(FSSHeader)));
406 :
407 12 : safe_close(fd);
408 12 : free(p);
409 :
410 12 : return r;
411 : }
412 :
413 9843 : int journal_file_hmac_setup(JournalFile *f) {
414 : gcry_error_t e;
415 :
416 9843 : if (!f->seal)
417 9843 : 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 25 : int journal_file_append_first_tag(JournalFile *f) {
429 : int r;
430 : uint64_t p;
431 :
432 25 : if (!f->seal)
433 25 : 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 : }
|