]>
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 | ||
25 | #include "journal-def.h" | |
26 | #include "journal-file.h" | |
27 | #include "journal-authenticate.h" | |
28 | #include "fsprg.h" | |
29 | ||
0284adc6 LP |
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 | ||
b7c9ae91 | 54 | log_debug("Writing tag for epoch %llu\n", (unsigned long long) FSPRG_GetEpoch(f->fsprg_state)); |
0284adc6 LP |
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 | ||
64 | /* Add the tag object itself, so that we can protect its | |
65 | * header. This will exclude the actual hash value in it */ | |
66 | r = journal_file_hmac_put_object(f, OBJECT_TAG, p); | |
67 | if (r < 0) | |
68 | return r; | |
69 | ||
70 | /* Get the HMAC tag and store it in the object */ | |
71 | memcpy(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH); | |
72 | f->hmac_running = false; | |
73 | ||
74 | return 0; | |
75 | } | |
76 | ||
77 | static int journal_file_hmac_start(JournalFile *f) { | |
78 | uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */ | |
79 | ||
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); | |
b7c9ae91 | 90 | FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0); |
0284adc6 LP |
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 | ||
b7c9ae91 LP |
105 | if (f->fsprg_start_usec == 0 || |
106 | f->fsprg_interval_usec == 0) | |
0284adc6 LP |
107 | return -ENOTSUP; |
108 | ||
b7c9ae91 | 109 | if (realtime < f->fsprg_start_usec) |
0284adc6 LP |
110 | return -ESTALE; |
111 | ||
b7c9ae91 LP |
112 | t = realtime - f->fsprg_start_usec; |
113 | t = t / f->fsprg_interval_usec; | |
0284adc6 LP |
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 | ||
b7c9ae91 | 131 | epoch = FSPRG_GetEpoch(f->fsprg_state); |
0284adc6 LP |
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 | ||
b7c9ae91 | 151 | epoch = FSPRG_GetEpoch(f->fsprg_state); |
0284adc6 LP |
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 | ||
b7c9ae91 LP |
161 | FSPRG_Evolve(f->fsprg_state); |
162 | epoch = FSPRG_GetEpoch(f->fsprg_state); | |
0284adc6 LP |
163 | } |
164 | } | |
165 | ||
166 | int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) { | |
167 | int r; | |
168 | ||
169 | assert(f); | |
170 | ||
171 | if (!f->authenticate) | |
172 | return 0; | |
173 | ||
174 | r = journal_file_need_evolve(f, realtime); | |
175 | if (r <= 0) | |
176 | return 0; | |
177 | ||
178 | r = journal_file_append_tag(f); | |
179 | if (r < 0) | |
180 | return r; | |
181 | ||
182 | r = journal_file_evolve(f, realtime); | |
183 | if (r < 0) | |
184 | return r; | |
185 | ||
186 | r = journal_file_hmac_start(f); | |
187 | if (r < 0) | |
188 | return r; | |
189 | ||
190 | return 0; | |
191 | } | |
192 | ||
193 | int journal_file_hmac_put_object(JournalFile *f, int type, uint64_t p) { | |
194 | int r; | |
195 | Object *o; | |
196 | ||
197 | assert(f); | |
198 | ||
199 | if (!f->authenticate) | |
200 | return 0; | |
201 | ||
202 | r = journal_file_hmac_start(f); | |
203 | if (r < 0) | |
204 | return r; | |
205 | ||
206 | r = journal_file_move_to_object(f, type, p, &o); | |
207 | if (r < 0) | |
208 | return r; | |
209 | ||
210 | gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload)); | |
211 | ||
212 | switch (o->object.type) { | |
213 | ||
214 | case OBJECT_DATA: | |
215 | /* All but: hash and payload are mutable */ | |
216 | gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash)); | |
217 | gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(DataObject, payload)); | |
218 | break; | |
219 | ||
220 | case OBJECT_ENTRY: | |
221 | /* All */ | |
222 | gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(EntryObject, seqnum)); | |
223 | break; | |
224 | ||
225 | case OBJECT_FIELD_HASH_TABLE: | |
226 | case OBJECT_DATA_HASH_TABLE: | |
227 | case OBJECT_ENTRY_ARRAY: | |
228 | /* Nothing: everything is mutable */ | |
229 | break; | |
230 | ||
231 | case OBJECT_TAG: | |
232 | /* All but the tag itself */ | |
233 | gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum)); | |
234 | break; | |
235 | default: | |
236 | return -EINVAL; | |
237 | } | |
238 | ||
239 | return 0; | |
240 | } | |
241 | ||
242 | int journal_file_hmac_put_header(JournalFile *f) { | |
243 | int r; | |
244 | ||
245 | assert(f); | |
246 | ||
247 | if (!f->authenticate) | |
248 | return 0; | |
249 | ||
250 | r = journal_file_hmac_start(f); | |
251 | if (r < 0) | |
252 | return r; | |
253 | ||
254 | /* All but state+reserved, boot_id, arena_size, | |
255 | * tail_object_offset, n_objects, n_entries, tail_seqnum, | |
256 | * head_entry_realtime, tail_entry_realtime, | |
257 | * tail_entry_monotonic, n_data, n_fields, header_tag */ | |
258 | ||
259 | gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature)); | |
260 | gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, boot_id) - offsetof(Header, file_id)); | |
261 | gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id)); | |
262 | gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset)); | |
263 | gcry_md_write(f->hmac, &f->header->head_entry_seqnum, offsetof(Header, head_entry_realtime) - offsetof(Header, head_entry_seqnum)); | |
264 | ||
265 | return 0; | |
266 | } | |
267 | ||
268 | int journal_file_load_fsprg(JournalFile *f) { | |
269 | int r, fd = -1; | |
270 | char *p = NULL; | |
271 | struct stat st; | |
272 | FSPRGHeader *m = NULL; | |
273 | sd_id128_t machine; | |
274 | ||
275 | assert(f); | |
276 | ||
277 | if (!f->authenticate) | |
278 | return 0; | |
279 | ||
280 | r = sd_id128_get_machine(&machine); | |
281 | if (r < 0) | |
282 | return r; | |
283 | ||
284 | if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fsprg", | |
285 | SD_ID128_FORMAT_VAL(machine)) < 0) | |
286 | return -ENOMEM; | |
287 | ||
288 | fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600); | |
289 | if (fd < 0) { | |
290 | log_error("Failed to open %s: %m", p); | |
291 | r = -errno; | |
292 | goto finish; | |
293 | } | |
294 | ||
295 | if (fstat(fd, &st) < 0) { | |
296 | r = -errno; | |
297 | goto finish; | |
298 | } | |
299 | ||
300 | if (st.st_size < (off_t) sizeof(FSPRGHeader)) { | |
301 | r = -ENODATA; | |
302 | goto finish; | |
303 | } | |
304 | ||
305 | m = mmap(NULL, PAGE_ALIGN(sizeof(FSPRGHeader)), PROT_READ, MAP_SHARED, fd, 0); | |
306 | if (m == MAP_FAILED) { | |
307 | m = NULL; | |
308 | r = -errno; | |
309 | goto finish; | |
310 | } | |
311 | ||
312 | if (memcmp(m->signature, FSPRG_HEADER_SIGNATURE, 8) != 0) { | |
313 | r = -EBADMSG; | |
314 | goto finish; | |
315 | } | |
316 | ||
317 | if (m->incompatible_flags != 0) { | |
318 | r = -EPROTONOSUPPORT; | |
319 | goto finish; | |
320 | } | |
321 | ||
322 | if (le64toh(m->header_size) < sizeof(FSPRGHeader)) { | |
323 | r = -EBADMSG; | |
324 | goto finish; | |
325 | } | |
326 | ||
327 | if (le64toh(m->state_size) != FSPRG_stateinbytes(m->secpar)) { | |
328 | r = -EBADMSG; | |
329 | goto finish; | |
330 | } | |
331 | ||
b7c9ae91 LP |
332 | f->fsprg_file_size = le64toh(m->header_size) + le64toh(m->state_size); |
333 | if ((uint64_t) st.st_size < f->fsprg_file_size) { | |
0284adc6 LP |
334 | r = -ENODATA; |
335 | goto finish; | |
336 | } | |
337 | ||
338 | if (!sd_id128_equal(machine, m->machine_id)) { | |
339 | r = -EHOSTDOWN; | |
340 | goto finish; | |
341 | } | |
342 | ||
343 | if (le64toh(m->fsprg_start_usec) <= 0 || | |
344 | le64toh(m->fsprg_interval_usec) <= 0) { | |
345 | r = -EBADMSG; | |
346 | goto finish; | |
347 | } | |
348 | ||
b7c9ae91 LP |
349 | f->fsprg_file = mmap(NULL, PAGE_ALIGN(f->fsprg_file_size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); |
350 | if (f->fsprg_file == MAP_FAILED) { | |
351 | f->fsprg_file = NULL; | |
0284adc6 LP |
352 | r = -errno; |
353 | goto finish; | |
354 | } | |
355 | ||
b7c9ae91 LP |
356 | f->fsprg_start_usec = le64toh(f->fsprg_file->fsprg_start_usec); |
357 | f->fsprg_interval_usec = le64toh(f->fsprg_file->fsprg_interval_usec); | |
358 | ||
359 | f->fsprg_state = (uint8_t*) f->fsprg_file + le64toh(f->fsprg_file->header_size); | |
360 | f->fsprg_state_size = le64toh(f->fsprg_file->state_size); | |
361 | ||
0284adc6 LP |
362 | r = 0; |
363 | ||
364 | finish: | |
365 | if (m) | |
366 | munmap(m, PAGE_ALIGN(sizeof(FSPRGHeader))); | |
367 | ||
368 | if (fd >= 0) | |
369 | close_nointr_nofail(fd); | |
370 | ||
371 | free(p); | |
372 | return r; | |
373 | } | |
374 | ||
375 | int journal_file_setup_hmac(JournalFile *f) { | |
376 | gcry_error_t e; | |
377 | ||
378 | if (!f->authenticate) | |
379 | return 0; | |
380 | ||
381 | e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); | |
382 | if (e != 0) | |
383 | return -ENOTSUP; | |
384 | ||
385 | return 0; | |
386 | } | |
387 | ||
388 | int journal_file_append_first_tag(JournalFile *f) { | |
389 | int r; | |
390 | uint64_t p; | |
391 | ||
392 | if (!f->authenticate) | |
393 | return 0; | |
394 | ||
395 | log_debug("Calculating first tag..."); | |
396 | ||
397 | r = journal_file_hmac_put_header(f); | |
398 | if (r < 0) | |
399 | return r; | |
400 | ||
401 | p = le64toh(f->header->field_hash_table_offset); | |
402 | if (p < offsetof(Object, hash_table.items)) | |
403 | return -EINVAL; | |
404 | p -= offsetof(Object, hash_table.items); | |
405 | ||
406 | r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, p); | |
407 | if (r < 0) | |
408 | return r; | |
409 | ||
410 | p = le64toh(f->header->data_hash_table_offset); | |
411 | if (p < offsetof(Object, hash_table.items)) | |
412 | return -EINVAL; | |
413 | p -= offsetof(Object, hash_table.items); | |
414 | ||
415 | r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, p); | |
416 | if (r < 0) | |
417 | return r; | |
418 | ||
419 | r = journal_file_append_tag(f); | |
420 | if (r < 0) | |
421 | return r; | |
422 | ||
423 | return 0; | |
424 | } | |
4da416aa LP |
425 | |
426 | bool journal_file_fsprg_enabled(JournalFile *f) { | |
427 | assert(f); | |
428 | ||
429 | return !!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED); | |
430 | } |