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