chiark / gitweb /
man: extend systemd-run man page a little
[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 %"PRIu64" for epoch %"PRIu64"\n",
64                   le64toh(o->tag.seqnum),
65                   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, o, 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 %"PRIu64" to %"PRIu64".", epoch, 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 %"PRIu64".", 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, Object *o, uint64_t p) {
233         int r;
234
235         assert(f);
236
237         if (!f->seal)
238                 return 0;
239
240         r = journal_file_hmac_start(f);
241         if (r < 0)
242                 return r;
243
244         if (!o) {
245                 r = journal_file_move_to_object(f, type, p, &o);
246                 if (r < 0)
247                         return r;
248         } else {
249                 if (type >= 0 && o->object.type != type)
250                         return -EBADMSG;
251         }
252
253         gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload));
254
255         switch (o->object.type) {
256
257         case OBJECT_DATA:
258                 /* All but hash and payload are mutable */
259                 gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash));
260                 gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(DataObject, payload));
261                 break;
262
263         case OBJECT_FIELD:
264                 /* Same here */
265                 gcry_md_write(f->hmac, &o->field.hash, sizeof(o->field.hash));
266                 gcry_md_write(f->hmac, o->field.payload, le64toh(o->object.size) - offsetof(FieldObject, payload));
267                 break;
268
269         case OBJECT_ENTRY:
270                 /* All */
271                 gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(EntryObject, seqnum));
272                 break;
273
274         case OBJECT_FIELD_HASH_TABLE:
275         case OBJECT_DATA_HASH_TABLE:
276         case OBJECT_ENTRY_ARRAY:
277                 /* Nothing: everything is mutable */
278                 break;
279
280         case OBJECT_TAG:
281                 /* All but the tag itself */
282                 gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum));
283                 gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch));
284                 break;
285         default:
286                 return -EINVAL;
287         }
288
289         return 0;
290 }
291
292 int journal_file_hmac_put_header(JournalFile *f) {
293         int r;
294
295         assert(f);
296
297         if (!f->seal)
298                 return 0;
299
300         r = journal_file_hmac_start(f);
301         if (r < 0)
302                 return r;
303
304         /* All but state+reserved, boot_id, arena_size,
305          * tail_object_offset, n_objects, n_entries,
306          * tail_entry_seqnum, head_entry_seqnum, entry_array_offset,
307          * head_entry_realtime, tail_entry_realtime,
308          * tail_entry_monotonic, n_data, n_fields, n_tags,
309          * n_entry_arrays. */
310
311         gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature));
312         gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, boot_id) - offsetof(Header, file_id));
313         gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id));
314         gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset));
315
316         return 0;
317 }
318
319 int journal_file_fss_load(JournalFile *f) {
320         int r, fd = -1;
321         char *p = NULL;
322         struct stat st;
323         FSSHeader *m = NULL;
324         sd_id128_t machine;
325
326         assert(f);
327
328         if (!f->seal)
329                 return 0;
330
331         r = sd_id128_get_machine(&machine);
332         if (r < 0)
333                 return r;
334
335         if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
336                      SD_ID128_FORMAT_VAL(machine)) < 0)
337                 return -ENOMEM;
338
339         fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
340         if (fd < 0) {
341                 if (errno != ENOENT)
342                         log_error("Failed to open %s: %m", p);
343
344                 r = -errno;
345                 goto finish;
346         }
347
348         if (fstat(fd, &st) < 0) {
349                 r = -errno;
350                 goto finish;
351         }
352
353         if (st.st_size < (off_t) sizeof(FSSHeader)) {
354                 r = -ENODATA;
355                 goto finish;
356         }
357
358         m = mmap(NULL, PAGE_ALIGN(sizeof(FSSHeader)), PROT_READ, MAP_SHARED, fd, 0);
359         if (m == MAP_FAILED) {
360                 m = NULL;
361                 r = -errno;
362                 goto finish;
363         }
364
365         if (memcmp(m->signature, FSS_HEADER_SIGNATURE, 8) != 0) {
366                 r = -EBADMSG;
367                 goto finish;
368         }
369
370         if (m->incompatible_flags != 0) {
371                 r = -EPROTONOSUPPORT;
372                 goto finish;
373         }
374
375         if (le64toh(m->header_size) < sizeof(FSSHeader)) {
376                 r = -EBADMSG;
377                 goto finish;
378         }
379
380         if (le64toh(m->fsprg_state_size) != FSPRG_stateinbytes(le16toh(m->fsprg_secpar))) {
381                 r = -EBADMSG;
382                 goto finish;
383         }
384
385         f->fss_file_size = le64toh(m->header_size) + le64toh(m->fsprg_state_size);
386         if ((uint64_t) st.st_size < f->fss_file_size) {
387                 r = -ENODATA;
388                 goto finish;
389         }
390
391         if (!sd_id128_equal(machine, m->machine_id)) {
392                 r = -EHOSTDOWN;
393                 goto finish;
394         }
395
396         if (le64toh(m->start_usec) <= 0 ||
397             le64toh(m->interval_usec) <= 0) {
398                 r = -EBADMSG;
399                 goto finish;
400         }
401
402         f->fss_file = mmap(NULL, PAGE_ALIGN(f->fss_file_size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
403         if (f->fss_file == MAP_FAILED) {
404                 f->fss_file = NULL;
405                 r = -errno;
406                 goto finish;
407         }
408
409         f->fss_start_usec = le64toh(f->fss_file->start_usec);
410         f->fss_interval_usec = le64toh(f->fss_file->interval_usec);
411
412         f->fsprg_state = (uint8_t*) f->fss_file + le64toh(f->fss_file->header_size);
413         f->fsprg_state_size = le64toh(f->fss_file->fsprg_state_size);
414
415         r = 0;
416
417 finish:
418         if (m)
419                 munmap(m, PAGE_ALIGN(sizeof(FSSHeader)));
420
421         if (fd >= 0)
422                 close_nointr_nofail(fd);
423
424         free(p);
425         return r;
426 }
427
428 static void initialize_libgcrypt(void) {
429         const char *p;
430
431         if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
432                 return;
433
434         p = gcry_check_version("1.4.5");
435         assert(p);
436
437         gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
438 }
439
440 int journal_file_hmac_setup(JournalFile *f) {
441         gcry_error_t e;
442
443         if (!f->seal)
444                 return 0;
445
446         initialize_libgcrypt();
447
448         e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
449         if (e != 0)
450                 return -ENOTSUP;
451
452         return 0;
453 }
454
455 int journal_file_append_first_tag(JournalFile *f) {
456         int r;
457         uint64_t p;
458
459         if (!f->seal)
460                 return 0;
461
462         log_debug("Calculating first tag...");
463
464         r = journal_file_hmac_put_header(f);
465         if (r < 0)
466                 return r;
467
468         p = le64toh(f->header->field_hash_table_offset);
469         if (p < offsetof(Object, hash_table.items))
470                 return -EINVAL;
471         p -= offsetof(Object, hash_table.items);
472
473         r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, NULL, p);
474         if (r < 0)
475                 return r;
476
477         p = le64toh(f->header->data_hash_table_offset);
478         if (p < offsetof(Object, hash_table.items))
479                 return -EINVAL;
480         p -= offsetof(Object, hash_table.items);
481
482         r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, NULL, p);
483         if (r < 0)
484                 return r;
485
486         r = journal_file_append_tag(f);
487         if (r < 0)
488                 return r;
489
490         return 0;
491 }
492
493 int journal_file_parse_verification_key(JournalFile *f, const char *key) {
494         uint8_t *seed;
495         size_t seed_size, c;
496         const char *k;
497         int r;
498         unsigned long long start, interval;
499
500         seed_size = FSPRG_RECOMMENDED_SEEDLEN;
501         seed = malloc(seed_size);
502         if (!seed)
503                 return -ENOMEM;
504
505         k = key;
506         for (c = 0; c < seed_size; c++) {
507                 int x, y;
508
509                 while (*k == '-')
510                         k++;
511
512                 x = unhexchar(*k);
513                 if (x < 0) {
514                         free(seed);
515                         return -EINVAL;
516                 }
517                 k++;
518                 y = unhexchar(*k);
519                 if (y < 0) {
520                         free(seed);
521                         return -EINVAL;
522                 }
523                 k++;
524
525                 seed[c] = (uint8_t) (x * 16 + y);
526         }
527
528         if (*k != '/') {
529                 free(seed);
530                 return -EINVAL;
531         }
532         k++;
533
534         r = sscanf(k, "%llx-%llx", &start, &interval);
535         if (r != 2) {
536                 free(seed);
537                 return -EINVAL;
538         }
539
540         f->fsprg_seed = seed;
541         f->fsprg_seed_size = seed_size;
542
543         f->fss_start_usec = start * interval;
544         f->fss_interval_usec = interval;
545
546         return 0;
547 }
548
549 bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u) {
550         uint64_t epoch;
551
552         assert(f);
553         assert(u);
554
555         if (!f->seal)
556                 return false;
557
558         epoch = FSPRG_GetEpoch(f->fsprg_state);
559
560         *u = (usec_t) (f->fss_start_usec + f->fss_interval_usec * epoch + f->fss_interval_usec);
561
562         return true;
563 }