chiark / gitweb /
journal: after verification output validated time range
[elogind.git] / src / journal / journalctl.c
index 62bb904dbe9e5433528bea4c71701521c23c8633..5c21ab0adffd7dbeaf50041da32787b26b3ff797 100644 (file)
 #include "logs-show.h"
 #include "strv.h"
 #include "journal-internal.h"
+#include "journal-def.h"
+#include "journal-verify.h"
+#include "journal-authenticate.h"
+#include "fsprg.h"
+
+#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
 
 static OutputMode arg_output = OUTPUT_SHORT;
 static bool arg_follow = false;
@@ -48,34 +54,47 @@ static bool arg_show_all = false;
 static bool arg_no_pager = false;
 static int arg_lines = -1;
 static bool arg_no_tail = false;
-static bool arg_new_id128 = false;
-static bool arg_print_header = false;
 static bool arg_quiet = false;
 static bool arg_local = false;
 static bool arg_this_boot = false;
 static const char *arg_directory = NULL;
 static int arg_priorities = 0xFF;
+static const char *arg_verify_key = NULL;
+static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC;
+
+static enum {
+        ACTION_SHOW,
+        ACTION_NEW_ID128,
+        ACTION_PRINT_HEADER,
+        ACTION_SETUP_KEYS,
+        ACTION_VERIFY
+} arg_action = ACTION_SHOW;
 
 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"
-               "     --header         Show journal header information\n"
-               "     --new-id128      Generate a new 128 Bit id\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"
+               "     --setup-keys        Generate new FSS key pair\n"
+               "       --interval=TIME   Time interval for changing the FSS sealing key\n"
+               "     --verify            Verify journal file consistency\n"
+               "       --verify-key=KEY  Specify FSS verification key\n",
                program_invocation_short_name);
 
         return 0;
@@ -88,26 +107,34 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_NO_PAGER,
                 ARG_NO_TAIL,
                 ARG_NEW_ID128,
