journald: initial version of FSPRG hookup

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.
This commit is contained in:
Lennart Poettering 2012-08-13 20:31:10 +02:00
parent 8caf9d6836
commit 7560fffcd2
13 changed files with 1250 additions and 93 deletions

View 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 \

2
TODO
View File

@ -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.

View File

@ -301,6 +301,39 @@ fi
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]),
@ -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}

384
src/journal/fsprg.c Normal file
View File

@ -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);
}

64
src/journal/fsprg.h Normal file
View File

@ -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

View 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;
};

View 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))
@ -66,13 +67,21 @@
#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 {

View File

@ -23,6 +23,10 @@
#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);

View File

@ -41,6 +41,10 @@
#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;

View 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;

View 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) {

View 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;

View 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);