chiark / gitweb /
journald: initial version of FSPRG hookup
authorLennart Poettering <lennart@poettering.net>
Mon, 13 Aug 2012 18:31:10 +0000 (20:31 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 13 Aug 2012 18:31:10 +0000 (20:31 +0200)
This adds forward-secure authentication of journal files. This patch
includes key generation as well as tagging of journal files,
Verification of journal files will be added in a later patch.

13 files changed:
Makefile.am
TODO
configure.ac
src/journal/fsprg.c [new file with mode: 0644]
src/journal/fsprg.h [new file with mode: 0644]
src/journal/journal-def.h
src/journal/journal-file.c
src/journal/journal-file.h
src/journal/journalctl.c
src/journal/journald.c
src/journal/sd-journal.c
src/journal/test-journal-stream.c
src/journal/test-journal.c

index 9062dd6..837bc6c 100644 (file)
@@ -2323,14 +2323,13 @@ systemd_journald_SOURCES = \
 nodist_systemd_journald_SOURCES = \
        src/journal/journald-gperf.c
 
-systemd_journald_CFLAGS =
-
 systemd_journald_LDADD = \
        libsystemd-label.la \
        libsystemd-shared.la \
        libsystemd-audit.la \
        libsystemd-daemon.la \
-       libsystemd-id128-internal.la
+       libsystemd-id128-internal.la \
+       libsystemd-journal-internal.la
 
 if ENABLE_LOGIND
 systemd_journald_LDADD += \
@@ -2342,18 +2341,6 @@ systemd_journald_LDADD += \
        libsystemd-acl.la
 endif
 
-if HAVE_XZ
-systemd_journald_SOURCES += \
-       src/journal/compress.c
-
-systemd_journald_CFLAGS += \
-       $(AM_CFLAGS) \
-       $(XZ_CFLAGS)
-
-systemd_journald_LDADD += \
-       $(XZ_LIBS)
-endif
-
 systemd_cat_SOURCES = \
        src/journal/cat.c
 
@@ -2364,6 +2351,9 @@ systemd_cat_LDADD = \
 journalctl_SOURCES = \
        src/journal/journalctl.c
 
+journalctl_CFLAGS = \
+       $(AM_CFLAGS)
+
 journalctl_LDADD = \
        libsystemd-shared.la \
        libsystemd-journal-internal.la \
@@ -2425,26 +2415,49 @@ libsystemd_journal_la_LIBADD = \
 libsystemd_journal_internal_la_SOURCES = \
        $(libsystemd_journal_la_SOURCES)
 
+libsystemd_journal_internal_la_CFLAGS = \
+       $(AM_CFLAGS)
+
+libsystemd_journal_internal_la_LIBADD =
+
 if HAVE_XZ
 libsystemd_journal_la_SOURCES += \
        src/journal/compress.c
 
 libsystemd_journal_la_CFLAGS += \
-       $(AM_CFLAGS) \
        $(XZ_CFLAGS)
 
 libsystemd_journal_la_LIBADD += \
        $(XZ_LIBS)
 
-libsystemd_journal_internal_la_CFLAGS = \
-       $(AM_CFLAGS)
+libsystemd_journal_internal_la_CFLAGS += \
        $(XZ_CFLAGS)
 
-libsystemd_journal_internal_la_LIBADD = \
+libsystemd_journal_internal_la_LIBADD += \
        $(XZ_LIBS)
 
 endif
 
+if HAVE_GCRYPT
+libsystemd_journal_la_SOURCES += \
+       src/journal/fsprg.c \
+       src/journal/fsprg.h
+
+libsystemd_journal_la_CFLAGS += \
+       $(GCRYPT_CFLAGS) \
+       -Wno-pointer-arith
+
+libsystemd_journal_la_LIBADD += \
+       $(GCRYPT_LIBS)
+
+libsystemd_journal_internal_la_CFLAGS += \
+       $(GCRYPT_CFLAGS) \
+       -Wno-pointer-arith
+
+libsystemd_journal_internal_la_LIBADD += \
+       $(GCRYPT_LIBS)
+endif
+
 # move lib from $(libdir) to $(rootlibdir) and update devel link, if needed
 libsystemd-journal-install-hook:
        if test "$(libdir)" != "$(rootlibdir)"; then \
diff --git a/TODO b/TODO
index 2467ea8..0a36aee 100644 (file)
--- a/TODO
+++ b/TODO
@@ -49,6 +49,8 @@ Bugfixes:
 
 Features:
 
+* shutdown: don't read-only mount anything when running in container
+
 * nspawn: --read-only is not applied recursively to submounts
 
 * MountFlags=shared acts as MountFlags=slave right now.
index 50176e1..c1e88da 100644 (file)
@@ -302,6 +302,39 @@ AC_SUBST(ACL_LIBS)
 AM_CONDITIONAL([HAVE_ACL], [test "x$have_acl" != xno])
 
 # ------------------------------------------------------------------------------
+AC_ARG_ENABLE([],
+        AS_HELP_STRING([--disable-gcrypt],[Disable optional GCRYPT support]),
+                [case "${enableval}" in
+                        yes) have_gcrypt=yes ;;
+                        no) have_gcrypt=no ;;
+                        *) AC_MSG_ERROR(bad value ${enableval} for --disable-gcrypt) ;;
+                esac],
+                [have_gcrypt=auto])
+
+if test "x${have_gcrypt}" != xno ; then
+        AM_PATH_LIBGCRYPT(
+                [1.4.5],
+                [have_gcrypt=yes],
+                [if test "x$have_gcrypt" = xyes ; then
+                        AC_MSG_ERROR([*** GCRYPT headers not found.])
+                fi])
+
+        if test "x$have_gcrypt" = xyes ; then
+                GCRYPT_LIBS="$LIBGCRYPT_LIBS"
+                GCRYPT_CFLAGS="$LIBGCRYPT_CFLAGS"
+                AC_DEFINE(HAVE_GCRYPT, 1, [GCRYPT available])
+        else
+                have_gcrypt=no
+        fi
+else
+        GCRYPT_LIBS=
+        GCRYPT_CFLAGS=
+fi
+AC_SUBST(GCRYPT_LIBS)
+AC_SUBST(GCRYPT_CFLAGS)
+AM_CONDITIONAL([HAVE_GCRYPT], [test "x$have_gcrypt" != xno])
+
+# ------------------------------------------------------------------------------
 AC_ARG_ENABLE([audit],
         AS_HELP_STRING([--disable-audit],[Disable optional AUDIT support]),
                 [case "${enableval}" in
@@ -726,6 +759,7 @@ AC_MSG_RESULT([
         SELinux:                 ${have_selinux}
         XZ:                      ${have_xz}
         ACL:                     ${have_acl}
+        GCRYPT:                  ${have_gcrypt}
         binfmt:                  ${have_binfmt}
         vconsole:                ${have_vconsole}
         readahead:               ${have_readahead}
diff --git a/src/journal/fsprg.c b/src/journal/fsprg.c
new file mode 100644 (file)
index 0000000..34ce3be
--- /dev/null
@@ -0,0 +1,384 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/*
+ * fsprg v0.1  -  (seekable) forward-secure pseudorandom generator
+ * Copyright (C) 2012 B. Poettering
+ * Contact: fsprg@point-at-infinity.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301  USA
+ *
+ */
+
+#include <gcrypt.h>
+#include <string.h>
+#include <assert.h>
+
+#include "fsprg.h"
+
+#define ISVALID_SECPAR(secpar) (((secpar) % 16 == 0) && ((secpar) >= 16) && ((secpar) <= 16384))
+#define VALIDATE_SECPAR(secpar) assert(ISVALID_SECPAR(secpar));
+
+#define RND_HASH GCRY_MD_SHA256
+#define RND_GEN_P 0x01
+#define RND_GEN_Q 0x02
+#define RND_GEN_X 0x03
+
+/******************************************************************************/
+
+static void mpi_export(void *buf, size_t buflen, const gcry_mpi_t x) {
+        unsigned len;
+        size_t nwritten;
+
+        assert(gcry_mpi_cmp_ui(x, 0) >= 0);
+        len = (gcry_mpi_get_nbits(x) + 7) / 8;
+        assert(len <= buflen);
+        memset(buf, 0, buflen);
+        gcry_mpi_print(GCRYMPI_FMT_USG, buf + (buflen - len), len, &nwritten, x);
+        assert(nwritten == len);
+}
+
+static gcry_mpi_t mpi_import(const void *buf, size_t buflen) {
+        gcry_mpi_t h;
+        unsigned len;
+
+        gcry_mpi_scan(&h, GCRYMPI_FMT_USG, buf, buflen, NULL);
+        len = (gcry_mpi_get_nbits(h) + 7) / 8;
+        assert(len <= buflen);
+        assert(gcry_mpi_cmp_ui(h, 0) >= 0);
+
+        return h;
+}
+
+static void uint64_export(void *buf, size_t buflen, uint64_t x) {
+        assert(buflen == 8);
+        ((uint8_t*) buf)[0] = (x >> 56) & 0xff;
+        ((uint8_t*) buf)[1] = (x >> 48) & 0xff;
+        ((uint8_t*) buf)[2] = (x >> 40) & 0xff;
+        ((uint8_t*) buf)[3] = (x >> 32) & 0xff;
+        ((uint8_t*) buf)[4] = (x >> 24) & 0xff;
+        ((uint8_t*) buf)[5] = (x >> 16) & 0xff;
+        ((uint8_t*) buf)[6] = (x >>  8) & 0xff;
+        ((uint8_t*) buf)[7] = (x >>  0) & 0xff;
+}
+
+static uint64_t uint64_import(const void *buf, size_t buflen) {
+        assert(buflen == 8);
+        return
+                (uint64_t)(((uint8_t*) buf)[0]) << 56 |
+                (uint64_t)(((uint8_t*) buf)[1]) << 48 |
+                (uint64_t)(((uint8_t*) buf)[2]) << 40 |
+                (uint64_t)(((uint8_t*) buf)[3]) << 32 |
+                (uint64_t)(((uint8_t*) buf)[4]) << 24 |
+                (uint64_t)(((uint8_t*) buf)[5]) << 16 |
+                (uint64_t)(((uint8_t*) buf)[6]) <<  8 |
+                (uint64_t)(((uint8_t*) buf)[7]) <<  0;
+}
+
+/* deterministically generate from seed/idx a string of buflen pseudorandom bytes */
+static void det_randomize(void *buf, size_t buflen, const void *seed, size_t seedlen, uint32_t idx) {
+        gcry_md_hd_t hd, hd2;
+        size_t olen, cpylen;
+        uint32_t ctr;
+
+        olen = gcry_md_get_algo_dlen(RND_HASH);
+        gcry_md_open(&hd, RND_HASH, 0);
+        gcry_md_write(hd, seed, seedlen);
+        gcry_md_putc(hd, (idx >> 24) & 0xff);
+        gcry_md_putc(hd, (idx >> 16) & 0xff);
+        gcry_md_putc(hd, (idx >>  8) & 0xff);
+        gcry_md_putc(hd, (idx >>  0) & 0xff);
+
+        for (ctr = 0; buflen; ctr++) {
+                gcry_md_copy(&hd2, hd);
+                gcry_md_putc(hd2, (ctr >> 24) & 0xff);
+                gcry_md_putc(hd2, (ctr >> 16) & 0xff);
+                gcry_md_putc(hd2, (ctr >>  8) & 0xff);
+                gcry_md_putc(hd2, (ctr >>  0) & 0xff);
+                gcry_md_final(hd2);
+                cpylen = (buflen < olen) ? buflen : olen;
+                memcpy(buf, gcry_md_read(hd2, RND_HASH), cpylen);
+                gcry_md_close(hd2);
+                buf += cpylen;
+                buflen -= cpylen;
+        }
+        gcry_md_close(hd);
+}
+
+/* deterministically generate from seed/idx a prime of length `bits' that is 3 (mod 4) */
+static gcry_mpi_t genprime3mod4(int bits, const void *seed, size_t seedlen, uint32_t idx) {
+        size_t buflen = bits / 8;
+        uint8_t buf[buflen];
+        gcry_mpi_t p;
+
+        assert(bits % 8 == 0);
+        assert(buflen > 0);
+
+        det_randomize(buf, buflen, seed, seedlen, idx);
+        buf[0] |= 0xc0; /* set upper two bits, so that n=pq has maximum size */
+        buf[buflen - 1] |= 0x03; /* set lower two bits, to have result 3 (mod 4) */
+
+        p = mpi_import(buf, buflen);
+        while (gcry_prime_check(p, 0))
+                gcry_mpi_add_ui(p, p, 4);
+
+        return p;
+}
+
+/* deterministically generate from seed/idx a quadratic residue (mod n) */
+static gcry_mpi_t gensquare(const gcry_mpi_t n, const void *seed, size_t seedlen, uint32_t idx, unsigned secpar) {
+        size_t buflen = secpar / 8;
+        uint8_t buf[buflen];
+        gcry_mpi_t x;
+
+        det_randomize(buf, buflen, seed, seedlen, idx);
+        buf[0] &= 0x7f; /* clear upper bit, so that we have x < n */
+        x = mpi_import(buf, buflen);
+        assert(gcry_mpi_cmp(x, n) < 0);
+        gcry_mpi_mulm(x, x, x, n);
+        return x;
+}
+
+/* compute 2^m (mod phi(p)), for a prime p */
+static gcry_mpi_t twopowmodphi(uint64_t m, const gcry_mpi_t p) {
+        gcry_mpi_t phi, r;
+        int n;
+
+        phi = gcry_mpi_new(0);
+        gcry_mpi_sub_ui(phi, p, 1);
+
+        /* count number of used bits in m */
+        for (n = 0; ((uint64_t)1 << n) <= m; n++)
+                ;
+
+        r = gcry_mpi_new(0);
+        gcry_mpi_set_ui(r, 1);
+        while (n) { /* square and multiply algorithm for fast exponentiation */
+                n--;
+                gcry_mpi_mulm(r, r, r, phi);
+                if (m & ((uint64_t)1 << n)) {
+                        gcry_mpi_add(r, r, r);
+                        if (gcry_mpi_cmp(r, phi) >= 0)
+                                gcry_mpi_sub(r, r, phi);
+                }
+        }
+
+        gcry_mpi_release(phi);
+        return r;
+}
+
+/* Decompose $x \in Z_n$ into $(xp,xq) \in Z_p \times Z_q$ using Chinese Remainder Theorem */
+static void CRT_decompose(gcry_mpi_t *xp, gcry_mpi_t *xq, const gcry_mpi_t x, const gcry_mpi_t p, const gcry_mpi_t q) {
+        *xp = gcry_mpi_new(0);
+        *xq = gcry_mpi_new(0);
+        gcry_mpi_mod(*xp, x, p);
+        gcry_mpi_mod(*xq, x, q);
+}
+
+/* Compose $(xp,xq) \in Z_p \times Z_q$ into $x \in Z_n$ using Chinese Remainder Theorem */
+static void CRT_compose(gcry_mpi_t *x, const gcry_mpi_t xp, const gcry_mpi_t xq, const gcry_mpi_t p, const gcry_mpi_t q) {
+        gcry_mpi_t a, u;
+
+        a = gcry_mpi_new(0);
+        u = gcry_mpi_new(0);
+        *x = gcry_mpi_new(0);
+        gcry_mpi_subm(a, xq, xp, q);
+        gcry_mpi_invm(u, p, q);
+        gcry_mpi_mulm(a, a, u, q); /* a = (xq - xp) / p  (mod q) */
+        gcry_mpi_mul(*x, p, a);
+        gcry_mpi_add(*x, *x, xp); /* x = p * ((xq - xp) / p mod q) + xp */
+        gcry_mpi_release(a);
+        gcry_mpi_release(u);
+}
+
+static void initialize_libgcrypt(void) {
+        const char *p;
+        if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
+                return;
+
+        p = gcry_check_version("1.4.5");
+        assert(p);
+
+        /* Turn off "secmem". Clients which whish to make use of this
+         * feature should initialize the library manually */
+        gcry_control(GCRYCTL_DISABLE_SECMEM);
+        gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+}
+
+/******************************************************************************/
+
+size_t FSPRG_mskinbytes(unsigned _secpar) {
+        VALIDATE_SECPAR(_secpar);
+        return 2 + 2 * (_secpar / 2) / 8; /* to store header,p,q */
+}
+
+size_t FSPRG_mpkinbytes(unsigned _secpar) {
+        VALIDATE_SECPAR(_secpar);
+        return 2 + _secpar / 8; /* to store header,n */
+}
+
+size_t FSPRG_stateinbytes(unsigned _secpar) {
+        VALIDATE_SECPAR(_secpar);
+        return 2 + 2 * _secpar / 8 + 8; /* to store header,n,x,epoch */
+}
+
+static void store_secpar(void *buf, uint16_t secpar) {
+        secpar = secpar / 16 - 1;
+        ((uint8_t*) buf)[0] = (secpar >> 8) & 0xff;
+        ((uint8_t*) buf)[1] = (secpar >> 0) & 0xff;
+}
+
+static uint16_t read_secpar(const void *buf) {
+        uint16_t secpar;
+        secpar =
+                (uint16_t)(((uint8_t*) buf)[0]) << 8 |
+                (uint16_t)(((uint8_t*) buf)[1]) << 0;
+        return 16 * (secpar + 1);
+}
+
+void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned _secpar) {
+        uint8_t iseed[FSPRG_RECOMMENDED_SEEDLEN];
+        gcry_mpi_t n, p, q;
+        uint16_t secpar;
+
+        VALIDATE_SECPAR(_secpar);
+        secpar = _secpar;
+
+        initialize_libgcrypt();
+
+        if (!seed) {
+                gcry_randomize(iseed, FSPRG_RECOMMENDED_SEEDLEN, GCRY_STRONG_RANDOM);
+                seed = iseed;
+                seedlen = FSPRG_RECOMMENDED_SEEDLEN;
+        }
+
+        p = genprime3mod4(secpar / 2, seed, seedlen, RND_GEN_P);
+        q = genprime3mod4(secpar / 2, seed, seedlen, RND_GEN_Q);
+
+        if (msk) {
+                store_secpar(msk + 0, secpar);
+                mpi_export(msk + 2 + 0 * (secpar / 2) / 8, (secpar / 2) / 8, p);
+                mpi_export(msk + 2 + 1 * (secpar / 2) / 8, (secpar / 2) / 8, q);
+        }
+
+        if (mpk) {
+                n = gcry_mpi_new(0);
+                gcry_mpi_mul(n, p, q);
+                assert(gcry_mpi_get_nbits(n) == secpar);
+
+                store_secpar(mpk + 0, secpar);
+                mpi_export(mpk + 2, secpar / 8, n);
+
+                gcry_mpi_release(n);
+        }
+
+        gcry_mpi_release(p);
+        gcry_mpi_release(q);
+}
+
+void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen) {
+        gcry_mpi_t n, x;
+        uint16_t secpar;
+
+        initialize_libgcrypt();
+
+        secpar = read_secpar(mpk + 0);
+        n = mpi_import(mpk + 2, secpar / 8);
+        x = gensquare(n, seed, seedlen, RND_GEN_X, secpar);
+
+        memcpy(state, mpk, 2 + secpar / 8);
+        mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x);
+        memset(state + 2 + 2 * secpar / 8, 0, 8);
+
+        gcry_mpi_release(n);
+        gcry_mpi_release(x);
+}
+
+void FSPRG_Evolve(void *state) {
+        gcry_mpi_t n, x;
+        uint16_t secpar;
+        uint64_t epoch;
+
+        initialize_libgcrypt();
+
+        secpar = read_secpar(state + 0);
+        n = mpi_import(state + 2 + 0 * secpar / 8, secpar / 8);
+        x = mpi_import(state + 2 + 1 * secpar / 8, secpar / 8);
+        epoch = uint64_import(state + 2 + 2 * secpar / 8, 8);
+
+        gcry_mpi_mulm(x, x, x, n);
+        epoch++;
+
+        mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x);
+        uint64_export(state + 2 + 2 * secpar / 8, 8, epoch);
+
+        gcry_mpi_release(n);
+        gcry_mpi_release(x);
+}
+
+uint64_t FSPRG_GetEpoch(const void *state) {
+        uint16_t secpar;
+        secpar = read_secpar(state + 0);
+        return uint64_import(state + 2 + 2 * secpar / 8, 8);
+}
+
+void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen) {
+        gcry_mpi_t p, q, n, x, xp, xq, kp, kq, xm;
+        uint16_t secpar;
+
+        initialize_libgcrypt();
+
+        secpar = read_secpar(msk + 0);
+        p  = mpi_import(msk + 2 + 0 * (secpar / 2) / 8, (secpar / 2) / 8);
+        q  = mpi_import(msk + 2 + 1 * (secpar / 2) / 8, (secpar / 2) / 8);
+
+        n = gcry_mpi_new(0);
+        gcry_mpi_mul(n, p, q);
+
+        x = gensquare(n, seed, seedlen, RND_GEN_X, secpar);
+        CRT_decompose(&xp, &xq, x, p, q); /* split (mod n) into (mod p) and (mod q) using CRT */
+
+        kp = twopowmodphi(epoch, p); /* compute 2^epoch (mod phi(p)) */
+        kq = twopowmodphi(epoch, q); /* compute 2^epoch (mod phi(q)) */
+
+        gcry_mpi_powm(xp, xp, kp, p); /* compute x^(2^epoch) (mod p) */
+        gcry_mpi_powm(xq, xq, kq, q); /* compute x^(2^epoch) (mod q) */
+
+        CRT_compose(&xm, xp, xq, p, q); /* combine (mod p) and (mod q) to (mod n) using CRT */
+
+        store_secpar(state + 0, secpar);
+        mpi_export(state + 2 + 0 * secpar / 8, secpar / 8, n);
+        mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, xm);
+        uint64_export(state + 2 + 2 * secpar / 8, 8, epoch);
+
+        gcry_mpi_release(p);
+        gcry_mpi_release(q);
+        gcry_mpi_release(n);
+        gcry_mpi_release(x);
+        gcry_mpi_release(xp);
+        gcry_mpi_release(xq);
+        gcry_mpi_release(kp);
+        gcry_mpi_release(kq);
+        gcry_mpi_release(xm);
+}
+
+void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx) {
+        uint16_t secpar;
+
+        initialize_libgcrypt();
+
+        secpar = read_secpar(state + 0);
+        det_randomize(key, keylen, state + 2, 2 * secpar / 8 + 8, idx);
+}
diff --git a/src/journal/fsprg.h b/src/journal/fsprg.h
new file mode 100644 (file)
index 0000000..306ef18
--- /dev/null
@@ -0,0 +1,64 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef __fsprgh__
+#define __fsprgh__
+
+/*
+ * fsprg v0.1  -  (seekable) forward-secure pseudorandom generator
+ * Copyright (C) 2012 B. Poettering
+ * Contact: fsprg@point-at-infinity.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301  USA
+ *
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define FSPRG_RECOMMENDED_SECPAR 1536
+#define FSPRG_RECOMMENDED_SEEDLEN (96/8)
+
+size_t FSPRG_mskinbytes(unsigned secpar);
+size_t FSPRG_mpkinbytes(unsigned secpar);
+size_t FSPRG_stateinbytes(unsigned secpar);
+
+/* Setup msk and mpk. Providing seed != NULL makes this algorithm deterministic. */
+void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned secpar);
+
+/* Initialize state deterministically in dependence on seed. */
+/* Note: in case one wants to run only one GenState0 per GenMK it is safe to use
+   the same seed for both GenMK and GenState0.
+*/
+void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen);
+
+void FSPRG_Evolve(void *state);
+
+uint64_t FSPRG_GetEpoch(const void *state);
+
+/* Seek to any arbitrary state (by providing msk together with seed from GenState0). */
+void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen);
+
+void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
index 096dd8e..af22e17 100644 (file)
@@ -37,11 +37,13 @@ typedef struct FieldObject FieldObject;
 typedef struct EntryObject EntryObject;
 typedef struct HashTableObject HashTableObject;
 typedef struct EntryArrayObject EntryArrayObject;
