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