journal: add FSPRG journal authentication
authorLennart Poettering <lennart@poettering.net>
Thu, 16 Aug 2012 21:58:14 +0000 (23:58 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 16 Aug 2012 21:58:14 +0000 (23:58 +0200)
src/journal/journal-authenticate.c
src/journal/journal-authenticate.h
src/journal/journal-def.h
src/journal/journal-file.c
src/journal/journal-verify.c
src/journal/journalctl.c
src/journal/test-journal-verify.c

index 809655e..c087ad4 100644 (file)
@@ -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;
 }
index 566d7a8..282c73f 100644 (file)
@@ -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);
index 660a92c..ab49880 100644 (file)
@@ -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 */
 };
 
index 3bb1e90..274f22d 100644 (file)
@@ -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)
index 94f90b6..e646e38 100644 (file)
 
 /* 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 ++;
index 25e441b..f0654fe 100644 (file)
@@ -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);
index bada498..8a7d998 100644 (file)
@@ -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...");