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