-                ARG_HEADER
+                ARG_HEADER,
+                ARG_SETUP_KEYS,
+                ARG_INTERVAL,
+                ARG_VERIFY,
+                ARG_VERIFY_KEY
         };
 
         static const struct option options[] = {
-                { "help",      no_argument,       NULL, 'h'           },
-                { "version" ,  no_argument,       NULL, ARG_VERSION   },
-                { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
-                { "follow",    no_argument,       NULL, 'f'           },
-                { "output",    required_argument, NULL, 'o'           },
-                { "all",       no_argument,       NULL, 'a'           },
-                { "lines",     required_argument, NULL, 'n'           },
-                { "no-tail",   no_argument,       NULL, ARG_NO_TAIL   },
-                { "new-id128", no_argument,       NULL, ARG_NEW_ID128 },
-                { "quiet",     no_argument,       NULL, 'q'           },
-                { "local",     no_argument,       NULL, 'l'           },
-                { "this-boot", no_argument,       NULL, 'b'           },
-                { "directory", required_argument, NULL, 'D'           },
-                { "header",    no_argument,       NULL, ARG_HEADER    },
-                { "priority",  no_argument,       NULL, 'p'           },
-                { NULL,        0,                 NULL, 0             }
+                { "help",         no_argument,       NULL, 'h'              },
+                { "version" ,     no_argument,       NULL, ARG_VERSION      },
+                { "no-pager",     no_argument,       NULL, ARG_NO_PAGER     },
+                { "follow",       no_argument,       NULL, 'f'              },
+                { "output",       required_argument, NULL, 'o'              },
+                { "all",          no_argument,       NULL, 'a'              },
+                { "lines",        required_argument, NULL, 'n'              },
+                { "no-tail",      no_argument,       NULL, ARG_NO_TAIL      },
+                { "new-id128",    no_argument,       NULL, ARG_NEW_ID128    },
+                { "quiet",        no_argument,       NULL, 'q'              },
+                { "local",        no_argument,       NULL, 'l'              },
+                { "this-boot",    no_argument,       NULL, 'b'              },
+                { "directory",    required_argument, NULL, 'D'              },
+                { "header",       no_argument,       NULL, ARG_HEADER       },
+                { "priority",     no_argument,       NULL, 'p'              },
+                { "setup-keys",   no_argument,       NULL, ARG_SETUP_KEYS   },
+                { "interval",     required_argument, NULL, ARG_INTERVAL     },
+                { "verify",       no_argument,       NULL, ARG_VERIFY       },
+                { "verify-key",   required_argument, NULL, ARG_VERIFY_KEY   },
+                { NULL,           0,                 NULL, 0                }
         };
 
         int c, r;
@@ -163,7 +190,7 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_NEW_ID128:
-                        arg_new_id128 = true;
+                        arg_action = ACTION_NEW_ID128;
                         break;
 
                 case 'q':
@@ -183,7 +210,28 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_HEADER:
-                        arg_print_header = true;
+                        arg_action = ACTION_PRINT_HEADER;
+                        break;
+
+                case ARG_SETUP_KEYS:
+                        arg_action = ACTION_SETUP_KEYS;
+                        break;
+
+                case ARG_VERIFY:
+                        arg_action = ACTION_VERIFY;
+                        break;
+
+                case ARG_VERIFY_KEY:
+                        arg_action = ACTION_VERIFY;
+                        arg_verify_key = optarg;
+                        break;
+
+                case ARG_INTERVAL:
+                        r = parse_usec(optarg, &arg_interval);
+                        if (r < 0 || arg_interval <= 0) {
+                                log_error("Failed to parse sealing key change interval: %s", optarg);
+                                return -EINVAL;
+                        }
                         break;
 
                 case 'p': {
@@ -400,6 +448,205 @@ static int add_priorities(sd_journal *j) {
         return 0;
 }
 
+static int setup_keys(void) {
+#ifdef HAVE_GCRYPT
+        size_t mpk_size, seed_size, state_size, i;
+        uint8_t *mpk, *seed, *state;
+        ssize_t l;
+        int fd = -1, r;
+        sd_id128_t machine, boot;
+        char *p = NULL, *k = NULL;
+        struct FSSHeader h;
+        uint64_t n;
+
+        r = sd_id128_get_machine(&machine);
+        if (r < 0) {
+                log_error("Failed to get machine ID: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_id128_get_boot(&boot);
+        if (r < 0) {
+                log_error("Failed to get boot ID: %s", strerror(-r));
+                return r;
+        }
+
+        if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
+                     SD_ID128_FORMAT_VAL(machine)) < 0)
+                return log_oom();
+
+        if (access(p, F_OK) >= 0) {
+                log_error("Evolving key file %s exists already.", p);
+                r = -EEXIST;
+                goto finish;
+        }
+
+        if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX",
+                     SD_ID128_FORMAT_VAL(machine)) < 0) {
+                r = log_oom();
+                goto finish;
+        }
+
+        mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR);
+        mpk = alloca(mpk_size);
+
+        seed_size = FSPRG_RECOMMENDED_SEEDLEN;
+        seed = alloca(seed_size);
+
+        state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
+        state = alloca(state_size);
+
+        fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0) {
+                log_error("Failed to open /dev/random: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        log_info("Generating seed...");
+        l = loop_read(fd, seed, seed_size, true);
+        if (l < 0 || (size_t) l != seed_size) {
+                log_error("Failed to read random seed: %s", strerror(EIO));
+                r = -EIO;
+                goto finish;
+        }
+
+        log_info("Generating key pair...");
+        FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR);
+
+        log_info("Generating sealing key...");
+        FSPRG_GenState0(state, mpk, seed, seed_size);
+
+        assert(arg_interval > 0);
+
+        n = now(CLOCK_REALTIME);
+        n /= arg_interval;
+
+        close_nointr_nofail(fd);
+        fd = mkostemp(k, O_WRONLY|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0) {
+                log_error("Failed to open %s: %m", k);
+                r = -errno;
+                goto finish;
+        }
+
+        zero(h);
+        memcpy(h.signature, "KSHHRHLP", 8);
+        h.machine_id = machine;
+        h.boot_id = boot;
+        h.header_size = htole64(sizeof(h));
+        h.start_usec = htole64(n * arg_interval);
+        h.interval_usec = htole64(arg_interval);
+        h.fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR);
+        h.fsprg_state_size = htole64(state_size);
+
+        l = loop_write(fd, &h, sizeof(h), false);
+        if (l < 0 || (size_t) l != sizeof(h)) {
+                log_error("Failed to write header: %s", strerror(EIO));
+                r = -EIO;
+                goto finish;
+        }
+
+        l = loop_write(fd, state, state_size, false);
+        if (l < 0 || (size_t) l != state_size) {
+                log_error("Failed to write state: %s", strerror(EIO));
+                r = -EIO;
+                goto finish;
+        }
+
+        if (link(k, p) < 0) {
+                log_error("Failed to link file: %m");
+                r = -errno;
+                goto finish;
+        }
+
+        if (isatty(STDOUT_FILENO)) {
+                fprintf(stderr,
+                        "\n"
+                        "The new key pair has been generated. The " ANSI_HIGHLIGHT_ON "secret sealing key" ANSI_HIGHLIGHT_OFF " has been written to\n"
+                        "the following local file. It should not be used on multiple hosts.\n"
+                        "\n"
+                        "\t%s\n"
+                        "\n"
+                        "Please write down the following " ANSI_HIGHLIGHT_ON "secret verification key" ANSI_HIGHLIGHT_OFF ". It should be stored\n"
+                        "at a safe location and should not be saved locally on disk.\n"
+                        "\n\t" ANSI_HIGHLIGHT_RED_ON, p);
+                fflush(stderr);
+        }
+        for (i = 0; i < seed_size; i++) {
+                if (i > 0 && i % 3 == 0)
+                        putchar('-');
+                printf("%02x", ((uint8_t*) seed)[i]);
+        }
+
+        printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) arg_interval);
+
+        if (isatty(STDOUT_FILENO)) {
+                char tsb[FORMAT_TIMESPAN_MAX];
+
+                fprintf(stderr,
+                        ANSI_HIGHLIGHT_OFF "\n"
+                        "The sealing key is automatically changed every %s.\n",
+                        format_timespan(tsb, sizeof(tsb), arg_interval));
+        }
+
+        r = 0;
+
+finish:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        if (k) {
+                unlink(k);
+                free(k);
+        }
+
+        free(p);
+
+        return r;
+#else
+        log_error("Forward-secure journal verification not available.");
+#endif
+}
+
+static int verify(sd_journal *j) {
+        int r = 0;
+        Iterator i;
+        JournalFile *f;
+
+        assert(j);
+
+        HASHMAP_FOREACH(f, j->files, i) {
+                int k;
+                usec_t from, to, total;
+
+#ifdef HAVE_GCRYPT
+                if (!arg_verify_key && journal_file_fss_enabled(f))
+                        log_warning("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path);
+#endif
+
+                k = journal_file_verify(f, arg_verify_key, &from, &to, &total);
+                if (k == -EINVAL) {
+                        /* If the key was invalid give up right-away. */
+                        return k;
+                } else if (k < 0) {
+                        log_warning("FAIL: %s (%s)", f->path, strerror(-k));
+                        r = k;
+                } else {
+                        char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX], c[FORMAT_TIMESPAN_MAX];
+                        log_info("PASS: %s", f->path);
+
+                        if (journal_file_fss_enabled(f))
+                                log_info("=> Validated from %s to %s, %s missing",
+                                         format_timestamp(a, sizeof(a), from),
+                                         format_timestamp(b, sizeof(b), to),
+                                         format_timespan(c, sizeof(c), total > to ? total - to : 0));
+                }
+        }
+
+        return r;
+}
+
 int main(int argc, char *argv[]) {
         int r;
         sd_journal *j = NULL;
@@ -416,15 +663,15 @@ int main(int argc, char *argv[]) {
         if (r <= 0)
                 goto finish;
 
-        if (arg_new_id128) {
+        if (arg_action == ACTION_NEW_ID128) {
                 r = generate_new_id128();
                 goto finish;
         }
 
-#ifdef HAVE_ACL
-        if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
-                log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
-#endif
+        if (arg_action == ACTION_SETUP_KEYS) {
+                r = setup_keys();
+                goto finish;
+        }
 
         if (arg_directory)
                 r = sd_journal_open_directory(&j, arg_directory, 0);
@@ -436,12 +683,22 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
-        if (arg_print_header) {
+        if (arg_action == ACTION_VERIFY) {
+                r = verify(j);
+                goto finish;
+        }
+
+        if (arg_action == ACTION_PRINT_HEADER) {
                 journal_print_header(j);
                 r = 0;
                 goto finish;
         }
 
+#ifdef HAVE_ACL
+        if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
+                log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
+#endif
+
         r = add_this_boot(j);
         if (r < 0)
                 goto finish;