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