]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
0284adc6 LP |
2 | |
3 | #include <fcntl.h> | |
4 | #include <sys/mman.h> | |
5 | ||
3ffd4af2 LP |
6 | #include "fd-util.h" |
7 | #include "fsprg.h" | |
91e023d8 | 8 | #include "gcrypt-util.h" |
cf0fbc49 | 9 | #include "hexdecoct.h" |
3ffd4af2 | 10 | #include "journal-authenticate.h" |
0284adc6 LP |
11 | #include "journal-def.h" |
12 | #include "journal-file.h" | |
0284adc6 | 13 | |
0284adc6 LP |
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 | ||
baed47c3 | 32 | if (!f->seal) |
0284adc6 LP |
33 | return 0; |
34 | ||
35 | if (!f->hmac_running) | |
36 | return 0; | |
37 | ||
0284adc6 LP |
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)); | |
14d10188 | 45 | o->tag.epoch = htole64(FSPRG_GetEpoch(f->fsprg_state)); |
0284adc6 | 46 | |
9f6445e3 | 47 | log_debug("Writing tag %"PRIu64" for epoch %"PRIu64"", |
507f22bd ZJS |
48 | le64toh(o->tag.seqnum), |
49 | FSPRG_GetEpoch(f->fsprg_state)); | |
e627440b | 50 | |
0284adc6 LP |
51 | /* Add the tag object itself, so that we can protect its |
52 | * header. This will exclude the actual hash value in it */ | |
5996c7c2 | 53 | r = journal_file_hmac_put_object(f, OBJECT_TAG, o, p); |
0284adc6 LP |
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 | ||
14d10188 | 64 | int journal_file_hmac_start(JournalFile *f) { |
0284adc6 | 65 | uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */ |
0284adc6 LP |
66 | assert(f); |
67 | ||
baed47c3 | 68 | if (!f->seal) |
0284adc6 LP |
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); | |
b7c9ae91 | 76 | FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0); |
0284adc6 LP |
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); | |
baed47c3 | 89 | assert(f->seal); |
0284adc6 | 90 | |
baed47c3 LP |
91 | if (f->fss_start_usec == 0 || |
92 | f->fss_interval_usec == 0) | |
15411c0c | 93 | return -EOPNOTSUPP; |
0284adc6 | 94 | |
baed47c3 | 95 | if (realtime < f->fss_start_usec) |
0284adc6 LP |
96 | return -ESTALE; |
97 | ||
baed47c3 LP |
98 | t = realtime - f->fss_start_usec; |
99 | t = t / f->fss_interval_usec; | |
0284adc6 LP |
100 | |
101 | *epoch = t; | |
102 | return 0; | |
103 | } | |
104 | ||
baed47c3 | 105 | static int journal_file_fsprg_need_evolve(JournalFile *f, uint64_t realtime) { |
0284adc6 LP |
106 | uint64_t goal, epoch; |
107 | int r; | |
108 | assert(f); | |
109 | ||
baed47c3 | 110 | if (!f->seal) |
0284adc6 LP |
111 | return 0; |
112 | ||
113 | r = journal_file_get_epoch(f, realtime, &goal); | |
114 | if (r < 0) | |
115 | return r; | |
116 | ||
b7c9ae91 | 117 | epoch = FSPRG_GetEpoch(f->fsprg_state); |
0284adc6 LP |
118 | if (epoch > goal) |
119 | return -ESTALE; | |
120 | ||
121 | return epoch != goal; | |
122 | } | |
123 | ||
baed47c3 | 124 | int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) { |
0284adc6 LP |
125 | uint64_t goal, epoch; |
126 | int r; | |
127 | ||
128 | assert(f); | |
129 | ||
baed47c3 | 130 | if (!f->seal) |
0284adc6 LP |
131 | return 0; |
132 | ||
133 | r = journal_file_get_epoch(f, realtime, &goal); | |
134 | if (r < 0) | |
135 | return r; | |
136 | ||
b7c9ae91 | 137 | epoch = FSPRG_GetEpoch(f->fsprg_state); |
0284adc6 | 138 | if (epoch < goal) |
507f22bd | 139 | log_debug("Evolving FSPRG key from epoch %"PRIu64" to %"PRIu64".", epoch, goal); |
0284adc6 LP |
140 | |
141 | for (;;) { | |
142 | if (epoch > goal) | |
143 | return -ESTALE; | |
144 | if (epoch == goal) | |
145 | return 0; | |
146 | ||
b7c9ae91 LP |
147 | FSPRG_Evolve(f->fsprg_state); |
148 | epoch = FSPRG_GetEpoch(f->fsprg_state); | |
0284adc6 LP |
149 | } |
150 | } | |
151 | ||
14d10188 LP |
152 | int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { |
153 | void *msk; | |
154 | uint64_t epoch; | |
155 | ||
156 | assert(f); | |
157 | ||
baed47c3 | 158 | if (!f->seal) |
14d10188 LP |
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 | ||
507f22bd | 182 | log_debug("Seeking FSPRG key to %"PRIu64".", goal); |
14d10188 LP |
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 | ||
0284adc6 LP |
190 | int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) { |
191 | int r; | |
192 | ||
193 | assert(f); | |
194 | ||
baed47c3 | 195 | if (!f->seal) |
0284adc6 LP |
196 | return 0; |
197 | ||
89fef990 | 198 | if (realtime <= 0) |
671e021c | 199 | realtime = now(CLOCK_REALTIME); |
89fef990 | 200 | |
baed47c3 | 201 | r = journal_file_fsprg_need_evolve(f, realtime); |
0284adc6 LP |
202 | if (r <= 0) |
203 | return 0; | |
204 | ||
205 | r = journal_file_append_tag(f); | |
206 | if (r < 0) | |
207 | return r; | |
208 | ||
baed47c3 | 209 | r = journal_file_fsprg_evolve(f, realtime); |
0284adc6 LP |
210 | if (r < 0) |
211 | return r; | |
212 | ||
0284adc6 LP |
213 | return 0; |
214 | } | |
215 | ||
78519831 | 216 | int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p) { |
0284adc6 | 217 | int r; |
0284adc6 LP |
218 | |
219 | assert(f); | |
220 | ||
baed47c3 | 221 | if (!f->seal) |
0284adc6 LP |
222 | return 0; |
223 | ||
224 | r = journal_file_hmac_start(f); | |
225 | if (r < 0) | |
226 | return r; | |
227 | ||
5996c7c2 LP |
228 | if (!o) { |
229 | r = journal_file_move_to_object(f, type, p, &o); | |
230 | if (r < 0) | |
231 | return r; | |
232 | } else { | |
d05089d8 | 233 | if (type > OBJECT_UNUSED && o->object.type != type) |
5996c7c2 LP |
234 | return -EBADMSG; |
235 | } | |
0284adc6 LP |
236 | |
237 | gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload)); | |
238 | ||
239 | switch (o->object.type) { | |
240 | ||
241 | case OBJECT_DATA: | |
14d10188 | 242 | /* All but hash and payload are mutable */ |
0284adc6 LP |
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 | ||
3c1668da LP |
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 | ||
0284adc6 LP |
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)); | |
14d10188 | 267 | gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch)); |
0284adc6 LP |
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 | ||
baed47c3 | 281 | if (!f->seal) |
0284adc6 LP |
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, | |
14d10188 LP |
289 | * tail_object_offset, n_objects, n_entries, |
290 | * tail_entry_seqnum, head_entry_seqnum, entry_array_offset, | |
0284adc6 | 291 | * head_entry_realtime, tail_entry_realtime, |
14d10188 LP |
292 | * tail_entry_monotonic, n_data, n_fields, n_tags, |
293 | * n_entry_arrays. */ | |
0284adc6 LP |
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)); | |
0284adc6 LP |
299 | |
300 | return 0; | |
301 | } | |
302 | ||
baed47c3 | 303 | int journal_file_fss_load(JournalFile *f) { |
0284adc6 LP |
304 | int r, fd = -1; |
305 | char *p = NULL; | |
306 | struct stat st; | |
baed47c3 | 307 | FSSHeader *m = NULL; |
0284adc6 LP |
308 | sd_id128_t machine; |
309 | ||
310 | assert(f); | |
311 | ||
baed47c3 | 312 | if (!f->seal) |
0284adc6 LP |
313 | return 0; |
314 | ||
315 | r = sd_id128_get_machine(&machine); | |
316 | if (r < 0) | |
317 | return r; | |
318 | ||
baed47c3 | 319 | if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss", |
0284adc6 LP |
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) { | |
272410e1 | 325 | if (errno != ENOENT) |
56f64d95 | 326 | log_error_errno(errno, "Failed to open %s: %m", p); |
272410e1 | 327 | |
0284adc6 LP |
328 | r = -errno; |
329 | goto finish; | |
330 | } | |
331 | ||
332 | if (fstat(fd, &st) < 0) { | |
333 | r = -errno; | |
334 | goto finish; | |
335 | } | |
336 | ||
baed47c3 | 337 | if (st.st_size < (off_t) sizeof(FSSHeader)) { |
0284adc6 LP |
338 | r = -ENODATA; |
339 | goto finish; | |
340 | } | |
341 | ||
baed47c3 | 342 | m = mmap(NULL, PAGE_ALIGN(sizeof(FSSHeader)), PROT_READ, MAP_SHARED, fd, 0); |
0284adc6 LP |
343 | if (m == MAP_FAILED) { |
344 | m = NULL; | |
345 | r = -errno; | |
346 | goto finish; | |
347 | } | |
348 | ||
baed47c3 | 349 | if (memcmp(m->signature, FSS_HEADER_SIGNATURE, 8) != 0) { |
0284adc6 LP |
350 | r = -EBADMSG; |
351 | goto finish; | |
352 | } | |
353 | ||
354 | if (m->incompatible_flags != 0) { | |
355 | r = -EPROTONOSUPPORT; | |
356 | goto finish; | |
357 | } | |
358 | ||
baed47c3 | 359 | if (le64toh(m->header_size) < sizeof(FSSHeader)) { |
0284adc6 LP |
360 | r = -EBADMSG; |
361 | goto finish; | |
362 | } | |
363 | ||
3e4b9b50 | 364 | if (le64toh(m->fsprg_state_size) != FSPRG_stateinbytes(le16toh(m->fsprg_secpar))) { |
0284adc6 LP |
365 | r = -EBADMSG; |
366 | goto finish; | |
367 | } | |
368 | ||
baed47c3 LP |
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) { | |
0284adc6 LP |
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 | ||
baed47c3 LP |
380 | if (le64toh(m->start_usec) <= 0 || |
381 | le64toh(m->interval_usec) <= 0) { | |
0284adc6 LP |
382 | r = -EBADMSG; |
383 | goto finish; | |
384 | } | |
385 | ||
baed47c3 LP |
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; | |
0284adc6 LP |
389 | r = -errno; |
390 | goto finish; | |
391 | } | |
392 | ||
baed47c3 LP |
393 | f->fss_start_usec = le64toh(f->fss_file->start_usec); |
394 | f->fss_interval_usec = le64toh(f->fss_file->interval_usec); | |
b7c9ae91 | 395 | |
baed47c3 LP |
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); | |
b7c9ae91 | 398 | |
0284adc6 LP |
399 | r = 0; |
400 | ||
401 | finish: | |
402 | if (m) | |
baed47c3 | 403 | munmap(m, PAGE_ALIGN(sizeof(FSSHeader))); |
0284adc6 | 404 | |
03e334a1 | 405 | safe_close(fd); |
0284adc6 | 406 | free(p); |
03e334a1 | 407 | |
0284adc6 LP |
408 | return r; |
409 | } | |
410 | ||
baed47c3 | 411 | int journal_file_hmac_setup(JournalFile *f) { |
0284adc6 LP |
412 | gcry_error_t e; |
413 | ||
baed47c3 | 414 | if (!f->seal) |
0284adc6 LP |
415 | return 0; |
416 | ||
91e023d8 | 417 | initialize_libgcrypt(true); |
72fbdd33 | 418 | |
0284adc6 LP |
419 | e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); |
420 | if (e != 0) | |
15411c0c | 421 | return -EOPNOTSUPP; |
0284adc6 LP |
422 | |
423 | return 0; | |
424 | } | |
425 | ||
426 | int journal_file_append_first_tag(JournalFile *f) { | |
427 | int r; | |
428 | uint64_t p; | |
429 | ||
baed47c3 | 430 | if (!f->seal) |
0284adc6 LP |
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 | ||
5996c7c2 | 444 | r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, NULL, p); |
0284adc6 LP |
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 | ||
5996c7c2 | 453 | r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, NULL, p); |
0284adc6 LP |
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 | } | |
4da416aa | 463 | |
feb12d3e LP |
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; | |
4da416aa | 518 | } |
89fef990 LP |
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 | } |