-typedef struct SignatureObject SignatureObject;
+typedef struct TagObject TagObject;
 
 typedef struct EntryItem EntryItem;
 typedef struct HashItem HashItem;
 
+typedef struct FSPRGHeader FSPRGHeader;
+
 /* Object types */
 enum {
         OBJECT_UNUSED,
@@ -51,7 +53,7 @@ enum {
         OBJECT_DATA_HASH_TABLE,
         OBJECT_FIELD_HASH_TABLE,
         OBJECT_ENTRY_ARRAY,
-        OBJECT_SIGNATURE,
+        OBJECT_TAG,
         _OBJECT_TYPE_MAX
 };
 
@@ -84,7 +86,6 @@ _packed_ struct FieldObject {
         le64_t hash;
         le64_t next_hash_offset;
         le64_t head_data_offset;
-        le64_t tail_data_offset;
         uint8_t payload[];
 };
 
@@ -119,12 +120,11 @@ _packed_ struct EntryArrayObject {
         le64_t items[];
 };
 
-#define SIGNATURE_LENGTH 160
+#define TAG_LENGTH (256/8)
 
-_packed_ struct SignatureObject {
+_packed_ struct TagObject {
         ObjectHeader object;
-        le64_t from;
-        uint8_t signature[SIGNATURE_LENGTH];
+        uint8_t tag[TAG_LENGTH]; /* SHA-256 HMAC */
 };
 
 union Object {
@@ -134,7 +134,7 @@ union Object {
         EntryObject entry;
         HashTableObject hash_table;
         EntryArrayObject entry_array;
-        SignatureObject signature;
+        TagObject tag;
 };
 
 enum {
@@ -149,17 +149,19 @@ enum {
 };
 
 enum {
-        HEADER_COMPATIBLE_SIGNED = 1
+        HEADER_COMPATIBLE_AUTHENTICATED = 1
 };
 
+#define HEADER_SIGNATURE ((char[]) { 'L', 'P', 'K', 'S', 'H', 'H', 'R', 'H' })
+
 _packed_ struct Header {
         uint8_t signature[8]; /* "LPKSHHRH" */
-        uint32_t compatible_flags;
-        uint32_t incompatible_flags;
+        le32_t compatible_flags;
+        le32_t incompatible_flags;
         uint8_t state;
         uint8_t reserved[7];
         sd_id128_t file_id;
-        sd_id128_t machine_id; /* last writer */
+        sd_id128_t machine_id;
         sd_id128_t boot_id;    /* last writer */
         sd_id128_t seqnum_id;
         le64_t header_size;
@@ -181,3 +183,19 @@ _packed_ struct Header {
         le64_t n_data;
         le64_t n_fields;
 };
+
+#define FSPRG_HEADER_SIGNATURE ((char[]) { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' })
+
+_packed_ struct FSPRGHeader {
+        uint8_t signature[8]; /* "KSHHRHLP" */
+        le32_t compatible_flags;
+        le32_t incompatible_flags;
+        sd_id128_t machine_id;
+        sd_id128_t boot_id;    /* last writer */
+        le64_t header_size;
+        le64_t fsprg_start_usec;
+        le64_t fsprg_interval_usec;
+        le16_t secpar;
+        le16_t reserved[3];
+        le64_t state_size;
+};
index 718dc5d..0e48893 100644 (file)
@@ -31,6 +31,7 @@
 #include "journal-file.h"
 #include "lookup3.h"
 #include "compress.h"
+#include "fsprg.h"
 
 #define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem))
 #define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem))
 #define JOURNAL_HEADER_CONTAINS(h, field) \
         (le64toh((h)->header_size) >= offsetof(Header, field) + sizeof((h)->field))
 
-static const char signature[] = { 'L', 'P', 'K', 'S', 'H', 'H', 'R', 'H' };
+static int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime);
 
 void journal_file_close(JournalFile *f) {
         int t;
 
         assert(f);
 
+        /* Sync everything to disk, before we mark the file offline */
+        for (t = 0; t < _WINDOW_MAX; t++)
+                if (f->windows[t].ptr)
+                        munmap(f->windows[t].ptr, f->windows[t].size);
+
+        if (f->writable && f->fd >= 0)
+                fdatasync(f->fd);
+
         if (f->header) {
                 /* Mark the file offline. Don't override the archived state if it already is set */
                 if (f->writable && f->header->state == STATE_ONLINE)
@@ -81,10 +90,6 @@ void journal_file_close(JournalFile *f) {
                 munmap(f->header, PAGE_ALIGN(sizeof(Header)));
         }
 
-        for (t = 0; t < _WINDOW_MAX; t++)
-                if (f->windows[t].ptr)
-                        munmap(f->windows[t].ptr, f->windows[t].size);
-
         if (f->fd >= 0)
                 close_nointr_nofail(f->fd);
 
@@ -94,6 +99,14 @@ void journal_file_close(JournalFile *f) {
         free(f->compress_buffer);
 #endif
 
+#ifdef HAVE_GCRYPT
+        if (f->fsprg_header)
+                munmap(f->fsprg_header, PAGE_ALIGN(f->fsprg_size));
+
+        if (f->hmac)
+                gcry_md_close(f->hmac);
+#endif
+
         free(f);
 }
 
@@ -105,9 +118,15 @@ static int journal_file_init_header(JournalFile *f, JournalFile *template) {
         assert(f);
 
         zero(h);
-        memcpy(h.signature, signature, 8);
+        memcpy(h.signature, HEADER_SIGNATURE, 8);
         h.header_size = htole64(ALIGN64(sizeof(h)));
 
+        h.incompatible_flags =
+                htole32(f->compress ? HEADER_INCOMPATIBLE_COMPRESSED : 0);
+
+        h.compatible_flags =
+                htole32(f->authenticate ? HEADER_COMPATIBLE_AUTHENTICATED : 0);
+
         r = sd_id128_randomize(&h.file_id);
         if (r < 0)
                 return r;
@@ -149,7 +168,9 @@ static int journal_file_refresh_header(JournalFile *f) {
 
         f->header->state = STATE_ONLINE;
 
-        __sync_synchronize();
+        /* Sync the online state to disk */
+        msync(f->header, PAGE_ALIGN(sizeof(Header)), MS_SYNC);
+        fdatasync(f->fd);
 
         return 0;
 }
@@ -157,17 +178,31 @@ static int journal_file_refresh_header(JournalFile *f) {
 static int journal_file_verify_header(JournalFile *f) {
         assert(f);
 
-        if (memcmp(f->header, signature, 8))
+        if (memcmp(f->header->signature, HEADER_SIGNATURE, 8))
                 return -EBADMSG;
 
+        /* In both read and write mode we refuse to open files with
+         * incompatible flags we don't know */
 #ifdef HAVE_XZ
-        if ((le64toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) != 0)
+        if ((le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) != 0)
                 return -EPROTONOSUPPORT;
 #else
         if (f->header->incompatible_flags != 0)
                 return -EPROTONOSUPPORT;
 #endif
 
+        /* When open for writing we refuse to open files with
+         * compatible flags, too */
+        if (f->writable) {
+#ifdef HAVE_GCRYPT
+                if ((le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_AUTHENTICATED) != 0)
+                        return -EPROTONOSUPPORT;
+#else
+                if (f->header->compatible_flags != 0)
+                        return -EPROTONOSUPPORT;
+#endif
+        }
+
         /* The first addition was n_data, so check that we are at least this large */
         if (le64toh(f->header->header_size) < HEADER_SIZE_MIN)
                 return -EBADMSG;
@@ -200,6 +235,9 @@ static int journal_file_verify_header(JournalFile *f) {
                 }
         }
 
+        f->compress = !!(le32toh(f->header->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED);
+        f->authenticate = !!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED);
+
         return 0;
 }
 
@@ -781,8 +819,6 @@ static int journal_file_append_data(
                         o->object.size = htole64(offsetof(Object, data.payload) + rsize);
                         o->object.flags |= OBJECT_COMPRESSED;
 
-                        f->header->incompatible_flags = htole32(le32toh(f->header->incompatible_flags) | HEADER_INCOMPATIBLE_COMPRESSED);
-
                         log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize);
                 }
         }
@@ -1057,6 +1093,10 @@ int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const st
             ts->monotonic < le64toh(f->header->tail_entry_monotonic))
                 return -EINVAL;
 
+        r = journal_file_maybe_append_tag(f, ts->realtime);
+        if (r < 0)
+                return r;
+
         /* alloca() can't take 0, hence let's allocate at least one */
         items = alloca(sizeof(EntryItem) * MAX(1, n_iovec));
 
@@ -1832,6 +1872,394 @@ int journal_file_move_to_entry_by_realtime_for_data(
                                              ret, offset, NULL);
 }
 
+static void *fsprg_state(JournalFile *f) {
+        uint64_t a, b;
+        assert(f);
+
+        if (!f->authenticate)
+                return NULL;
+
+        a = le64toh(f->fsprg_header->header_size);
+        b = le64toh(f->fsprg_header->state_size);
+
+        if (a + b > f->fsprg_size)
+                return NULL;
+
+        return (uint8_t*) f->fsprg_header + a;
+}
+
+static int journal_file_append_tag(JournalFile *f) {
+        Object *o;
+        uint64_t p;
+        int r;
+
+        assert(f);
+
+        if (!f->authenticate)
+                return 0;
+
+        if (!f->hmac_running)
+                return 0;
+
+        log_debug("Writing tag for epoch %llu\n", (unsigned long long) FSPRG_GetEpoch(fsprg_state(f)));
+
+        assert(f->hmac);
+
+        r = journal_file_append_object(f, OBJECT_TAG, sizeof(struct TagObject), &o, &p);
+        if (r < 0)
+                return r;
+
+        /* Get the HMAC tag and store it in the object */
+        memcpy(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH);
+        f->hmac_running = false;
+
+        return 0;
+}
+
+static 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)
+                return 0;
+
+        if (f->hmac_running)
+                return 0;
+
+        /* Prepare HMAC for next cycle */
+        gcry_md_reset(f->hmac);
+        FSPRG_GetKey(fsprg_state(f), key, sizeof(key), 0);
+        gcry_md_setkey(f->hmac, key, sizeof(key));
+
+        f->hmac_running = true;
+
+        return 0;
+}
+
+static int journal_file_get_epoch(JournalFile *f, uint64_t realtime, uint64_t *epoch) {
+        uint64_t t;
+
+        assert(f);
+        assert(epoch);
+        assert(f->authenticate);
+
+        if (le64toh(f->fsprg_header->fsprg_start_usec) == 0 ||
+            le64toh(f->fsprg_header->fsprg_interval_usec) == 0)
+                return -ENOTSUP;
+
+        if (realtime < le64toh(f->fsprg_header->fsprg_start_usec))
+                return -ESTALE;
+
+        t = realtime - le64toh(f->fsprg_header->fsprg_start_usec);
+        t = t / le64toh(f->fsprg_header->fsprg_interval_usec);
+
+        *epoch = t;
+        return 0;
+}
+
+static int journal_file_need_evolve(JournalFile *f, uint64_t realtime) {
+        uint64_t goal, epoch;
+        int r;
+        assert(f);
+
+        if (!f->authenticate)
+                return 0;
+
+        r = journal_file_get_epoch(f, realtime, &goal);
+        if (r < 0)
+                return r;
+
+        epoch = FSPRG_GetEpoch(fsprg_state(f));
+        if (epoch > goal)
+                return -ESTALE;
+
+        return epoch != goal;
+}
+
+static int journal_file_evolve(JournalFile *f, uint64_t realtime) {
+        uint64_t goal, epoch;
+        int r;
+
+        assert(f);
+
+        if (!f->authenticate)
+                return 0;
+
+        r = journal_file_get_epoch(f, realtime, &goal);
+        if (r < 0)
+                return r;
+
+        epoch = FSPRG_GetEpoch(fsprg_state(f));
+        if (epoch < goal)
+                log_debug("Evolving FSPRG key from epoch %llu to %llu.", (unsigned long long) epoch, (unsigned long long) goal);
+
+        for (;;) {
+                if (epoch > goal)
+                        return -ESTALE;
+                if (epoch == goal)
+                        return 0;
+
+                FSPRG_Evolve(fsprg_state(f));
+                epoch = FSPRG_GetEpoch(fsprg_state(f));
+        }
+}
+
+static int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) {
+        int r;
+
+        assert(f);
+
+        if (!f->authenticate)
+                return 0;
+
+        r = journal_file_need_evolve(f, realtime);
+        if (r <= 0)
+                return 0;
+
+        r = journal_file_append_tag(f);
+        if (r < 0)
+                return r;
+
+        r = journal_file_evolve(f, realtime);
+        if (r < 0)
+                return r;
+
+        r = journal_file_hmac_start(f);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int journal_file_hmac_put_object(JournalFile *f, int type, uint64_t p) {
+        int r;
+        Object *o;
+
+        assert(f);
+
+        if (!f->authenticate)
+                return 0;
+
+        r = journal_file_hmac_start(f);
+        if (r < 0)
+                return r;
+
+        r = journal_file_move_to_object(f, type, p, &o);
+        if (r < 0)
+                return r;
+
+        gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload));
+
+        switch (o->object.type) {
+
+        case OBJECT_DATA:
+                /* All but: entry_array_offset, n_entries are mutable */
+                gcry_md_write(f->hmac, &o->data.hash, offsetof(DataObject, entry_array_offset) - offsetof(DataObject, hash));
+                gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(DataObject, payload));
+                break;
+
+        case OBJECT_ENTRY:
+                /* All */
+                gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(EntryObject, seqnum));
+                break;
+
+        case OBJECT_FIELD_HASH_TABLE:
+        case OBJECT_DATA_HASH_TABLE:
+        case OBJECT_ENTRY_ARRAY:
+                /* Nothing: everything is mutable */
+                break;
+
+        case OBJECT_TAG:
+                /* All */
+                gcry_md_write(f->hmac, o->tag.tag, le64toh(o->object.size) - offsetof(TagObject, tag));
+                break;
+
+        default:
+                return -EINVAL;
+        }
+
+        return 0;
+}
+
+static int journal_file_hmac_put_header(JournalFile *f) {
+        int r;
+
+        assert(f);
+
+        if (!f->authenticate)
+                return 0;
+
+        r = journal_file_hmac_start(f);
+        if (r < 0)
+                return r;
+
+        /* All but state+reserved, boot_id, arena_size,
+         * tail_object_offset, n_objects, n_entries, tail_seqnum,
+         * head_entry_realtime, tail_entry_realtime,
+         * tail_entry_monotonic, n_data, n_fields, header_tag */
+
+        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_seqnum, offsetof(Header, head_entry_realtime) - offsetof(Header, head_seqnum));
+
+        return 0;
+}
+
+static int journal_file_load_fsprg(JournalFile *f) {
+        int r, fd = -1;
+        char *p = NULL;
+        struct stat st;
+        FSPRGHeader *m = NULL;
+        sd_id128_t machine;
+
+        assert(f);
+
+        if (!f->authenticate)
+                return 0;
+
+        r = sd_id128_get_machine(&machine);
+        if (r < 0)
+                return r;
+
+        if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fsprg",
+                     SD_ID128_FORMAT_VAL(machine)) < 0)
+                return -ENOMEM;
+
+        fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
+        if (fd < 0) {
+                log_error("Failed to open %s: %m", p);
+                r = -errno;
+                goto finish;
+        }
+
+        if (fstat(fd, &st) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (st.st_size < (off_t) sizeof(FSPRGHeader)) {
+                r = -ENODATA;
+                goto finish;
+        }
+
+        m = mmap(NULL, PAGE_ALIGN(sizeof(FSPRGHeader)), PROT_READ, MAP_SHARED, fd, 0);
+        if (m == MAP_FAILED) {
+                m = NULL;
+                r = -errno;
+                goto finish;
+        }
+
+        if (memcmp(m->signature, FSPRG_HEADER_SIGNATURE, 8) != 0) {
+                r = -EBADMSG;
+                goto finish;
+        }
+
+        if (m->incompatible_flags != 0) {
+                r = -EPROTONOSUPPORT;
+                goto finish;
+        }
+
+        if (le64toh(m->header_size) < sizeof(FSPRGHeader)) {
+                r = -EBADMSG;
+                goto finish;
+        }
+
+        if (le64toh(m->state_size) != FSPRG_stateinbytes(m->secpar)) {
+                r = -EBADMSG;
+                goto finish;
+        }
+
+        f->fsprg_size = le64toh(m->header_size) + le64toh(m->state_size);
+        if ((uint64_t) st.st_size < f->fsprg_size) {
+                r = -ENODATA;
+                goto finish;
+        }
+
+        if (!sd_id128_equal(machine, m->machine_id)) {
+                r = -EHOSTDOWN;
+                goto finish;
+        }
+
+        if (le64toh(m->fsprg_start_usec) <= 0 ||
+            le64toh(m->fsprg_interval_usec) <= 0) {
+                r = -EBADMSG;
+                goto finish;
+        }
+
+        f->fsprg_header = mmap(NULL, PAGE_ALIGN(f->fsprg_size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+        if (f->fsprg_header == MAP_FAILED) {
+                f->fsprg_header = NULL;
+                r = -errno;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                munmap(m, PAGE_ALIGN(sizeof(FSPRGHeader)));
+
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        free(p);
+        return r;
+}
+
+static int journal_file_setup_hmac(JournalFile *f) {
+        gcry_error_t e;
+
+        if (!f->authenticate)
+                return 0;
+
+        e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+        if (e != 0)
+                return -ENOTSUP;
+
+        return 0;
+}
+
+static int journal_file_append_first_tag(JournalFile *f) {
+        int r;
+        uint64_t p;
+
+        if (!f->authenticate)
+                return 0;
+
+        log_debug("Calculating first tag...");
+
+        r = journal_file_hmac_put_header(f);
+        if (r < 0)
+                return r;
+
+        p = le64toh(f->header->field_hash_table_offset);
+        if (p < offsetof(Object, hash_table.items))
+                return -EINVAL;
+        p -= offsetof(Object, hash_table.items);
+
+        r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, p);
+        if (r < 0)
+                return r;
+
+        p = le64toh(f->header->data_hash_table_offset);
+        if (p < offsetof(Object, hash_table.items))
+                return -EINVAL;
+        p -= offsetof(Object, hash_table.items);
+
+        r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, p);
+        if (r < 0)
+                return r;
+
+        r = journal_file_append_tag(f);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 void journal_file_dump(JournalFile *f) {
         Object *o;
         int r;
@@ -1876,8 +2304,8 @@ void journal_file_dump(JournalFile *f) {
                         printf("Type: OBJECT_ENTRY_ARRAY\n");
                         break;
 
-                case OBJECT_SIGNATURE:
-                        printf("Type: OBJECT_SIGNATURE\n");
+                case OBJECT_TAG:
+                        printf("Type: OBJECT_TAG\n");
                         break;
                 }
 
@@ -1928,8 +2356,8 @@ void journal_file_print_header(JournalFile *f) {
                f->header->state == STATE_OFFLINE ? "offline" :
                f->header->state == STATE_ONLINE ? "online" :
                f->header->state == STATE_ARCHIVED ? "archived" : "unknown",
-               (f->header->compatible_flags & HEADER_COMPATIBLE_SIGNED) ? " SIGNED" : "",
-               (f->header->compatible_flags & ~HEADER_COMPATIBLE_SIGNED) ? " ???" : "",
+               (f->header->compatible_flags & HEADER_COMPATIBLE_AUTHENTICATED) ? " AUTHENTICATED" : "",
+               (f->header->compatible_flags & ~HEADER_COMPATIBLE_AUTHENTICATED) ? " ???" : "",
                (f->header->incompatible_flags & HEADER_INCOMPATIBLE_COMPRESSED) ? " COMPRESSED" : "",
                (f->header->incompatible_flags & ~HEADER_INCOMPATIBLE_COMPRESSED) ? " ???" : "",
                (unsigned long long) le64toh(f->header->header_size),
@@ -1961,6 +2389,8 @@ int journal_file_open(
                 const char *fname,
                 int flags,
                 mode_t mode,
+                bool compress,
+                bool authenticate,
                 JournalMetrics *metrics,
                 JournalFile *template,
                 JournalFile **ret) {
@@ -1983,13 +2413,13 @@ int journal_file_open(
                 return -ENOMEM;
 
         f->fd = -1;
-        f->flags = flags;
         f->mode = mode;
-        f->writable = (flags & O_ACCMODE) != O_RDONLY;
-        f->prot = prot_from_flags(flags);
 
-        if (template)
-                f->compress = template->compress;
+        f->flags = flags;
+        f->prot = prot_from_flags(flags);
+        f->writable = (flags & O_ACCMODE) != O_RDONLY;
+        f->compress = compress;
+        f->authenticate = authenticate;
 
         f->path = strdup(fname);
         if (!f->path) {
@@ -2011,6 +2441,12 @@ int journal_file_open(
         if (f->last_stat.st_size == 0 && f->writable) {
                 newly_created = true;
 
+                /* Try to load the FSPRG state, and if we can't, then
+                 * just don't do authentication */
+                r = journal_file_load_fsprg(f);
+                if (r < 0)
+                        f->authenticate = false;
+
                 r = journal_file_init_header(f, template);
                 if (r < 0)
                         goto fail;
@@ -2037,6 +2473,10 @@ int journal_file_open(
                 r = journal_file_verify_header(f);
                 if (r < 0)
                         goto fail;
+
+                r = journal_file_load_fsprg(f);
+                if (r < 0)
+                        goto fail;
         }
 
         if (f->writable) {
@@ -2049,10 +2489,13 @@ 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;
         }
 
         if (newly_created) {
-
                 r = journal_file_setup_field_hash_table(f);
                 if (r < 0)
                         goto fail;
@@ -2060,6 +2503,10 @@ int journal_file_open(
                 r = journal_file_setup_data_hash_table(f);
                 if (r < 0)
                         goto fail;
+
+                r = journal_file_append_first_tag(f);
+                if (r < 0)
+                        goto fail;
         }
 
         r = journal_file_map_field_hash_table(f);
@@ -2081,7 +2528,7 @@ fail:
         return r;
 }
 
-int journal_file_rotate(JournalFile **f) {
+int journal_file_rotate(JournalFile **f, bool compress, bool authenticate) {
         char *p;
         size_t l;
         JournalFile *old_file, *new_file = NULL;
@@ -2120,7 +2567,7 @@ int journal_file_rotate(JournalFile **f) {
 
         old_file->header->state = STATE_ARCHIVED;
 
-        r = journal_file_open(old_file->path, old_file->flags, old_file->mode, NULL, old_file, &new_file);
+        r = journal_file_open(old_file->path, old_file->flags, old_file->mode, compress, authenticate, NULL, old_file, &new_file);
         journal_file_close(old_file);
 
         *f = new_file;
@@ -2131,6 +2578,8 @@ int journal_file_open_reliably(
                 const char *fname,
                 int flags,
                 mode_t mode,
+                bool compress,
+                bool authenticate,
                 JournalMetrics *metrics,
                 JournalFile *template,
                 JournalFile **ret) {
@@ -2139,7 +2588,7 @@ int journal_file_open_reliably(
         size_t l;
         char *p;
 
-        r = journal_file_open(fname, flags, mode, metrics, template, ret);
+        r = journal_file_open(fname, flags, mode, compress, authenticate, metrics, template, ret);
         if (r != -EBADMSG && /* corrupted */
             r != -ENODATA && /* truncated */
             r != -EHOSTDOWN && /* other machine */
@@ -2154,6 +2603,9 @@ int journal_file_open_reliably(
         if (!(flags & O_CREAT))
                 return r;
 
+        if (!endswith(fname, ".journal"))
+                return r;
+
         /* The file is corrupted. Rotate it away and try it again (but only once) */
 
         l = strlen(fname);
@@ -2170,7 +2622,7 @@ int journal_file_open_reliably(
 
         log_warning("File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
 
-        return journal_file_open(fname, flags, mode, metrics, template, ret);
+        return journal_file_open(fname, flags, mode, compress, authenticate, metrics, template, ret);
 }
 
 struct vacuum_info {
index eed49e0..25d9720 100644 (file)
 
 #include <inttypes.h>
 
+#ifdef HAVE_GCRYPT
+#include <gcrypt.h>
+#endif
+
 #include <systemd/sd-id128.h>
 
 #include "sparse-endian.h"
@@ -42,7 +46,7 @@ enum {
         WINDOW_DATA_HASH_TABLE = OBJECT_DATA_HASH_TABLE,
         WINDOW_FIELD_HASH_TABLE = OBJECT_FIELD_HASH_TABLE,
         WINDOW_ENTRY_ARRAY = OBJECT_ENTRY_ARRAY,
-        WINDOW_SIGNATURE = OBJECT_SIGNATURE,
+        WINDOW_TAG = OBJECT_TAG,
         WINDOW_HEADER,
         _WINDOW_MAX
 };
@@ -59,9 +63,13 @@ typedef struct JournalFile {
         char *path;
         struct stat last_stat;
         mode_t mode;
+
         int flags;
         int prot;
         bool writable;
+        bool compress;
+        bool authenticate;
+
         bool tail_entry_monotonic_valid;
 
         Header *header;
@@ -74,12 +82,18 @@ typedef struct JournalFile {
 
         JournalMetrics metrics;
 
-        bool compress;
-
 #ifdef HAVE_XZ
         void *compress_buffer;
         uint64_t compress_buffer_size;
 #endif
+
+#ifdef HAVE_GCRYPT
+        gcry_md_hd_t hmac;
+        bool hmac_running;
+
+        FSPRGHeader *fsprg_header;
+        size_t fsprg_size;
+#endif
 } JournalFile;
 
 typedef enum direction {
@@ -91,6 +105,8 @@ int journal_file_open(
                 const char *fname,
                 int flags,
                 mode_t mode,
+                bool compress,
+                bool authenticate,
                 JournalMetrics *metrics,
                 JournalFile *template,
                 JournalFile **ret);
@@ -101,6 +117,8 @@ int journal_file_open_reliably(
                 const char *fname,
                 int flags,
                 mode_t mode,
+                bool compress,
+                bool authenticate,
                 JournalMetrics *metrics,
                 JournalFile *template,
                 JournalFile **ret);
@@ -134,7 +152,7 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6
 void journal_file_dump(JournalFile *f);
 void journal_file_print_header(JournalFile *f);
 
-int journal_file_rotate(JournalFile **f);
+int journal_file_rotate(JournalFile **f, bool compress, bool authenticate);
 
 int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free);
 
index 62bb904..138bf09 100644 (file)
 #include "logs-show.h"
 #include "strv.h"
 #include "journal-internal.h"
+#include "fsprg.h"
+#include "journal-def.h"
+
+#define DEFAULT_FSPRG_INTERVAL_USEC (15*USEC_PER_MINUTE)
 
 static OutputMode arg_output = OUTPUT_SHORT;
 static bool arg_follow = false;
@@ -48,14 +52,19 @@ 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 enum {
+        ACTION_SHOW,
+        ACTION_NEW_ID128,
+        ACTION_PRINT_HEADER,
+        ACTION_SETUP_KEYS
+} arg_action = ACTION_SHOW;
+
 static int help(void) {
 
         printf("%s [OPTIONS...] [MATCH]\n\n"
@@ -73,9 +82,11 @@ static int help(void) {
                "  -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"
+               "  -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"
-               "     --new-id128      Generate a new 128 Bit id\n",
+               "     --setup-keys     Generate new FSPRG key pair\n",
                program_invocation_short_name);
 
         return 0;
@@ -88,7 +99,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_NO_PAGER,
                 ARG_NO_TAIL,
                 ARG_NEW_ID128,
-                ARG_HEADER
+                ARG_HEADER,
+                ARG_SETUP_KEYS
         };
 
         static const struct option options[] = {
@@ -107,6 +119,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "directory", required_argument, NULL, 'D'           },
                 { "header",    no_argument,       NULL, ARG_HEADER    },
                 { "priority",  no_argument,       NULL, 'p'           },
+                { "setup-keys",no_argument,       NULL, ARG_SETUP_KEYS},
                 { NULL,        0,                 NULL, 0             }
         };
 
@@ -163,7 +176,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 +196,11 @@ 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 'p': {
@@ -400,6 +417,161 @@ 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 FSPRGHeader h;
+        uint64_t n, interval;
+
+        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 "/fsprg",
+                     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 "/fsprg.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 evolving key...");
+        FSPRG_GenState0(state, mpk, seed, seed_size);
+
+        interval = DEFAULT_FSPRG_INTERVAL_USEC;
+        n = now(CLOCK_REALTIME);
+        n /= 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.fsprg_start_usec = htole64(n * interval);
+        h.fsprg_interval_usec = htole64(interval);
+        h.secpar = htole16(FSPRG_RECOMMENDED_SECPAR);
+        h.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 evolving key has been written to the\n"
+                        "following file. It will be used to protect local journal files. This file does\n"
+                        "not need to be kept secret. It should not be used on multiple hosts.\n"
+                        "\n"
+                        "\t%s\n"
+                        "\n"
+                        "Please write down the following " ANSI_HIGHLIGHT_ON "secret" ANSI_HIGHLIGHT_OFF " seed value. It should not be stored\n"
+                        "locally on disk, and may be used to verify journal files from this host.\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) interval);
+
+        if (isatty(STDOUT_FILENO))
+                fputs(ANSI_HIGHLIGHT_OFF "\n", stderr);
+
+        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
+}
+
 int main(int argc, char *argv[]) {
         int r;
         sd_journal *j = NULL;
@@ -416,11 +588,16 @@ 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;
         }
 
+        if (arg_action == ACTION_SETUP_KEYS) {
+                r = setup_keys();
+                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.");
@@ -436,7 +613,7 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
-        if (arg_print_header) {
+        if (arg_action == ACTION_PRINT_HEADER) {
                 journal_print_header(j);
                 r = 0;
                 goto finish;
index 7c89689..8c41d9b 100644 (file)
@@ -281,7 +281,6 @@ static JournalFile* find_journal(Server *s, uid_t uid) {
         char *p;
         int r;
         JournalFile *f;
-        char ids[33];
         sd_id128_t machine;
 
         assert(s);
@@ -305,7 +304,8 @@ static JournalFile* find_journal(Server *s, uid_t uid) {
         if (f)
                 return f;
 
-        if (asprintf(&p, "/var/log/journal/%s/user-%lu.journal", sd_id128_to_string(machine, ids), (unsigned long) uid) < 0)
+        if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/user-%lu.journal",
+                     SD_ID128_FORMAT_VAL(machine), (unsigned long) uid) < 0)
                 return s->system_journal;
 
         while (hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) {
@@ -315,7 +315,7 @@ static JournalFile* find_journal(Server *s, uid_t uid) {
                 journal_file_close(f);
         }
 
-        r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, &s->system_metrics, s->system_journal, &f);
+        r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, s->compress, false, &s->system_metrics, s->system_journal, &f);
         free(p);
 
         if (r < 0)
@@ -341,7 +341,7 @@ static void server_rotate(Server *s) {
         log_info("Rotating...");
 
         if (s->runtime_journal) {
-                r = journal_file_rotate(&s->runtime_journal);
+                r = journal_file_rotate(&s->runtime_journal, s->compress, false);
                 if (r < 0)
                         if (s->runtime_journal)
                                 log_error("Failed to rotate %s: %s", s->runtime_journal->path, strerror(-r));
@@ -352,7 +352,7 @@ static void server_rotate(Server *s) {
         }
 
         if (s->system_journal) {
-                r = journal_file_rotate(&s->system_journal);
+                r = journal_file_rotate(&s->system_journal, s->compress, true);
                 if (r < 0)
                         if (s->system_journal)
                                 log_error("Failed to rotate %s: %s", s->system_journal->path, strerror(-r));
@@ -364,7 +364,7 @@ static void server_rotate(Server *s) {
         }
 
         HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) {
-                r = journal_file_rotate(&f);
+                r = journal_file_rotate(&f, s->compress, false);
                 if (r < 0)
                         if (f->path)
                                 log_error("Failed to rotate %s: %s", f->path, strerror(-r));
@@ -2006,14 +2006,12 @@ static int system_journal_open(Server *s) {
                 if (!fn)
                         return -ENOMEM;
 
-                r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, &s->system_metrics, NULL, &s->system_journal);
+                r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, true, &s->system_metrics, NULL, &s->system_journal);
                 free(fn);
 
-                if (r >= 0) {
-                        s->system_journal->compress = s->compress;
-
+                if (r >= 0)
                         server_fix_perms(s, s->system_journal, 0);
-                else if (r < 0) {
+                else if (r < 0) {
 
                         if (r != -ENOENT && r != -EROFS)
                                 log_warning("Failed to open system journal: %s", strerror(-r));
@@ -2035,7 +2033,7 @@ static int system_journal_open(Server *s) {
                          * if it already exists, so that we can flush
                          * it into the system journal */
 
-                        r = journal_file_open(fn, O_RDWR, 0640, &s->runtime_metrics, NULL, &s->runtime_journal);
+                        r = journal_file_open(fn, O_RDWR, 0640, s->compress, false, &s->runtime_metrics, NULL, &s->runtime_journal);
                         free(fn);
 
                         if (r < 0) {
@@ -2051,7 +2049,7 @@ static int system_journal_open(Server *s) {
                          * it if necessary. */
 
                         (void) mkdir_parents(fn, 0755);
-                        r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, &s->runtime_metrics, NULL, &s->runtime_journal);
+                        r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, false, &s->runtime_metrics, NULL, &s->runtime_journal);
                         free(fn);
 
                         if (r < 0) {
@@ -2060,11 +2058,8 @@ static int system_journal_open(Server *s) {
                         }
                 }
 
-                if (s->runtime_journal) {
-                        s->runtime_journal->compress = s->compress;
-
+                if (s->runtime_journal)
                         server_fix_perms(s, s->runtime_journal, 0);
-                }
         }
 
         return r;
index 33686ed..359a7ca 100644 (file)
@@ -1118,7 +1118,7 @@ static int add_file(sd_journal *j, const char *prefix, const char *filename) {
                 return 0;
         }
 
-        r = journal_file_open(path, O_RDONLY, 0, NULL, NULL, &f);
+        r = journal_file_open(path, O_RDONLY, 0, false, false, NULL, NULL, &f);
         free(path);
 
         if (r < 0) {
index fd448cb..0925995 100644 (file)
@@ -79,9 +79,9 @@ int main(int argc, char *argv[]) {
         assert_se(mkdtemp(t));
         assert_se(chdir(t) >= 0);
 
-        assert_se(journal_file_open("one.journal", O_RDWR|O_CREAT, 0666, NULL, NULL, &one) == 0);
-        assert_se(journal_file_open("two.journal", O_RDWR|O_CREAT, 0666, NULL, NULL, &two) == 0);
-        assert_se(journal_file_open("three.journal", O_RDWR|O_CREAT, 0666, NULL, NULL, &three) == 0);
+        assert_se(journal_file_open("one.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, &one) == 0);
+        assert_se(journal_file_open("two.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, &two) == 0);
+        assert_se(journal_file_open("three.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, &three) == 0);
 
         for (i = 0; i < N_ENTRIES; i++) {
                 char *p, *q;
index ca3b474..7b1583c 100644 (file)
@@ -41,7 +41,7 @@ int main(int argc, char *argv[]) {
         assert_se(mkdtemp(t));
         assert_se(chdir(t) >= 0);
 
-        assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, NULL, NULL, &f) == 0);
+        assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, &f) == 0);
 
         dual_timestamp_get(&ts);
 
@@ -109,8 +109,8 @@ int main(int argc, char *argv[]) {
 
         assert(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0);
 
-        journal_file_rotate(&f);
-        journal_file_rotate(&f);
+        journal_file_rotate(&f, true, true);
+        journal_file_rotate(&f, true, true);
 
         journal_file_close(f);