chiark / gitweb /
593bf7eb2ac0d95fd568f7ac62bcff91357116cd
[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                 if (errno != ENOENT)
332                         log_error("Failed to open %s: %m", p);
333
334                 r = -errno;
335                 goto finish;
336         }
337
338         if (fstat(fd, &st) < 0) {
339                 r = -errno;
340                 goto finish;
341         }
342
343         if (st.st_size < (off_t) sizeof(FSSHeader)) {
344                 r = -ENODATA;
345                 goto finish;
346         }
347
348         m = mmap(NULL, PAGE_ALIGN(sizeof(FSSHeader)), PROT_READ, MAP_SHARED, fd, 0);
349         if (m == MAP_FAILED) {
350                 m = NULL;
351                 r = -errno;
352                 goto finish;
353         }
354
355         if (memcmp(m->signature, FSS_HEADER_SIGNATURE, 8) != 0) {
356                 r = -EBADMSG;
357                 goto finish;
358         }
359
360         if (m->incompatible_flags != 0) {
361                 r = -EPROTONOSUPPORT;
362                 goto finish;
363         }
364
365         if (le64toh(m->header_size) < sizeof(FSSHeader)) {
366                 r = -EBADMSG;
367                 goto finish;
368         }
369
370         if (le64toh(m->fsprg_state_size) != FSPRG_stateinbytes(le16toh(m->fsprg_secpar))) {
371                 r = -EBADMSG;
372                 goto finish;
373         }
374
375         f->fss_file_size = le64toh(m->header_size) + le64toh(m->fsprg_state_size);
376         if ((uint64_t) st.st_size < f->fss_file_size) {
377                 r = -ENODATA;
378                 goto finish;
379         }
380
381         if (!sd_id128_equal(machine, m->machine_id)) {
382                 r = -EHOSTDOWN;
383                 goto finish;
384         }
385
386         if (le64toh(m->start_usec) <= 0 ||
387             le64toh(m->interval_usec) <= 0) {
388                 r = -EBADMSG;
389                 goto finish;
390         }
391
392         f->fss_file = mmap(NULL, PAGE_ALIGN(f->fss_file_size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
393         if (f->fss_file == MAP_FAILED) {
394                 f->fss_file = NULL;
395                 r = -errno;
396                 goto finish;
397         }
398
399         f->fss_start_usec = le64toh(f->fss_file->start_usec);
400         f->fss_interval_usec = le64toh(f->fss_file->interval_usec);
401
402         f->fsprg_state = (uint8_t*) f->fss_file + le64toh(f->fss_file->header_size);
403         f->fsprg_state_size = le64toh(f->fss_file->fsprg_state_size);
404
405         r = 0;
406
407 finish:
408         if (m)
409                 munmap(m, PAGE_ALIGN(sizeof(FSSHeader)));
410
411         if (fd >= 0)
412                 close_nointr_nofail(fd);
413
414         free(p);
415         return r;
416 }
417
418 static void initialize_libgcrypt(void) {
419         const char *p;
420
421         if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
422                 return;
423
424         p = gcry_check_version("1.4.5");
425         assert(p);
426
427         gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
428 }
429
430 int journal_file_hmac_setup(JournalFile *f) {
431         gcry_error_t e;
432
433         if (!f->seal)
434                 return 0;
435
436         initialize_libgcrypt();
437
438         e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
439         if (e != 0)
440                 return -ENOTSUP;
441
442         return 0;
443 }
444
445 int journal_file_append_first_tag(JournalFile *f) {
446         int r;
447         uint64_t p;
448
449         if (!f->seal)
450                 return 0;
451
452         log_debug("Calculating first tag...");
453
454         r = journal_file_hmac_put_header(f);
455         if (r < 0)
456                 return r;
457
458         p = le64toh(f->header->field_hash_table_offset);
459         if (p < offsetof(Object, hash_table.items))
460                 return -EINVAL;
461         p -= offsetof(Object, hash_table.items);
462
463         r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, p);
464         if (r < 0)
465                 return r;
466
467         p = le64toh(f->header->data_hash_table_offset);
468         if (p < offsetof(Object, hash_table.items))
469                 return -EINVAL;
470         p -= offsetof(Object, hash_table.items);
471
472         r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, p);
473         if (r < 0)
474                 return r;
475
476         r = journal_file_append_tag(f);
477         if (r < 0)
478                 return r;
479
480         return 0;
481 }
482
483
484 int journal_file_parse_verification_key(JournalFile *f, const char *key) {
485         uint8_t *seed;
486         size_t seed_size, c;
487         const char *k;
488         int r;
489         unsigned long long start, interval;
490
491         seed_size = FSPRG_RECOMMENDED_SEEDLEN;
492         seed = malloc(seed_size);
493         if (!seed)
494                 return -ENOMEM;
495
496         k = key;
497         for (c = 0; c < seed_size; c++) {
498                 int x, y;
499
500                 while (*k == '-')
501                         k++;
502
503                 x = unhexchar(*k);
504                 if (x < 0) {
505                         free(seed);
506                         return -EINVAL;
507                 }
508                 k++;
509                 y = unhexchar(*k);
510                 if (y < 0) {
511                         free(seed);
512                         return -EINVAL;
513                 }
514                 k++;
515
516                 seed[c] = (uint8_t) (x * 16 + y);
517         }
518
519         if (*k != '/') {
520                 free(seed);
521                 return -EINVAL;
522         }
523         k++;
524
525         r = sscanf(k, "%llx-%llx", &start, &interval);
526         if (r != 2) {
527                 free(seed);
528                 return -EINVAL;
529         }
530
531         f->fsprg_seed = seed;
532         f->fsprg_seed_size = seed_size;
533
534         f->fss_start_usec = start * interval;
535         f->fss_interval_usec = interval;
536
537         return 0;
538 }
539
540 bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u) {
541         uint64_t epoch;
542
543         assert(f);
544         assert(u);
545
546         if (!f->seal)
547                 return false;
548
549         epoch = FSPRG_GetEpoch(f->fsprg_state);
550
551         *u = (usec_t) (f->fss_start_usec + f->fss_interval_usec * epoch + f->fss_interval_usec);
552
553         return true;
554 }