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