sd-journal: drop libgcrypt dependency from libsystemd
This introduce a vtable for journal tagging feature in sd-journal, and makes libgcrypt dependent features loaded by users (journald, journalctl, journal-remote, and unit tests) when necessary.
This commit is contained in:
@@ -0,0 +1,421 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*
|
||||
* fsprg v0.1 - (seekable) forward-secure pseudorandom generator
|
||||
* Copyright © 2012 B. Poettering
|
||||
* Contact: fsprg@point-at-infinity.org
|
||||
*/
|
||||
|
||||
/*
|
||||
* See "Practical Secure Logging: Seekable Sequential Key Generators"
|
||||
* by G. A. Marson, B. Poettering for details:
|
||||
*
|
||||
* http://eprint.iacr.org/2013/397
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "fsprg.h"
|
||||
#include "gcrypt-util.h"
|
||||
#include "memory-util.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
|
||||
|
||||
#if HAVE_GCRYPT
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wpointer-arith"
|
||||
/* TODO: remove void* arithmetic and this work-around */
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static void mpi_export(void *buf, size_t buflen, gcry_mpi_t x) {
|
||||
unsigned len;
|
||||
size_t nwritten;
|
||||
|
||||
assert(sym_gcry_mpi_cmp_ui(x, 0) >= 0);
|
||||
len = (sym_gcry_mpi_get_nbits(x) + 7) / 8;
|
||||
assert(len <= buflen);
|
||||
memzero(buf, buflen);
|
||||
sym_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;
|
||||
_unused_ unsigned len;
|
||||
|
||||
assert_se(sym_gcry_mpi_scan(&h, GCRYMPI_FMT_USG, buf, buflen, NULL) == 0);
|
||||
len = (sym_gcry_mpi_get_nbits(h) + 7) / 8;
|
||||
assert(len <= buflen);
|
||||
assert(sym_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;
|
||||
gcry_error_t err;
|
||||
uint32_t ctr;
|
||||
|
||||
olen = sym_gcry_md_get_algo_dlen(RND_HASH);
|
||||
err = sym_gcry_md_open(&hd, RND_HASH, 0);
|
||||
assert_se(gcry_err_code(err) == GPG_ERR_NO_ERROR); /* This shouldn't happen */
|
||||
sym_gcry_md_write(hd, seed, seedlen);
|
||||
sym_gcry_md_putc(hd, (idx >> 24) & 0xff);
|
||||
sym_gcry_md_putc(hd, (idx >> 16) & 0xff);
|
||||
sym_gcry_md_putc(hd, (idx >> 8) & 0xff);
|
||||
sym_gcry_md_putc(hd, (idx >> 0) & 0xff);
|
||||
|
||||
for (ctr = 0; buflen; ctr++) {
|
||||
err = sym_gcry_md_copy(&hd2, hd);
|
||||
assert_se(gcry_err_code(err) == GPG_ERR_NO_ERROR); /* This shouldn't happen */
|
||||
sym_gcry_md_putc(hd2, (ctr >> 24) & 0xff);
|
||||
sym_gcry_md_putc(hd2, (ctr >> 16) & 0xff);
|
||||
sym_gcry_md_putc(hd2, (ctr >> 8) & 0xff);
|
||||
sym_gcry_md_putc(hd2, (ctr >> 0) & 0xff);
|
||||
sym_gcry_md_ctl(hd2, GCRYCTL_FINALIZE, NULL, 0);
|
||||
cpylen = (buflen < olen) ? buflen : olen;
|
||||
memcpy(buf, sym_gcry_md_read(hd2, RND_HASH), cpylen);
|
||||
sym_gcry_md_close(hd2);
|
||||
buf += cpylen;
|
||||
buflen -= cpylen;
|
||||
}
|
||||
sym_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 (sym_gcry_prime_check(p, 0))
|
||||
sym_gcry_mpi_add_ui(p, p, 4);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/* deterministically generate from seed/idx a quadratic residue (mod n) */
|
||||
static gcry_mpi_t gensquare(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(sym_gcry_mpi_cmp(x, n) < 0);
|
||||
sym_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, gcry_mpi_t p) {
|
||||
gcry_mpi_t phi, r;
|
||||
int n;
|
||||
|
||||
phi = sym_gcry_mpi_new(0);
|
||||
sym_gcry_mpi_sub_ui(phi, p, 1);
|
||||
|
||||
/* count number of used bits in m */
|
||||
for (n = 0; (1ULL << n) <= m; n++)
|
||||
;
|
||||
|
||||
r = sym_gcry_mpi_new(0);
|
||||
sym_gcry_mpi_set_ui(r, 1);
|
||||
while (n) { /* square and multiply algorithm for fast exponentiation */
|
||||
n--;
|
||||
sym_gcry_mpi_mulm(r, r, r, phi);
|
||||
if (m & ((uint64_t)1 << n)) {
|
||||
sym_gcry_mpi_add(r, r, r);
|
||||
if (sym_gcry_mpi_cmp(r, phi) >= 0)
|
||||
sym_gcry_mpi_sub(r, r, phi);
|
||||
}
|
||||
}
|
||||
|
||||
sym_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, gcry_mpi_t x, gcry_mpi_t p, gcry_mpi_t q) {
|
||||
*xp = sym_gcry_mpi_new(0);
|
||||
*xq = sym_gcry_mpi_new(0);
|
||||
sym_gcry_mpi_mod(*xp, x, p);
|
||||
sym_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, gcry_mpi_t xp, gcry_mpi_t xq, gcry_mpi_t p, gcry_mpi_t q) {
|
||||
gcry_mpi_t a, u;
|
||||
|
||||
a = sym_gcry_mpi_new(0);
|
||||
u = sym_gcry_mpi_new(0);
|
||||
*x = sym_gcry_mpi_new(0);
|
||||
sym_gcry_mpi_subm(a, xq, xp, q);
|
||||
sym_gcry_mpi_invm(u, p, q);
|
||||
sym_gcry_mpi_mulm(a, a, u, q); /* a = (xq - xp) / p (mod q) */
|
||||
sym_gcry_mpi_mul(*x, p, a);
|
||||
sym_gcry_mpi_add(*x, *x, xp); /* x = p * ((xq - xp) / p mod q) + xp */
|
||||
sym_gcry_mpi_release(a);
|
||||
sym_gcry_mpi_release(u);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
#endif
|
||||
|
||||
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 */
|
||||
}
|
||||
|
||||
#if HAVE_GCRYPT
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
||||
int FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned _secpar) {
|
||||
#if HAVE_GCRYPT
|
||||
uint8_t iseed[FSPRG_RECOMMENDED_SEEDLEN];
|
||||
gcry_mpi_t n, p, q;
|
||||
uint16_t secpar;
|
||||
int r;
|
||||
|
||||
VALIDATE_SECPAR(_secpar);
|
||||
secpar = _secpar;
|
||||
|
||||
r = initialize_libgcrypt(false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!seed) {
|
||||
sym_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 = sym_gcry_mpi_new(0);
|
||||
sym_gcry_mpi_mul(n, p, q);
|
||||
assert(sym_gcry_mpi_get_nbits(n) == secpar);
|
||||
|
||||
store_secpar(mpk + 0, secpar);
|
||||
mpi_export(mpk + 2, secpar / 8, n);
|
||||
|
||||
sym_gcry_mpi_release(n);
|
||||
}
|
||||
|
||||
sym_gcry_mpi_release(p);
|
||||
sym_gcry_mpi_release(q);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -EOPNOTSUPP;
|
||||
#endif
|
||||
}
|
||||
|
||||
int FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen) {
|
||||
#if HAVE_GCRYPT
|
||||
gcry_mpi_t n, x;
|
||||
uint16_t secpar;
|
||||
int r;
|
||||
|
||||
r = initialize_libgcrypt(false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
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);
|
||||
memzero(state + 2 + 2 * secpar / 8, 8);
|
||||
|
||||
sym_gcry_mpi_release(n);
|
||||
sym_gcry_mpi_release(x);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -EOPNOTSUPP;
|
||||
#endif
|
||||
}
|
||||
|
||||
int FSPRG_Evolve(void *state) {
|
||||
#if HAVE_GCRYPT
|
||||
gcry_mpi_t n, x;
|
||||
uint16_t secpar;
|
||||
uint64_t epoch;
|
||||
int r;
|
||||
|
||||
r = initialize_libgcrypt(false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
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);
|
||||
|
||||
sym_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);
|
||||
|
||||
sym_gcry_mpi_release(n);
|
||||
sym_gcry_mpi_release(x);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -EOPNOTSUPP;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t FSPRG_GetEpoch(const void *state) {
|
||||
#if HAVE_GCRYPT
|
||||
uint16_t secpar;
|
||||
secpar = read_secpar(state + 0);
|
||||
return uint64_import(state + 2 + 2 * secpar / 8, 8);
|
||||
#else
|
||||
return -EOPNOTSUPP;
|
||||
#endif
|
||||
}
|
||||
|
||||
int FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen) {
|
||||
#if HAVE_GCRYPT
|
||||
gcry_mpi_t p, q, n, x, xp, xq, kp, kq, xm;
|
||||
uint16_t secpar;
|
||||
int r;
|
||||
|
||||
r = initialize_libgcrypt(false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
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 = sym_gcry_mpi_new(0);
|
||||
sym_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)) */
|
||||
|
||||
sym_gcry_mpi_powm(xp, xp, kp, p); /* compute x^(2^epoch) (mod p) */
|
||||
sym_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);
|
||||
|
||||
sym_gcry_mpi_release(p);
|
||||
sym_gcry_mpi_release(q);
|
||||
sym_gcry_mpi_release(n);
|
||||
sym_gcry_mpi_release(x);
|
||||
sym_gcry_mpi_release(xp);
|
||||
sym_gcry_mpi_release(xq);
|
||||
sym_gcry_mpi_release(kp);
|
||||
sym_gcry_mpi_release(kq);
|
||||
sym_gcry_mpi_release(xm);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -EOPNOTSUPP;
|
||||
#endif
|
||||
}
|
||||
|
||||
int FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx) {
|
||||
#if HAVE_GCRYPT
|
||||
uint16_t secpar;
|
||||
int r;
|
||||
|
||||
r = initialize_libgcrypt(false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
secpar = read_secpar(state + 0);
|
||||
det_randomize(key, keylen, state + 2, 2 * secpar / 8 + 8, idx);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -EOPNOTSUPP;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* fsprg v0.1 - (seekable) forward-secure pseudorandom generator
|
||||
* Copyright © 2012 B. Poettering
|
||||
* Contact: fsprg@point-at-infinity.org
|
||||
*/
|
||||
|
||||
#include "sd-forward.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define FSPRG_RECOMMENDED_SECPAR 1536
|
||||
#define FSPRG_RECOMMENDED_SEEDLEN (96/8)
|
||||
|
||||
size_t FSPRG_mskinbytes(unsigned secpar) _const_;
|
||||
size_t FSPRG_mpkinbytes(unsigned secpar) _const_;
|
||||
size_t FSPRG_stateinbytes(unsigned secpar) _const_;
|
||||
|
||||
/* Setup msk and mpk. Providing seed != NULL makes this algorithm deterministic. */
|
||||
int 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.
|
||||
*/
|
||||
int FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen);
|
||||
|
||||
int FSPRG_Evolve(void *state);
|
||||
|
||||
uint64_t FSPRG_GetEpoch(const void *state) _pure_;
|
||||
|
||||
/* Seek to any arbitrary state (by providing msk together with seed from GenState0). */
|
||||
int FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen);
|
||||
|
||||
int FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,118 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/syslog.h>
|
||||
|
||||
#include "sd-dlopen.h"
|
||||
|
||||
#include "gcrypt-util.h"
|
||||
#include "log.h" /* IWYU pragma: keep */
|
||||
|
||||
#if HAVE_GCRYPT
|
||||
|
||||
static DLSYM_PROTOTYPE(gcry_control) = NULL;
|
||||
static DLSYM_PROTOTYPE(gcry_check_version) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_md_close) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_md_copy) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_md_ctl) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_md_get_algo_dlen) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_md_open) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_md_read) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_md_reset) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_md_setkey) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_md_write) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_add) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_add_ui) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_cmp) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_cmp_ui) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_get_nbits) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_invm) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_mod) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_mul) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_mulm) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_new) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_powm) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_print) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_release) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_scan) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_set_ui) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_sub) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_subm) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_mpi_sub_ui) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_prime_check) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_randomize) = NULL;
|
||||
DLSYM_PROTOTYPE(gcry_strerror) = NULL;
|
||||
#endif
|
||||
|
||||
int dlopen_gcrypt(int log_level) {
|
||||
#if HAVE_GCRYPT
|
||||
static void *gcrypt_dl = NULL;
|
||||
|
||||
GCRYPT_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED);
|
||||
|
||||
return dlopen_many_sym_or_warn(
|
||||
&gcrypt_dl,
|
||||
"libgcrypt.so.20", log_level,
|
||||
DLSYM_ARG(gcry_control),
|
||||
DLSYM_ARG(gcry_check_version),
|
||||
DLSYM_ARG(gcry_md_close),
|
||||
DLSYM_ARG(gcry_md_copy),
|
||||
DLSYM_ARG(gcry_md_ctl),
|
||||
DLSYM_ARG(gcry_md_get_algo_dlen),
|
||||
DLSYM_ARG(gcry_md_open),
|
||||
DLSYM_ARG(gcry_md_read),
|
||||
DLSYM_ARG(gcry_md_reset),
|
||||
DLSYM_ARG(gcry_md_setkey),
|
||||
DLSYM_ARG(gcry_md_write),
|
||||
DLSYM_ARG(gcry_mpi_add),
|
||||
DLSYM_ARG(gcry_mpi_add_ui),
|
||||
DLSYM_ARG(gcry_mpi_cmp),
|
||||
DLSYM_ARG(gcry_mpi_cmp_ui),
|
||||
DLSYM_ARG(gcry_mpi_get_nbits),
|
||||
DLSYM_ARG(gcry_mpi_invm),
|
||||
DLSYM_ARG(gcry_mpi_mod),
|
||||
DLSYM_ARG(gcry_mpi_mul),
|
||||
DLSYM_ARG(gcry_mpi_mulm),
|
||||
DLSYM_ARG(gcry_mpi_new),
|
||||
DLSYM_ARG(gcry_mpi_powm),
|
||||
DLSYM_ARG(gcry_mpi_print),
|
||||
DLSYM_ARG(gcry_mpi_release),
|
||||
DLSYM_ARG(gcry_mpi_scan),
|
||||
DLSYM_ARG(gcry_mpi_set_ui),
|
||||
DLSYM_ARG(gcry_mpi_sub),
|
||||
DLSYM_ARG(gcry_mpi_subm),
|
||||
DLSYM_ARG(gcry_mpi_sub_ui),
|
||||
DLSYM_ARG(gcry_prime_check),
|
||||
DLSYM_ARG(gcry_randomize),
|
||||
DLSYM_ARG(gcry_strerror));
|
||||
#else
|
||||
return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"gcrypt support is not compiled in.");
|
||||
#endif
|
||||
}
|
||||
|
||||
int initialize_libgcrypt(bool secmem) {
|
||||
#if HAVE_GCRYPT
|
||||
int r;
|
||||
|
||||
r = dlopen_gcrypt(LOG_DEBUG);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (sym_gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
|
||||
return 0;
|
||||
|
||||
sym_gcry_control(GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_SYSTEM);
|
||||
assert_se(sym_gcry_check_version("1.4.5"));
|
||||
|
||||
/* Turn off "secmem". Clients which wish to make use of this
|
||||
* feature should initialize the library manually */
|
||||
if (!secmem)
|
||||
sym_gcry_control(GCRYCTL_DISABLE_SECMEM);
|
||||
|
||||
sym_gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -EOPNOTSUPP;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sd-dlopen.h"
|
||||
|
||||
#include "basic-forward.h"
|
||||
|
||||
int dlopen_gcrypt(int log_level);
|
||||
|
||||
int initialize_libgcrypt(bool secmem);
|
||||
|
||||
#if HAVE_GCRYPT
|
||||
#define GCRYPT_NOTE(priority) \
|
||||
SD_ELF_NOTE_DLOPEN("gcrypt", \
|
||||
"Support for journald forward-sealing", \
|
||||
priority, \
|
||||
"libgcrypt.so.20")
|
||||
|
||||
#define DLOPEN_GCRYPT(log_level, priority) \
|
||||
({ \
|
||||
GCRYPT_NOTE(priority); \
|
||||
dlopen_gcrypt(log_level); \
|
||||
})
|
||||
|
||||
#include <gcrypt.h> /* IWYU pragma: export */
|
||||
|
||||
#include "dlfcn-util.h"
|
||||
|
||||
extern DLSYM_PROTOTYPE(gcry_md_close);
|
||||
extern DLSYM_PROTOTYPE(gcry_md_copy);
|
||||
extern DLSYM_PROTOTYPE(gcry_md_ctl);
|
||||
extern DLSYM_PROTOTYPE(gcry_md_get_algo_dlen);
|
||||
extern DLSYM_PROTOTYPE(gcry_md_open);
|
||||
extern DLSYM_PROTOTYPE(gcry_md_read);
|
||||
extern DLSYM_PROTOTYPE(gcry_md_reset);
|
||||
extern DLSYM_PROTOTYPE(gcry_md_setkey);
|
||||
extern DLSYM_PROTOTYPE(gcry_md_write);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_add);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_add_ui);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_cmp);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_cmp_ui);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_get_nbits);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_invm);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_mod);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_mul);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_mulm);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_new);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_powm);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_print);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_release);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_scan);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_set_ui);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_sub);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_subm);
|
||||
extern DLSYM_PROTOTYPE(gcry_mpi_sub_ui);
|
||||
extern DLSYM_PROTOTYPE(gcry_prime_check);
|
||||
extern DLSYM_PROTOTYPE(gcry_randomize);
|
||||
extern DLSYM_PROTOTYPE(gcry_strerror);
|
||||
|
||||
/* Copied from gcry_md_putc from gcrypt.h due to the need to call the sym_ variant */
|
||||
#define sym_gcry_md_putc(h,c) \
|
||||
do { \
|
||||
gcry_md_hd_t h__ = (h); \
|
||||
if ((h__)->bufpos == (h__)->bufsize) \
|
||||
sym_gcry_md_write((h__), NULL, 0); \
|
||||
(h__)->buf[(h__)->bufpos++] = (c) & 0xff; \
|
||||
} while(false)
|
||||
#else
|
||||
#define DLOPEN_GCRYPT(log_level, priority) dlopen_gcrypt(log_level)
|
||||
|
||||
typedef struct gcry_md_handle *gcry_md_hd_t;
|
||||
|
||||
static inline void sym_gcry_md_close(gcry_md_hd_t h) {
|
||||
assert(h == NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(gcry_md_hd_t, sym_gcry_md_close, NULL);
|
||||
@@ -0,0 +1,542 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fsprg.h"
|
||||
#include "gcrypt-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "iovec-util.h"
|
||||
#include "journal-authenticate.h"
|
||||
#include "journal-def.h"
|
||||
#include "journal-file.h"
|
||||
#include "log.h"
|
||||
#include "memory-util.h"
|
||||
#include "string-util.h"
|
||||
#include "time-util.h"
|
||||
|
||||
#if HAVE_GCRYPT
|
||||
|
||||
struct JournalAuthContext {
|
||||
gcry_md_hd_t hmac;
|
||||
bool hmac_running;
|
||||
|
||||
FSSHeader *fss_file;
|
||||
size_t fss_mmap_size;
|
||||
|
||||
uint64_t fss_start_usec;
|
||||
uint64_t fss_interval_usec;
|
||||
|
||||
struct iovec fsprg_state;
|
||||
struct iovec fsprg_seed;
|
||||
};
|
||||
|
||||
static JournalAuthContext* journal_auth_free(JournalAuthContext *c) {
|
||||
if (!c)
|
||||
return NULL;
|
||||
|
||||
if (c->fss_file)
|
||||
munmap(c->fss_file, c->fss_mmap_size);
|
||||
else
|
||||
iovec_done_erase(&c->fsprg_state);
|
||||
|
||||
iovec_done_erase(&c->fsprg_seed);
|
||||
|
||||
if (c->hmac)
|
||||
sym_gcry_md_close(c->hmac);
|
||||
|
||||
return mfree(c);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(JournalAuthContext*, journal_auth_free);
|
||||
|
||||
static void* fssheader_free(FSSHeader *p) {
|
||||
/* mmap() returns MAP_FAILED on error and sets the errno */
|
||||
if (!p || p == MAP_FAILED)
|
||||
return NULL;
|
||||
|
||||
assert_se(munmap(p, PAGE_ALIGN(sizeof(FSSHeader))) >= 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(FSSHeader*, fssheader_free);
|
||||
|
||||
static int journal_auth_load(JournalAuthContext **ret) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
/* This function is used to determine whether sealing should be enabled in the journal header so we
|
||||
* can't check the header to check if sealing is enabled here. */
|
||||
|
||||
sd_id128_t machine;
|
||||
r = sd_id128_get_machine(&machine);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *path = NULL;
|
||||
if (asprintf(&path, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
|
||||
SD_ID128_FORMAT_VAL(machine)) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
_cleanup_close_ int fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
|
||||
if (fd < 0) {
|
||||
if (errno != ENOENT)
|
||||
log_error_errno(errno, "Failed to open %s: %m", path);
|
||||
|
||||
return -errno;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
|
||||
if (st.st_size < (off_t) sizeof(FSSHeader))
|
||||
return -ENODATA;
|
||||
|
||||
_cleanup_(fssheader_freep) FSSHeader *header =
|
||||
mmap(NULL, PAGE_ALIGN(sizeof(FSSHeader)), PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (header == MAP_FAILED)
|
||||
return -errno;
|
||||
|
||||
if (memcmp(header->signature, (const uint8_t[]) FSS_HEADER_SIGNATURE, 8) != 0)
|
||||
return -EBADMSG;
|
||||
|
||||
if (header->incompatible_flags != 0)
|
||||
return -EPROTONOSUPPORT;
|
||||
|
||||
if (le64toh(header->header_size) < sizeof(FSSHeader))
|
||||
return -EBADMSG;
|
||||
|
||||
if (le64toh(header->fsprg_state_size) != FSPRG_stateinbytes(le16toh(header->fsprg_secpar)))
|
||||
return -EBADMSG;
|
||||
|
||||
uint64_t fss_file_size;
|
||||
if (!ADD_SAFE(&fss_file_size, le64toh(header->header_size), le64toh(header->fsprg_state_size)))
|
||||
return -EBADMSG;
|
||||
|
||||
if (fss_file_size >= SIZE_MAX)
|
||||
return -EBADMSG;
|
||||
|
||||
if ((uint64_t) st.st_size < fss_file_size)
|
||||
return -ENODATA;
|
||||
|
||||
if (!sd_id128_equal(machine, header->machine_id))
|
||||
return -EHOSTDOWN;
|
||||
|
||||
if (le64toh(header->start_usec) <= 0 || le64toh(header->interval_usec) <= 0)
|
||||
return -EBADMSG;
|
||||
|
||||
_cleanup_(journal_auth_freep) JournalAuthContext *c = new0(JournalAuthContext, 1);
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
|
||||
size_t sz = PAGE_ALIGN(fss_file_size);
|
||||
if (sz >= SIZE_MAX)
|
||||
return -EBADMSG;
|
||||
|
||||
FSSHeader *p = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (p == MAP_FAILED)
|
||||
return -errno;
|
||||
|
||||
*c = (JournalAuthContext) {
|
||||
.fss_mmap_size = sz,
|
||||
.fss_file = p,
|
||||
|
||||
.fss_start_usec = le64toh(p->start_usec),
|
||||
.fss_interval_usec = le64toh(p->interval_usec),
|
||||
|
||||
.fsprg_state = IOVEC_MAKE(
|
||||
(uint8_t*) p + le64toh(p->header_size),
|
||||
le64toh(p->fsprg_state_size)),
|
||||
};
|
||||
|
||||
*ret = TAKE_PTR(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int journal_auth_load_key(JournalAuthContext **ret, const char *key) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
assert(key);
|
||||
|
||||
size_t seed_size = FSPRG_RECOMMENDED_SEEDLEN;
|
||||
_cleanup_(erase_and_freep) uint8_t *seed = malloc(seed_size);
|
||||
if (!seed)
|
||||
return -ENOMEM;
|
||||
|
||||
const char *k = key;
|
||||
for (size_t c = 0; c < seed_size; c++) {
|
||||
int x, y;
|
||||
|
||||
k = skip_leading_chars(k, "-");
|
||||
|
||||
x = unhexchar(*k);
|
||||
if (x < 0)
|
||||
return -EKEYREJECTED;
|
||||
k++;
|
||||
|
||||
y = unhexchar(*k);
|
||||
if (y < 0)
|
||||
return -EKEYREJECTED;
|
||||
k++;
|
||||
|
||||
seed[c] = (uint8_t) (x * 16 + y);
|
||||
}
|
||||
|
||||
if (*k != '/')
|
||||
return -EKEYREJECTED;
|
||||
k++;
|
||||
|
||||
uint64_t start, interval;
|
||||
r = sscanf(k, "%"PRIx64"-%"PRIx64, &start, &interval);
|
||||
if (r != 2)
|
||||
return -EKEYREJECTED;
|
||||
|
||||
if (start == 0 || interval == 0)
|
||||
return -EKEYREJECTED;
|
||||
|
||||
uint64_t start_usec;
|
||||
if (!MUL_SAFE(&start_usec, start, interval))
|
||||
return -EKEYREJECTED;
|
||||
|
||||
_cleanup_(journal_auth_freep) JournalAuthContext *c = new(JournalAuthContext, 1);
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
|
||||
*c = (JournalAuthContext) {
|
||||
.fss_start_usec = start_usec,
|
||||
.fss_interval_usec = interval,
|
||||
|
||||
.fsprg_seed = IOVEC_MAKE(TAKE_PTR(seed), seed_size),
|
||||
};
|
||||
|
||||
*ret = TAKE_PTR(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int journal_auth_epoch_to_realtime_usec(const JournalAuthContext *c, uint64_t epoch, usec_t *ret_start, usec_t *ret_end) {
|
||||
assert(c);
|
||||
assert(c->fss_start_usec > 0);
|
||||
assert(c->fss_interval_usec > 0);
|
||||
|
||||
uint64_t start, end;
|
||||
if (!MUL_SAFE(&start, epoch, c->fss_interval_usec) ||
|
||||
!INC_SAFE(&start, c->fss_start_usec) ||
|
||||
!ADD_SAFE(&end, start, c->fss_interval_usec))
|
||||
return -ERANGE;
|
||||
|
||||
if (ret_start)
|
||||
*ret_start = start;
|
||||
if (ret_end)
|
||||
*ret_end = end;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int journal_auth_next_evolve_usec(const JournalAuthContext *c, usec_t *ret) {
|
||||
assert(c);
|
||||
|
||||
uint64_t epoch = FSPRG_GetEpoch(c->fsprg_state.iov_base);
|
||||
|
||||
return journal_auth_epoch_to_realtime_usec(c, epoch, /* ret_start= */ NULL, ret);
|
||||
}
|
||||
|
||||
static int journal_auth_seek(JournalAuthContext *c, uint64_t goal) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(iovec_is_set(&c->fsprg_seed));
|
||||
|
||||
if (iovec_is_set(&c->fsprg_state)) {
|
||||
uint64_t epoch = FSPRG_GetEpoch(c->fsprg_state.iov_base);
|
||||
if (goal == epoch)
|
||||
return 0;
|
||||
|
||||
if (goal == epoch + 1)
|
||||
return FSPRG_Evolve(c->fsprg_state.iov_base);
|
||||
} else {
|
||||
r = iovec_alloc(FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR), &c->fsprg_state);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
log_debug("Seeking FSPRG key to %"PRIu64".", goal);
|
||||
|
||||
_cleanup_(iovec_erase) struct iovec msk = IOVEC_ALLOCA(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR));
|
||||
r = FSPRG_GenMK(msk.iov_base, NULL, c->fsprg_seed.iov_base, c->fsprg_seed.iov_len, FSPRG_RECOMMENDED_SECPAR);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return FSPRG_Seek(c->fsprg_state.iov_base, goal, msk.iov_base, c->fsprg_seed.iov_base, c->fsprg_seed.iov_len);
|
||||
}
|
||||
|
||||
static int journal_auth_setup(JournalAuthContext *c) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
|
||||
if (c->hmac)
|
||||
return 0;
|
||||
|
||||
r = initialize_libgcrypt(true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (sym_gcry_md_open(&c->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC) != 0)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int journal_auth_start(JournalAuthContext *c) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
|
||||
if (c->hmac_running)
|
||||
return 0;
|
||||
|
||||
r = journal_auth_setup(c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Prepare HMAC for next cycle */
|
||||
sym_gcry_md_reset(c->hmac);
|
||||
|
||||
uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */
|
||||
CLEANUP_ERASE(key);
|
||||
r = FSPRG_GetKey(c->fsprg_state.iov_base, key, sizeof(key), 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
gcry_error_t err = sym_gcry_md_setkey(c->hmac, key, sizeof(key));
|
||||
if (gcry_err_code(err) != GPG_ERR_NO_ERROR)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"sym_gcry_md_setkey() failed with error code: %s",
|
||||
sym_gcry_strerror(err));
|
||||
|
||||
c->hmac_running = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int journal_auth_end(JournalAuthContext *c, uint8_t ret[static TAG_LENGTH]) {
|
||||
assert(c);
|
||||
assert(ret);
|
||||
|
||||
if (!c->hmac_running)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(ret, sym_gcry_md_read(c->hmac, 0), TAG_LENGTH);
|
||||
c->hmac_running = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int journal_auth_put_header(JournalAuthContext *c, JournalFile *f) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(f);
|
||||
|
||||
r = journal_auth_start(c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* All but state+reserved, boot_id, arena_size,
|
||||
* tail_object_offset, n_objects, n_entries,
|
||||
* tail_entry_seqnum, head_entry_seqnum, entry_array_offset,
|
||||
* head_entry_realtime, tail_entry_realtime,
|
||||
* tail_entry_monotonic, n_data, n_fields, n_tags,
|
||||
* n_entry_arrays. */
|
||||
|
||||
sym_gcry_md_write(c->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature));
|
||||
sym_gcry_md_write(c->hmac, &f->header->file_id, offsetof(Header, tail_entry_boot_id) - offsetof(Header, file_id));
|
||||
sym_gcry_md_write(c->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id));
|
||||
sym_gcry_md_write(c->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int journal_auth_put_object(JournalAuthContext *c, JournalFile *f, ObjectType type, Object *o, uint64_t p) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(f);
|
||||
|
||||
r = journal_auth_start(c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!o) {
|
||||
r = journal_file_move_to_object(f, type, p, &o);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else if (type > OBJECT_UNUSED && o->object.type != type)
|
||||
return -EBADMSG;
|
||||
|
||||
sym_gcry_md_write(c->hmac, o, offsetof(ObjectHeader, payload));
|
||||
|
||||
switch (o->object.type) {
|
||||
|
||||
case OBJECT_DATA:
|
||||
/* All but hash and payload are mutable */
|
||||
sym_gcry_md_write(c->hmac, &o->data.hash, sizeof(o->data.hash));
|
||||
sym_gcry_md_write(c->hmac, journal_file_data_payload_field(f, o), le64toh(o->object.size) - journal_file_data_payload_offset(f));
|
||||
break;
|
||||
|
||||
case OBJECT_FIELD:
|
||||
/* Same here */
|
||||
sym_gcry_md_write(c->hmac, &o->field.hash, sizeof(o->field.hash));
|
||||
sym_gcry_md_write(c->hmac, o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload));
|
||||
break;
|
||||
|
||||
case OBJECT_ENTRY:
|
||||
/* All */
|
||||
sym_gcry_md_write(c->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(Object, entry.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 but the tag itself */
|
||||
sym_gcry_md_write(c->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum));
|
||||
sym_gcry_md_write(c->hmac, &o->tag.epoch, sizeof(o->tag.epoch));
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int journal_auth_append_tag(JournalAuthContext *c, JournalFile *f) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(f);
|
||||
|
||||
r = journal_auth_start(c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
Object *o;
|
||||
uint64_t p;
|
||||
r = journal_file_append_object(f, OBJECT_TAG, sizeof(struct TagObject), &o, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
uint64_t seqnum = le64toh(f->header->n_tags) + 1;
|
||||
f->header->n_tags = htole64(seqnum);
|
||||
|
||||
o->tag.seqnum = htole64(seqnum);
|
||||
o->tag.epoch = htole64(FSPRG_GetEpoch(c->fsprg_state.iov_base));
|
||||
|
||||
log_debug("Writing tag %"PRIu64" for epoch %"PRIu64"",
|
||||
le64toh(o->tag.seqnum), le64toh(o->tag.epoch));
|
||||
|
||||
/* Add the tag object itself, so that we can protect its
|
||||
* header. This will exclude the actual hash value in it */
|
||||
r = journal_auth_put_object(c, f, OBJECT_TAG, o, p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Get the HMAC tag and store it in the object */
|
||||
return journal_auth_end(c, o->tag.tag);
|
||||
}
|
||||
|
||||
static int journal_auth_append_tag_first(JournalAuthContext *c, JournalFile *f) {
|
||||
uint64_t p;
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(f);
|
||||
|
||||
log_debug("Calculating first tag...");
|
||||
|
||||
r = journal_auth_put_header(c, 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_auth_put_object(c, f, OBJECT_FIELD_HASH_TABLE, NULL, 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_auth_put_object(c, f, OBJECT_DATA_HASH_TABLE, NULL, p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return journal_auth_append_tag(c, f);
|
||||
}
|
||||
|
||||
static int journal_auth_append_tag_maybe(JournalAuthContext *c, JournalFile *f, usec_t realtime) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(c->fss_start_usec > 0);
|
||||
assert(c->fss_interval_usec > 0);
|
||||
assert(f);
|
||||
|
||||
if (realtime <= 0)
|
||||
realtime = now(CLOCK_REALTIME);
|
||||
|
||||
uint64_t goal = usec_sub_unsigned(realtime, c->fss_start_usec) / c->fss_interval_usec;
|
||||
|
||||
for (;;) {
|
||||
uint64_t epoch = FSPRG_GetEpoch(c->fsprg_state.iov_base);
|
||||
if (epoch >= goal)
|
||||
return 0;
|
||||
|
||||
r = journal_auth_append_tag(c, f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = FSPRG_Evolve(c->fsprg_state.iov_base);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
static const JournalAuthOps journal_auth_ops = {
|
||||
.free = journal_auth_free,
|
||||
.load = journal_auth_load,
|
||||
.load_key = journal_auth_load_key,
|
||||
.epoch_to_realtime_usec = journal_auth_epoch_to_realtime_usec,
|
||||
.next_evolve_usec = journal_auth_next_evolve_usec,
|
||||
.seek = journal_auth_seek,
|
||||
.start = journal_auth_start,
|
||||
.end = journal_auth_end,
|
||||
.put_header = journal_auth_put_header,
|
||||
.put_object = journal_auth_put_object,
|
||||
.append_tag = journal_auth_append_tag,
|
||||
.append_tag_first = journal_auth_append_tag_first,
|
||||
.append_tag_maybe = journal_auth_append_tag_maybe,
|
||||
};
|
||||
|
||||
void journal_auth_init(void) {
|
||||
journal_auth_set_ops(&journal_auth_ops);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void journal_auth_init(void) {
|
||||
}
|
||||
|
||||
#endif /* HAVE_GCRYPT */
|
||||
@@ -0,0 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "journal-authenticate-internal.h" /* IWYU pragma: export */
|
||||
#include "shared-forward.h"
|
||||
|
||||
void journal_auth_init(void);
|
||||
@@ -87,7 +87,9 @@ shared_sources = files(
|
||||
'firewall-util.c',
|
||||
'fork-notify.c',
|
||||
'format-table.c',
|
||||
'fsprg.c',
|
||||
'fstab-util.c',
|
||||
'gcrypt-util.c',
|
||||
'generator.c',
|
||||
'geneve-util.c',
|
||||
'gnutls-util.c',
|
||||
@@ -110,6 +112,7 @@ shared_sources = files(
|
||||
'ioprio-util.c',
|
||||
'ip-protocol-list.c',
|
||||
'ipvlan-util.c',
|
||||
'journal-authenticate.c',
|
||||
'journal-file-util.c',
|
||||
'journal-importer.c',
|
||||
'journal-util.c',
|
||||
|
||||
Reference in New Issue
Block a user