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