From: Lennart Poettering Date: Thu, 16 Aug 2012 21:58:14 +0000 (+0200) Subject: journal: add FSPRG journal authentication X-Git-Tag: v189~58 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=14d10188de1fd58e663d73683a400d8d7dc67dba;hp=a8e5f51484ba832e299a38f2a54e455e445d2896 journal: add FSPRG journal authentication --- diff --git a/src/journal/journal-authenticate.c b/src/journal/journal-authenticate.c index 809655e1a..c087ad4c8 100644 --- a/src/journal/journal-authenticate.c +++ b/src/journal/journal-authenticate.c @@ -60,6 +60,7 @@ int journal_file_append_tag(JournalFile *f) { return r; o->tag.seqnum = htole64(journal_file_tag_seqnum(f)); + o->tag.epoch = htole64(FSPRG_GetEpoch(f->fsprg_state)); /* Add the tag object itself, so that we can protect its * header. This will exclude the actual hash value in it */ @@ -74,9 +75,8 @@ int journal_file_append_tag(JournalFile *f) { return 0; } -static int journal_file_hmac_start(JournalFile *f) { +int journal_file_hmac_start(JournalFile *f) { uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */ - assert(f); if (!f->authenticate) @@ -163,6 +163,44 @@ static int journal_file_evolve(JournalFile *f, uint64_t realtime) { } } +int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { + void *msk; + uint64_t epoch; + + assert(f); + + if (!f->authenticate) + return 0; + + assert(f->fsprg_seed); + + if (f->fsprg_state) { + /* Cheaper... */ + + epoch = FSPRG_GetEpoch(f->fsprg_state); + if (goal == epoch) + return 0; + + if (goal == epoch+1) { + FSPRG_Evolve(f->fsprg_state); + return 0; + } + } else { + f->fsprg_state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); + f->fsprg_state = malloc(f->fsprg_state_size); + + if (!f->fsprg_state) + return -ENOMEM; + } + + log_debug("Seeking FSPRG key to %llu.", (unsigned long long) goal); + + msk = alloca(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR)); + FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR); + FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size); + return 0; +} + int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) { int r; @@ -212,7 +250,7 @@ int journal_file_hmac_put_object(JournalFile *f, int type, uint64_t p) { switch (o->object.type) { case OBJECT_DATA: - /* All but: hash and payload are mutable */ + /* All but hash and payload are mutable */ gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash)); gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(DataObject, payload)); break; @@ -231,6 +269,7 @@ int journal_file_hmac_put_object(JournalFile *f, int type, uint64_t p) { case OBJECT_TAG: /* All but the tag itself */ gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum)); + gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch)); break; default: return -EINVAL; @@ -252,15 +291,16 @@ int journal_file_hmac_put_header(JournalFile *f) { return r; /* All but state+reserved, boot_id, arena_size, - * tail_object_offset, n_objects, n_entries, tail_seqnum, + * tail_object_offset, n_objects, n_entries, + * tail_entry_seqnum, head_entry_seqnum, entry_array_offset, * head_entry_realtime, tail_entry_realtime, - * tail_entry_monotonic, n_data, n_fields, header_tag */ + * tail_entry_monotonic, n_data, n_fields, n_tags, + * n_entry_arrays. */ gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature)); gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, boot_id) - offsetof(Header, file_id)); gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id)); gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset)); - gcry_md_write(f->hmac, &f->header->head_entry_seqnum, offsetof(Header, head_entry_realtime) - offsetof(Header, head_entry_seqnum)); return 0; } diff --git a/src/journal/journal-authenticate.h b/src/journal/journal-authenticate.h index 566d7a81a..282c73f68 100644 --- a/src/journal/journal-authenticate.h +++ b/src/journal/journal-authenticate.h @@ -30,6 +30,7 @@ int journal_file_append_tag(JournalFile *f); int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime); int journal_file_append_first_tag(JournalFile *f); +int journal_file_hmac_start(JournalFile *f); int journal_file_hmac_put_header(JournalFile *f); int journal_file_hmac_put_object(JournalFile *f, int type, uint64_t p); @@ -38,3 +39,5 @@ int journal_file_load_fsprg(JournalFile *f); int journal_file_setup_hmac(JournalFile *f); bool journal_file_fsprg_enabled(JournalFile *f); + +int journal_file_fsprg_seek(JournalFile *f, uint64_t epoch); diff --git a/src/journal/journal-def.h b/src/journal/journal-def.h index 660a92c14..ab4988037 100644 --- a/src/journal/journal-def.h +++ b/src/journal/journal-def.h @@ -125,6 +125,7 @@ _packed_ struct EntryArrayObject { _packed_ struct TagObject { ObjectHeader object; uint64_t seqnum; + uint64_t epoch; uint8_t tag[TAG_LENGTH]; /* SHA-256 HMAC */ }; diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c index 3bb1e90fb..274f22db1 100644 --- a/src/journal/journal-file.c +++ b/src/journal/journal-file.c @@ -2049,12 +2049,12 @@ int journal_file_open( r = journal_file_refresh_header(f); if (r < 0) goto fail; - - r = journal_file_setup_hmac(f); - if (r < 0) - goto fail; } + r = journal_file_setup_hmac(f); + if (r < 0) + goto fail; + if (newly_created) { r = journal_file_setup_field_hash_table(f); if (r < 0) diff --git a/src/journal/journal-verify.c b/src/journal/journal-verify.c index 94f90b670..e646e38dd 100644 --- a/src/journal/journal-verify.c +++ b/src/journal/journal-verify.c @@ -35,7 +35,10 @@ /* FIXME: * - * - verify FSPRG + * - write tag only if non-tag objects have been written + * - change terms + * - write bit mucking test + * * - Allow building without libgcrypt * - check with sparse * - 64bit conversions @@ -650,11 +653,11 @@ static int journal_file_parse_seed(JournalFile *f, const char *s) { int journal_file_verify(JournalFile *f, const char *seed) { int r; Object *o; - uint64_t p = 0, last_tag = 0; - uint64_t n_tags = 0, entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0; + uint64_t p = 0, last_tag = 0, last_epoch = 0; + uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0; sd_id128_t entry_boot_id; bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false; - uint64_t n_weird = 0, n_objects = 0, n_entries = 0, n_data = 0, n_fields = 0, n_data_hash_tables = 0, n_field_hash_tables = 0, n_entry_arrays = 0; + uint64_t n_weird = 0, n_objects = 0, n_entries = 0, n_data = 0, n_fields = 0, n_data_hash_tables = 0, n_field_hash_tables = 0, n_entry_arrays = 0, n_tags = 0; usec_t last_usec = 0; int data_fd = -1, entry_fd = -1, entry_array_fd = -1; char data_path[] = "/var/tmp/journal-data-XXXXXX", @@ -842,7 +845,9 @@ int journal_file_verify(JournalFile *f, const char *seed) { n_entry_arrays++; break; - case OBJECT_TAG: + case OBJECT_TAG: { + uint64_t q; + if (!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED)) { log_error("Tag object without authentication at %llu", (unsigned long long) p); r = -EBADMSG; @@ -855,8 +860,61 @@ int journal_file_verify(JournalFile *f, const char *seed) { goto fail; } + if (le64toh(o->tag.epoch) < last_epoch) { + log_error("Epoch sequence out of synchronization at %llu", (unsigned long long) p); + r = -EBADMSG; + goto fail; + } + + /* OK, now we know the epoch. So let's now set + * it, and calculate the HMAC for everything + * since the last tag. */ + r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch)); + if (r < 0) + goto fail; + + r = journal_file_hmac_start(f); + if (r < 0) + goto fail; + + if (last_tag == 0) { + r = journal_file_hmac_put_header(f); + if (r < 0) + goto fail; + + q = le64toh(f->header->header_size); + } else + q = last_tag; + + while (q <= p) { + r = journal_file_move_to_object(f, -1, q, &o); + if (r < 0) + goto fail; + + r = journal_file_hmac_put_object(f, -1, q); + if (r < 0) + goto fail; + + q = q + ALIGN64(le64toh(o->object.size)); + } + + /* Position might have changed, let's reposition things */ + r = journal_file_move_to_object(f, -1, p, &o); + if (r < 0) + goto fail; + + if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { + log_error("Tag did not authenticate at %llu", (unsigned long long) p); + r = -EBADMSG; + goto fail; + } + + f->hmac_running = false; + + last_tag = p + ALIGN64(le64toh(o->object.size)); n_tags ++; break; + } default: n_weird ++; diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 25e441b02..f0654fe4e 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -60,6 +60,7 @@ static bool arg_this_boot = false; static const char *arg_directory = NULL; static int arg_priorities = 0xFF; static const char *arg_verify_seed = NULL; +static usec_t arg_evolve = DEFAULT_FSPRG_INTERVAL_USEC; static enum { ACTION_SHOW, @@ -73,26 +74,27 @@ static int help(void) { printf("%s [OPTIONS...] [MATCH]\n\n" "Send control commands to or query the journal.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " -a --all Show all fields, including long and unprintable\n" - " -f --follow Follow journal\n" - " -n --lines=INTEGER Journal entries to show\n" - " --no-tail Show all lines, even in follow mode\n" - " -o --output=STRING Change journal output mode (short, short-monotonic,\n" - " verbose, export, json, cat)\n" - " -q --quiet Don't show privilege warning\n" - " -l --local Only local entries\n" - " -b --this-boot Show data only from current boot\n" - " -D --directory=PATH Show journal files from directory\n" - " -p --priority=RANGE Show only messages within the specified priority range\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " -a --all Show all fields, including long and unprintable\n" + " -f --follow Follow journal\n" + " -n --lines=INTEGER Journal entries to show\n" + " --no-tail Show all lines, even in follow mode\n" + " -o --output=STRING Change journal output mode (short, short-monotonic,\n" + " verbose, export, json, cat)\n" + " -q --quiet Don't show privilege warning\n" + " -l --local Only local entries\n" + " -b --this-boot Show data only from current boot\n" + " -D --directory=PATH Show journal files from directory\n" + " -p --priority=RANGE Show only messages within the specified priority range\n\n" "Commands:\n" - " --new-id128 Generate a new 128 Bit ID\n" - " --header Show journal header information\n" - " --verify Verify journal file consistency\n" - " --verify-seed=SEED Specify FSPRG seed for verification\n" - " --setup-keys Generate new FSPRG key and seed\n", + " --new-id128 Generate a new 128 Bit ID\n" + " --header Show journal header information\n" + " --verify Verify journal file consistency\n" + " --verify-seed=SEED Specify FSPRG seed for verification\n" + " --setup-keys Generate new FSPRG key and seed\n" + " --evolve=TIME How of to evolve FSPRG keys\n", program_invocation_short_name); return 0; @@ -108,7 +110,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_HEADER, ARG_SETUP_KEYS, ARG_VERIFY, - ARG_VERIFY_SEED + ARG_VERIFY_SEED, + ARG_EVOLVE }; static const struct option options[] = { @@ -130,6 +133,7 @@ static int parse_argv(int argc, char *argv[]) { { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS }, { "verify", no_argument, NULL, ARG_VERIFY }, { "verify-seed", required_argument, NULL, ARG_VERIFY_SEED }, + { "evolve", required_argument, NULL, ARG_EVOLVE }, { NULL, 0, NULL, 0 } }; @@ -222,6 +226,14 @@ static int parse_argv(int argc, char *argv[]) { arg_verify_seed = optarg; break; + case ARG_EVOLVE: + r = parse_usec(optarg, &arg_evolve); + if (r < 0 || arg_evolve <= 0) { + log_error("Failed to parse evolve interval: %s", optarg); + return -EINVAL; + } + break; + case 'p': { const char *dots; @@ -445,7 +457,7 @@ static int setup_keys(void) { sd_id128_t machine, boot; char *p = NULL, *k = NULL; struct FSPRGHeader h; - uint64_t n, interval; + uint64_t n; r = sd_id128_get_machine(&machine); if (r < 0) { @@ -505,9 +517,8 @@ static int setup_keys(void) { log_info("Generating evolving key..."); FSPRG_GenState0(state, mpk, seed, seed_size); - interval = DEFAULT_FSPRG_INTERVAL_USEC; n = now(CLOCK_REALTIME); - n /= interval; + n /= arg_evolve; close_nointr_nofail(fd); fd = mkostemp(k, O_WRONLY|O_CLOEXEC|O_NOCTTY); @@ -522,8 +533,8 @@ static int setup_keys(void) { h.machine_id = machine; h.boot_id = boot; h.header_size = htole64(sizeof(h)); - h.fsprg_start_usec = htole64(n * interval); - h.fsprg_interval_usec = htole64(interval); + h.fsprg_start_usec = htole64(n * arg_evolve); + h.fsprg_interval_usec = htole64(arg_evolve); h.secpar = htole16(FSPRG_RECOMMENDED_SECPAR); h.state_size = htole64(state_size); @@ -567,7 +578,7 @@ static int setup_keys(void) { printf("%02x", ((uint8_t*) seed)[i]); } - printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) interval); + printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) arg_evolve); if (isatty(STDOUT_FILENO)) fputs(ANSI_HIGHLIGHT_OFF "\n", stderr); diff --git a/src/journal/test-journal-verify.c b/src/journal/test-journal-verify.c index bada498fa..8a7d998ff 100644 --- a/src/journal/test-journal-verify.c +++ b/src/journal/test-journal-verify.c @@ -35,6 +35,7 @@ int main(int argc, char *argv[]) { char t[] = "/tmp/journal-XXXXXX"; unsigned n; JournalFile *f; + const char *verification_key = argv[1]; log_set_max_level(LOG_DEBUG); @@ -43,7 +44,7 @@ int main(int argc, char *argv[]) { log_info("Generating..."); - assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, &f) == 0); + assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, true, !!verification_key, NULL, NULL, NULL, &f) == 0); for (n = 0; n < N_ENTRIES; n++) { struct iovec iovec; @@ -67,7 +68,7 @@ int main(int argc, char *argv[]) { log_info("Verifying..."); assert_se(journal_file_open("test.journal", O_RDONLY, 0666, false, false, NULL, NULL, NULL, &f) == 0); - assert_se(journal_file_verify(f, NULL) >= 0); + assert_se(journal_file_verify(f, verification_key) >= 0); journal_file_close(f); log_info("Exiting...");