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:
Yu Watanabe
2026-06-28 00:00:11 +09:00
parent 14236c019b
commit 24c4729198
19 changed files with 141 additions and 108 deletions
+421
View File
@@ -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
}
+43
View File
@@ -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
+118
View File
@@ -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
}
+79
View File
@@ -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);
+542
View File
@@ -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 */
+7
View File
@@ -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);
+3
View File
@@ -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',