chiark / gitweb /
journal: add FSPRG journal authentication
[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->authenticate)
49                 return 0;
50
51         if (!f->hmac_running)
52                 return 0;
53
54         log_debug("Writing tag for epoch %llu\n", (unsigned long long) FSPRG_GetEpoch(f->fsprg_state));
55
56         assert(f->hmac);
57
58         r = journal_file_append_object(f, OBJECT_TAG, sizeof(struct TagObject), &o, &p);
59         if (r < 0)
60                 return r;
61
62         o->tag.seqnum = htole64(journal_file_tag_seqnum(f));
63         o->tag.epoch = htole64(FSPRG_GetEpoch(f->fsprg_state));
64
65         /* Add the tag object itself, so that we can protect its
66          * header. This will exclude the actual hash value in it */
67         r = journal_file_hmac_put_object(f, OBJECT_TAG, p);
68         if (r < 0)
69                 return r;
70
71         /* Get the HMAC tag and store it in the object */
72         memcpy(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH);
73         f->hmac_running = false;
74
75         return 0;
76 }
77
78 int journal_file_hmac_start(JournalFile *f) {
79         uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */
80         assert(f);
81
82         if (!f->authenticate)
83                 return 0;
84
85         if (f->hmac_running)
86                 return 0;
87
88         /* Prepare HMAC for next cycle */
89         gcry_md_reset(f->hmac);
90         FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0);
91         gcry_md_setkey(f->hmac, key, sizeof(key));
92
93         f->hmac_running = true;
94
95         return 0;
96 }
97
98 static int journal_file_get_epoch(JournalFile *f, uint64_t realtime, uint64_t *epoch) {
99         uint64_t t;
100
101         assert(f);
102         assert(epoch);
103         assert(f->authenticate);
104
105         if (f->fsprg_start_usec == 0 ||
106             f->fsprg_interval_usec == 0)
107                 return -ENOTSUP;
108
109         if (realtime < f->fsprg_start_usec)
110                 return -ESTALE;
111
112         t = realtime - f->fsprg_start_usec;
113         t = t / f->fsprg_interval_usec;
114
115         *epoch = t;
116         return 0;
117 }
118
119 static int journal_file_need_evolve(JournalFile *f, uint64_t realtime) {
120         uint64_t goal, epoch;
121         int r;
122         assert(f);
123
124         if (!f->authenticate)
125                 return 0;
126
127         r = journal_file_get_epoch(f, realtime, &goal);
128         if (r < 0)
129                 return r;
130
131         epoch = FSPRG_GetEpoch(f->fsprg_state);
132         if (epoch > goal)
133                 return -ESTALE;
134
135         return epoch != goal;
136 }
137
138 static int journal_file_evolve(JournalFile *f, uint64_t realtime) {
139         uint64_t goal, epoch;
140         int r;
141
142         assert(f);
143
144         if (!f->authenticate)
145                 return 0;
146
147         r = journal_file_get_epoch(f, realtime, &goal);
148         if (r < 0)
149                 return r;
150
151         epoch = FSPRG_GetEpoch(f->fsprg_state);
152         if (epoch < goal)
153                 log_debug("Evolving FSPRG key from epoch %llu to %llu.", (unsigned long long) epoch, (unsigned long long) goal);
154
155         for (;;) {
156                 if (epoch > goal)
157                         return -ESTALE;
158                 if (epoch == goal)
159                         return 0;
160
161                 FSPRG_Evolve(f->fsprg_state);
162                 epoch = FSPRG_GetEpoch(f->fsprg_state);
163         }
164 }
165
166 int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) {
167         void *msk;
168         uint64_t epoch;
169
170         assert(f);
171
172         if (!f->authenticate)
173                 return 0;
174
175         assert(f->fsprg_seed);
176
177         if (f->fsprg_state) {
178                 /* Cheaper... */
179
180                 epoch = FSPRG_GetEpoch(f->fsprg_state);
181                 if (goal == epoch)
182                         return 0;
183
184                 if (goal == epoch+1) {
185                         FSPRG_Evolve(f->fsprg_state);
186                         return 0;
187                 }
188         } else {
189                 f->fsprg_state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
190                 f->fsprg_state = malloc(f->fsprg_state_size);
191
192                 if (!f->fsprg_state)
193                         return -ENOMEM;
194         }
195
196         log_debug("Seeking FSPRG key to %llu.", (unsigned long long) goal);
197
198         msk = alloca(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR));
199         FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR);
200         FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size);
201         return 0;
202 }
203
204 int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) {
205         int r;
206
207         assert(f);
208
209         if (!f->authenticate)
210                 return 0;
211
212         r = journal_file_need_evolve(f, realtime);
213         if (r <= 0)
214                 return 0;
215
216         r = journal_file_append_tag(f);
217         if (r < 0)
218                 return r;
219
220         r = journal_file_evolve(f, realtime);
221         if (r < 0)
222                 return r;
223
224         r = journal_file_hmac_start(f);
225         if (r < 0)
226                 return r;
227
228         return 0;
229 }
230
231 int journal_file_hmac_put_object(JournalFile *f, int type, uint64_t p) {
232         int r;
233         Object *o;
234
235         assert(f);
236
237         if (!f->authenticate)
238                 return 0;
239
240         r = journal_file_hmac_start(f);
241         if (r < 0)
242                 return r;
243
244         r = journal_file_move_to_object(f, type, p, &o);
245         if (r < 0)
246                 return r;
247
248         gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload));
249
250         switch (o->object.type) {
251
252         case OBJECT_DATA:
253                 /* All but hash and payload are mutable */
254                 gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash));
255                 gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(DataObject, payload));
256                 break;
257
258         case OBJECT_ENTRY:
259                 /* All */
260                 gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(EntryObject, seqnum));
261                 break;
262
263         case OBJECT_FIELD_HASH_TABLE:
264         case OBJECT_DATA_HASH_TABLE:
265         case OBJECT_ENTRY_ARRAY:
266                 /* Nothing: everything is mutable */
267                 break;
268
269         case OBJECT_TAG:
270                 /* All but the tag itself */
271                 gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum));
272                 gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch));
273                 break;
274         default:
275                 return -EINVAL;
276         }
277
278         return 0;
279 }
280
281 int journal_file_hmac_put_header(JournalFile *f) {
282         int r;
283
284         assert(f);
285
286         if (!f->authenticate)
287                 return 0;
288
289         r = journal_file_hmac_start(f);
290         if (r < 0)
291                 return r;
292
293         /* All but state+reserved, boot_id, arena_size,
294          * tail_object_offset, n_objects, n_entries,
295          * tail_entry_seqnum, head_entry_seqnum, entry_array_offset,
296          * head_entry_realtime, tail_entry_realtime,
297          * tail_entry_monotonic, n_data, n_fields, n_tags,
298          * n_entry_arrays. */
299
300         gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature));
301         gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, boot_id) - offsetof(Header, file_id));
302         gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id));
303         gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset));
304
305         return 0;
306 }
307
308 int journal_file_load_fsprg(JournalFile *f) {
309         int r, fd = -1;
310         char *p = NULL;
311         struct stat st;
312         FSPRGHeader *m = NULL;
313         sd_id128_t machine;
314
315         assert(f);
316
317         if (!f->authenticate)
318                 return 0;
319
320         r = sd_id128_get_machine(&machine);
321         if (r < 0)
322                 return r;
323
324         if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fsprg",
325                      SD_ID128_FORMAT_VAL(machine)) < 0)
326                 return -ENOMEM;
327
328         fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
329         if (fd < 0) {
330                 log_error("Failed to open %s: %m", p);
331                 r = -errno;
332                 goto finish;
333         }
334
335         if (fstat(fd, &st) < 0) {
336                 r = -errno;
337                 goto finish;
338         }
339
340         if (st.st_size < (off_t) sizeof(FSPRGHeader)) {
341                 r = -ENODATA;
342                 goto finish;
343         }
344
345         m = mmap(NULL, PAGE_ALIGN(sizeof(FSPRGHeader)), PROT_READ, MAP_SHARED, fd, 0);
346         if (m == MAP_FAILED) {
347                 m = NULL;
348                 r = -errno;
349                 goto finish;
350         }
351
352         if (memcmp(m->signature, FSPRG_HEADER_SIGNATURE, 8) != 0) {
353                 r = -EBADMSG;
354                 goto finish;
355         }
356
357         if (m->incompatible_flags != 0) {
358                 r = -EPROTONOSUPPORT;
359                 goto finish;
360         }
361
362         if (le64toh(m->header_size) < sizeof(FSPRGHeader)) {
363                 r = -EBADMSG;
364                 goto finish;
365         }
366
367         if (le64toh(m->state_size) != FSPRG_stateinbytes(m->secpar)) {
368                 r = -EBADMSG;
369                 goto finish;
370         }
371
372         f->fsprg_file_size = le64toh(m->header_size) + le64toh(m->state_size);
373         if ((uint64_t) st.st_size < f->fsprg_file_size) {
374                 r = -ENODATA;
375                 goto finish;
376         }
377
378         if (!sd_id128_equal(machine, m->machine_id)) {
379                 r = -EHOSTDOWN;
380                 goto finish;
381         }
382
383         if (le64toh(m->fsprg_start_usec) <= 0 ||
384             le64toh(m->fsprg_interval_usec) <= 0) {
385                 r = -EBADMSG;
386                 goto finish;
387         }
388
389         f->fsprg_file = mmap(NULL, PAGE_ALIGN(f->fsprg_file_size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
390         if (f->fsprg_file == MAP_FAILED) {
391                 f->fsprg_file = NULL;
392                 r = -errno;
393                 goto finish;
394         }
395
396         f->fsprg_start_usec = le64toh(f->fsprg_file->fsprg_start_usec);
397         f->fsprg_interval_usec = le64toh(f->fsprg_file->fsprg_interval_usec);
398
399         f->fsprg_state = (uint8_t*) f->fsprg_file + le64toh(f->fsprg_file->header_size);
400         f->fsprg_state_size = le64toh(f->fsprg_file->state_size);
401
402         r = 0;
403
404 finish:
405         if (m)
406                 munmap(m, PAGE_ALIGN(sizeof(FSPRGHeader)));
407
408         if (fd >= 0)
409                 close_nointr_nofail(fd);
410
411         free(p);
412         return r;
413 }
414
415 int journal_file_setup_hmac(JournalFile *f) {
416         gcry_error_t e;
417
418         if (!f->authenticate)
419                 return 0;
420
421         e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
422         if (e != 0)
423                 return -ENOTSUP;
424
425         return 0;
426 }
427
428 int journal_file_append_first_tag(JournalFile *f) {
429         int r;
430         uint64_t p;
431
432         if (!f->authenticate)
433                 return 0;
434
435         log_debug("Calculating first tag...");
436
437         r = journal_file_hmac_put_header(f);
438         if (r < 0)
439                 return r;
440
441         p = le64toh(f->header->field_hash_table_offset);
442         if (p < offsetof(Object, hash_table.items))
443                 return -EINVAL;
444         p -= offsetof(Object, hash_table.items);
445
446         r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, p);
447         if (r < 0)
448                 return r;
449
450         p = le64toh(f->header->data_hash_table_offset);
451         if (p < offsetof(Object, hash_table.items))
452                 return -EINVAL;
453         p -= offsetof(Object, hash_table.items);
454
455         r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, p);
456         if (r < 0)
457                 return r;
458
459         r = journal_file_append_tag(f);
460         if (r < 0)
461                 return r;
462
463         return 0;
464 }
465
466 bool journal_file_fsprg_enabled(JournalFile *f) {
467         assert(f);
468
469         return !!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED);
470 }