From: Yu Watanabe Date: Fri, 19 Jun 2026 18:29:47 +0000 (+0900) Subject: fsprg-openssl: rewrite fsprg with OpenSSL X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7fae5c5bcc0b6013699202e830ec67e3e60b6f18;p=thirdparty%2Fsystemd.git fsprg-openssl: rewrite fsprg with OpenSSL This introduce OpenSSL port of fsprg, which is implemented by using libgcrypt. --- diff --git a/src/shared/fsprg-openssl.c b/src/shared/fsprg-openssl.c new file mode 100644 index 00000000000..167f0607f42 --- /dev/null +++ b/src/shared/fsprg-openssl.c @@ -0,0 +1,668 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * This is based on + * fsprg v0.1 - (seekable) forward-secure pseudorandom generator + * Copyright © 2012 B. Poettering + * Contact: fsprg@point-at-infinity.org + * + * OpenSSL port of the original libgcrypt-based implementation. + * + * See "Practical Secure Logging: Seekable Sequential Key Generators" + * by G. A. Marson, B. Poettering for details: + * + * http://eprint.iacr.org/2013/397 + */ + +#include + +#include "alloc-util.h" /* IWYU pragma: keep */ +#include "crypto-util.h" +#include "fsprg-openssl.h" +#include "iovec-util.h" +#include "logarithm.h" +#include "sparse-endian.h" +#include "unaligned.h" + +#define RND_GEN_P 0x01 +#define RND_GEN_Q 0x02 +#define RND_GEN_X 0x03 + +/* Suppress a false positive from GCC's -Wstringop-overflow warning. Keep a local helper so the compiler can + * propagate the range check within this translation unit. */ +static bool secpar_is_valid(uint16_t secpar) { + return + secpar % 16 == 0 && + secpar >= 16 && + secpar <= 16384; +} + +bool fsprg_secpar_is_valid(uint16_t secpar) { + return secpar_is_valid(secpar); +} + +size_t fsprg_state_size(uint16_t secpar) { + assert(secpar_is_valid(secpar)); + + /* See comment in parse_state(). */ + return sizeof(uint16_t) + 2 * secpar / 8 + sizeof(uint64_t); +} + +#if HAVE_OPENSSL +static int mpi_export(struct iovec *iov, const BIGNUM *x) { + assert(iovec_is_set(iov)); + assert(x); + + if (sym_BN_is_negative(x)) + return -EINVAL; + + if (sym_BN_num_bytes(x) > (int) iov->iov_len) + return -ENOSPC; + + if (sym_BN_bn2binpad(x, iov->iov_base, iov->iov_len) != (int) iov->iov_len) + return -EIO; + + return 0; +} + +static int mpi_import(const struct iovec *iov, BIGNUM **ret) { + assert(iovec_is_set(iov)); + assert(ret); + + /* Allocate a new BIGNUM. */ + _cleanup_(BN_clear_freep) BIGNUM *x = sym_BN_secure_new(); + if (!x) + return -ENOMEM; + + if (!sym_BN_bin2bn(iov->iov_base, iov->iov_len, x)) + return -EIO; + + *ret = TAKE_PTR(x); + return 0; +} + +static int det_randomize(const struct iovec *seed, uint32_t idx, struct iovec *iov) { + assert(iovec_is_set(seed)); + assert(iovec_is_valid(iov)); + + /* Expand (seed, idx) into a deterministic pseudorandom byte stream. */ + + if (!iovec_is_set(iov)) + return 0; + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = sym_EVP_MD_CTX_new(); + if (!ctx) + return -ENOMEM; + + if (sym_EVP_DigestInit_ex(ctx, sym_EVP_sha256(), NULL) <= 0) + return -EIO; + + if (sym_EVP_DigestUpdate(ctx, seed->iov_base, seed->iov_len) <= 0) + return -EIO; + + if (sym_EVP_DigestUpdate(ctx, (be32_t[]) { htobe32(idx) }, sizeof(be32_t)) <= 0) + return -EIO; + + struct iovec v = *iov; + for (uint32_t ctr = 0; iovec_is_set(&v); ctr++) { + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *tmp = sym_EVP_MD_CTX_new(); + if (!tmp) + return -ENOMEM; + + if (sym_EVP_MD_CTX_copy_ex(tmp, ctx) <= 0) + return -EIO; + + if (sym_EVP_DigestUpdate(tmp, (be32_t[]) { htobe32(ctr) }, sizeof(be32_t)) <= 0) + return -EIO; + + unsigned dlen; + uint8_t digest[EVP_MAX_MD_SIZE]; + CLEANUP_ERASE(digest); + + if (sym_EVP_DigestFinal_ex(tmp, digest, &dlen) <= 0) + return -EIO; + + size_t n = MIN(v.iov_len, dlen); + memcpy(v.iov_base, digest, n); + iovec_inc(&v, n); + } + + return 0; +} + +/* deterministically generate from seed/idx a prime of length (secpar / 2) bits that is 3 mod 4 */ +static int generate_prime3mod4( + uint16_t secpar, + const struct iovec *seed, + uint32_t idx, + BN_CTX *bn_ctx, + BIGNUM **ret) { + + int r; + + assert(iovec_is_set(seed)); + assert(bn_ctx); + assert(ret); + + if (!secpar_is_valid(secpar)) + return -EINVAL; + + _cleanup_(iovec_erase) struct iovec iov = IOVEC_ALLOCA(secpar / 2 / 8); + r = det_randomize(seed, idx, &iov); + if (r < 0) + return r; + + uint8_t *buf = iov.iov_base; + + /* Set the upper two bits so that n = pq has the maximum size. */ + buf[0] |= 0xc0; + + /* Make the candidate congruent to 3 (mod 4). */ + buf[iov.iov_len - 1] |= 0x03; + + _cleanup_(BN_clear_freep) BIGNUM *p = NULL; + r = mpi_import(&iov, &p); + if (r < 0) + return r; + + for (;;) { + /* negative: error, 0: composite, 1: prime */ + r = sym_BN_check_prime(p, bn_ctx, /* cb= */ NULL); + if (r < 0) + return -EIO; + if (r > 0) + break; + + /* Increment p by 4. */ + if (sym_BN_add_word(p, 4) <= 0) + return -EIO; + + /* Check if p is still (secpar / 2) bits. */ + if (sym_BN_num_bits(p) != secpar / 2) + return -EOVERFLOW; + } + + *ret = TAKE_PTR(p); + return 0; +} + +static int generate_keys( + uint16_t secpar, + const struct iovec *seed, + BN_CTX *bn_ctx, + BIGNUM **ret_p, /* prime */ + BIGNUM **ret_q, /* prime */ + BIGNUM **ret_n) { /* n = p * q */ + + int r; + + assert(iovec_is_set(seed)); + assert(bn_ctx); + assert(ret_p); + assert(ret_q); + assert(ret_n); + + if (!secpar_is_valid(secpar)) + return -EINVAL; + + _cleanup_(BN_clear_freep) BIGNUM *p = NULL; + r = generate_prime3mod4(secpar, seed, RND_GEN_P, bn_ctx, &p); + if (r < 0) + return r; + + _cleanup_(BN_clear_freep) BIGNUM *q = NULL; + r = generate_prime3mod4(secpar, seed, RND_GEN_Q, bn_ctx, &q); + if (r < 0) + return r; + + _cleanup_(BN_clear_freep) BIGNUM *n = sym_BN_secure_new(); + if (!n) + return -ENOMEM; + + if (sym_BN_mul(n, p, q, bn_ctx) <= 0) + return -EIO; + + if (sym_BN_num_bits(n) != secpar) + return -EIO; + + *ret_p = TAKE_PTR(p); + *ret_q = TAKE_PTR(q); + *ret_n = TAKE_PTR(n); + return 0; +} + +/* deterministically generate from seed/idx a quadratic residue (mod n) */ +static int generate_square( + uint16_t secpar, + const struct iovec *seed, + uint32_t idx, + const BIGNUM *n, + BN_CTX *bn_ctx, + BIGNUM **ret) { + + int r; + + assert(iovec_is_set(seed)); + assert(n); + assert(bn_ctx); + assert(ret); + + if (!secpar_is_valid(secpar)) + return -EINVAL; + + _cleanup_(iovec_erase) struct iovec iov = IOVEC_ALLOCA(secpar / 8); + r = det_randomize(seed, idx, &iov); + if (r < 0) + return r; + + *(uint8_t*) iov.iov_base &= 0x7f; /* Clear the upper bit so that we are likely to have x < n. */ + + _cleanup_(BN_clear_freep) BIGNUM *x = NULL; + r = mpi_import(&iov, &x); + if (r < 0) + return r; + + /* x < n should always hold for a valid modulus generated by generate_keys(). */ + if (sym_BN_cmp(x, n) >= 0) + return -EINVAL; + + /* x := x^2 mod n */ + if (sym_BN_mod_sqr(x, x, n, bn_ctx) <= 0) + return -EIO; + + *ret = TAKE_PTR(x); + return 0; +} + +/* Compute 2^m mod phi(p), where p is prime and phi(p) = p - 1. */ +static int compute_two_pow_mod_phi( + uint64_t m, + const BIGNUM *p, + BN_CTX *bn_ctx, + BIGNUM **ret) { + + assert(p); + assert(bn_ctx); + assert(ret); + + _cleanup_(BN_clear_freep) BIGNUM *phi = sym_BN_secure_new(); + if (!phi) + return -ENOMEM; + + /* phi := p */ + if (!sym_BN_copy(phi, p)) + return -EIO; + + /* phi := p - 1 */ + if (sym_BN_sub_word(phi, 1) <= 0) + return -EIO; + + _cleanup_(BN_clear_freep) BIGNUM *x = sym_BN_secure_new(); + if (!x) + return -ENOMEM; + + /* x := 1 */ + if (!sym_BN_one(x)) + return -EIO; + + if (m == 0) { + /* 2^0 mod phi = 1. */ + *ret = TAKE_PTR(x); + return 0; + } + + /* Square-and-multiply. Iterate over the bits of m from MSB to LSB. */ + for (int n = LOG2ULL(m); n >= 0; n--) { + if (sym_BN_mod_sqr(x, x, phi, bn_ctx) <= 0) /* x := x^2 mod phi */ + return -EIO; + + if (m & (UINT64_C(1) << n) && + sym_BN_mod_lshift1_quick(x, x, phi) <= 0) /* x := 2x mod phi */ + return -EIO; + } + + *ret = TAKE_PTR(x); + return 0; +} + +/* Decompose x ∈ Z_n into (xp, xq) ∈ Z_p × Z_q using the Chinese Remainder Theorem. */ +static int crt_decompose( + const BIGNUM *x, + const BIGNUM *p, + const BIGNUM *q, + BN_CTX *bn_ctx, + BIGNUM **ret_xp, + BIGNUM **ret_xq) { + + assert(x); + assert(p); + assert(q); + assert(bn_ctx); + assert(ret_xp); + assert(ret_xq); + + _cleanup_(BN_clear_freep) BIGNUM *xp = sym_BN_secure_new(); + if (!xp) + return -ENOMEM; + + _cleanup_(BN_clear_freep) BIGNUM *xq = sym_BN_secure_new(); + if (!xq) + return -ENOMEM; + + if (sym_BN_nnmod(xp, x, p, bn_ctx) <= 0) + return -EIO; + + if (sym_BN_nnmod(xq, x, q, bn_ctx) <= 0) + return -EIO; + + *ret_xp = TAKE_PTR(xp); + *ret_xq = TAKE_PTR(xq); + return 0; +} + +/* Compose (xp, xq) ∈ Z_p × Z_q into x ∈ Z_n using the Chinese Remainder Theorem. */ +static int crt_compose( + const BIGNUM *xp, + const BIGNUM *xq, + const BIGNUM *p, + const BIGNUM *q, + BN_CTX *bn_ctx, + BIGNUM **ret) { + + assert(xp); + assert(xq); + assert(p); + assert(q); + assert(bn_ctx); + assert(ret); + + _cleanup_(BN_clear_freep) BIGNUM *x = sym_BN_secure_new(); + if (!x) + return -ENOMEM; + + /* x := xq - xp mod q */ + if (sym_BN_mod_sub(x, xq, xp, q, bn_ctx) <= 0) + return -EIO; + + /* Compute p^-1 mod q. */ + _cleanup_(BN_clear_freep) BIGNUM *p_inv = sym_BN_secure_new(); + if (!p_inv) + return -ENOMEM; + + if (!sym_BN_mod_inverse(p_inv, p, q, bn_ctx)) + return -EIO; + + /* x := (xq - xp) * p^-1 mod q */ + if (sym_BN_mod_mul(x, x, p_inv, q, bn_ctx) <= 0) + return -EIO; + + /* x := p * ((xq - xp) * p^-1 mod q) */ + if (sym_BN_mul(x, x, p, bn_ctx) <= 0) + return -EIO; + + /* x := p * ((xq - xp) * p^-1 mod q) + xp */ + if (sym_BN_add(x, x, xp) <= 0) + return -EIO; + + *ret = TAKE_PTR(x); + return 0; +} + +static int save_state( + uint16_t secpar, + const BIGNUM *modulus, + const BIGNUM *current, + uint64_t epoch, + struct iovec *state) { + + int r; + + assert(modulus); + assert(current); + assert(iovec_is_set(state)); + + if (!secpar_is_valid(secpar)) + return -EINVAL; + + if (state->iov_len != fsprg_state_size(secpar)) + return -EINVAL; + + uint8_t *s = state->iov_base; + + /* header */ + unaligned_write_be16(s, secpar / 16 - 1); + s += sizeof(uint16_t); + + /* modulus */ + r = mpi_export(&IOVEC_MAKE(s, secpar / 8), modulus); + if (r < 0) + return r; + s += secpar / 8; + + /* current */ + r = mpi_export(&IOVEC_MAKE(s, secpar / 8), current); + if (r < 0) + return r; + s += secpar / 8; + + /* epoch */ + unaligned_write_be64(s, epoch); + return 0; +} + +static int parse_state( + const struct iovec *state, + uint16_t *ret_secpar, + BIGNUM **ret_modulus, + BIGNUM **ret_current, + uint64_t *ret_epoch) { + + int r; + + assert(iovec_is_set(state)); + + /* The serialized state consists of: + * - header: 2 bytes. Encodes secpar / 16 - 1, where 16 <= secpar <= 16384. + * - modulus (a.k.a. n): secpar / 8 bytes. + * - current (a.k.a. x): secpar / 8 bytes. + * - epoch: 8 bytes. */ + + if (state->iov_len < sizeof(uint16_t)) + return -EBADMSG; + + uint16_t header = unaligned_read_be16(state->iov_base); + if (header >= 1024) + return -EBADMSG; + + uint16_t secpar = 16 * (header + 1); + if (state->iov_len != fsprg_state_size(secpar)) + return -EBADMSG; + + _cleanup_(BN_clear_freep) BIGNUM *modulus = NULL; + if (ret_modulus) { + r = mpi_import(&IOVEC_MAKE((uint8_t*) state->iov_base + sizeof(uint16_t), secpar / 8), &modulus); + if (r < 0) + return r; + } + + _cleanup_(BN_clear_freep) BIGNUM *current = NULL; + if (ret_current) { + r = mpi_import(&IOVEC_MAKE((uint8_t*) state->iov_base + sizeof(uint16_t) + secpar / 8, secpar / 8), ¤t); + if (r < 0) + return r; + } + + if (ret_secpar) + *ret_secpar = secpar; + if (ret_modulus) + *ret_modulus = TAKE_PTR(modulus); + if (ret_current) + *ret_current = TAKE_PTR(current); + if (ret_epoch) + *ret_epoch = unaligned_read_be64((uint8_t*) state->iov_base + sizeof(uint16_t) + 2 * secpar / 8); + return 0; +} +#endif + +int fsprg_generate_state( + uint16_t secpar, + uint64_t epoch, + const struct iovec *seed, + struct iovec *state) { + +#if HAVE_OPENSSL + int r; + + assert(iovec_is_set(seed)); + assert(iovec_is_set(state)); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + if (!secpar_is_valid(secpar)) + return -EINVAL; + + _cleanup_(BN_CTX_freep) BN_CTX *bn_ctx = sym_BN_CTX_secure_new(); + if (!bn_ctx) + return -ENOMEM; + + _cleanup_(BN_clear_freep) BIGNUM *p = NULL, *q = NULL, *n = NULL; + r = generate_keys(secpar, seed, bn_ctx, &p, &q, &n); + if (r < 0) + return r; + + _cleanup_(BN_clear_freep) BIGNUM *x = NULL; + r = generate_square(secpar, seed, RND_GEN_X, n, bn_ctx, &x); + if (r < 0) + return r; + + if (epoch != 0) { + /* Decompose x from Z_n into Z_p × Z_q using CRT. */ + _cleanup_(BN_clear_freep) BIGNUM *xp = NULL, *xq = NULL; + r = crt_decompose(x, p, q, bn_ctx, &xp, &xq); + if (r < 0) + return r; + + /* Compute 2^epoch (mod phi(p)). */ + _cleanup_(BN_clear_freep) BIGNUM *kp = NULL; + r = compute_two_pow_mod_phi(epoch, p, bn_ctx, &kp); + if (r < 0) + return r; + + /* Compute 2^epoch (mod phi(q)). */ + _cleanup_(BN_clear_freep) BIGNUM *kq = NULL; + r = compute_two_pow_mod_phi(epoch, q, bn_ctx, &kq); + if (r < 0) + return r; + + /* Compute x^(2^epoch) (mod p). */ + if (sym_BN_mod_exp(xp, xp, kp, p, bn_ctx) <= 0) + return -EIO; + + /* Compute x^(2^epoch) (mod q). */ + if (sym_BN_mod_exp(xq, xq, kq, q, bn_ctx) <= 0) + return -EIO; + + /* Reconstruct x modulo n from its residues modulo p and q. */ + BN_clear_freep(&x); + r = crt_compose(xp, xq, p, q, bn_ctx, &x); + if (r < 0) + return r; + } + + return save_state(secpar, n, x, epoch, state); +#else + return -EOPNOTSUPP; +#endif +} + +int fsprg_evolve(struct iovec *state) { +#if HAVE_OPENSSL + int r; + + assert(iovec_is_set(state)); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + uint16_t secpar; + uint64_t epoch; + _cleanup_(BN_clear_freep) BIGNUM *n = NULL, *x = NULL; + r = parse_state(state, &secpar, &n, &x, &epoch); + if (r < 0) + return r; + + _cleanup_(BN_CTX_freep) BN_CTX *bn_ctx = sym_BN_CTX_secure_new(); + if (!bn_ctx) + return -ENOMEM; + + /* Update the current state x := x^2 mod n */ + if (sym_BN_mod_sqr(x, x, n, bn_ctx) <= 0) + return -EIO; + + /* Store the updated current state. */ + uint8_t *s = (uint8_t*) state->iov_base + sizeof(uint16_t) + secpar / 8; + r = mpi_export(&IOVEC_MAKE(s, secpar / 8), x); + if (r < 0) + return r; + + /* Increment epoch */ + unaligned_write_be64(s + secpar / 8, epoch + 1); + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +int fsprg_get_epoch(const struct iovec *state, uint64_t *ret) { +#if HAVE_OPENSSL + int r; + + assert(iovec_is_set(state)); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + return parse_state( + state, + /* ret_secpar= */ NULL, + /* ret_modulus= */ NULL, + /* ret_current= */ NULL, + ret); +#else + return -EOPNOTSUPP; +#endif +} + +int fsprg_get_key(const struct iovec *state, struct iovec *key) { +#if HAVE_OPENSSL + int r; + + assert(iovec_is_set(state)); + assert(iovec_is_valid(key)); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + uint16_t secpar; + r = parse_state(state, + /* ret_secpar= */ &secpar, + /* ret_modulus= */ NULL, + /* ret_current= */ NULL, + /* ret_epoch= */ NULL); + if (r < 0) + return r; + + struct iovec seed = IOVEC_MAKE( + (uint8_t*) state->iov_base + sizeof(uint16_t), + 2 * secpar / 8 + sizeof(uint64_t)); + + return det_randomize(&seed, /* idx= */ 0, key); +#else + return -EOPNOTSUPP; +#endif +} diff --git a/src/shared/fsprg-openssl.h b/src/shared/fsprg-openssl.h new file mode 100644 index 00000000000..e9e56496173 --- /dev/null +++ b/src/shared/fsprg-openssl.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#define FSPRG_RECOMMENDED_SECPAR 1536 +#define FSPRG_RECOMMENDED_SEEDLEN (96/8) + +bool fsprg_secpar_is_valid(uint16_t secpar); +size_t fsprg_state_size(uint16_t secpar); +int fsprg_generate_state( + uint16_t secpar, + uint64_t epoch, + const struct iovec *seed, + struct iovec *state); +int fsprg_evolve(struct iovec *state); +int fsprg_get_epoch(const struct iovec *state, uint64_t *ret); +int fsprg_get_key(const struct iovec *state, struct iovec *key); diff --git a/src/shared/meson.build b/src/shared/meson.build index 0bd41719f7a..24536b87ab2 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -88,6 +88,7 @@ shared_sources = files( 'fork-notify.c', 'format-table.c', 'fsprg.c', + 'fsprg-openssl.c', 'fstab-util.c', 'gcrypt-util.c', 'generator.c', diff --git a/src/test/meson.build b/src/test/meson.build index 53ff5c3ab78..7970dc89506 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -342,6 +342,7 @@ executables += [ ), 'dependencies' : [ libgcrypt_cflags, + libopenssl_cflags, ], 'conditions' : ['HAVE_GCRYPT'], }, diff --git a/src/test/test-fsprg.c b/src/test/test-fsprg.c index 07c758f314e..94183e22462 100644 --- a/src/test/test-fsprg.c +++ b/src/test/test-fsprg.c @@ -4,7 +4,9 @@ # include #endif +#include "crypto-util.h" #include "fsprg.h" +#include "fsprg-openssl.h" #include "gcrypt-util.h" #include "iovec-util.h" #include "journal-def.h" @@ -339,6 +341,48 @@ TEST(fsprg) { ASSERT_TRUE(iovec_equal(&state, &states[i])); } } + + if (dlopen_libcrypto(LOG_DEBUG) >= 0) { + struct iovec seed = IOVEC_ALLOCA(FSPRG_RECOMMENDED_SEEDLEN), + state = IOVEC_ALLOCA(fsprg_state_size(FSPRG_RECOMMENDED_SECPAR)), + key = IOVEC_ALLOCA(32); + + generate_seed(&seed); + + /* Generate states by fsprg_generate_state() */ + for (size_t i = 0; i < n_states; i++) { + ASSERT_OK(fsprg_generate_state(FSPRG_RECOMMENDED_SECPAR, i, &seed, &state)); + + /* Verify state */ + if (i < ELEMENTSOF(expected_state)) + ASSERT_EQ(iovec_memcmp(&state, &IOVEC_MAKE(expected_state[i], sizeof(expected_state[i]))), 0); + + /* Verify key */ + ASSERT_OK(fsprg_get_key(&state, &key)); + if (i < ELEMENTSOF(expected_key)) + ASSERT_EQ(iovec_memcmp(&key, &IOVEC_MAKE(expected_key[i], sizeof(expected_key[i]))), 0); + + /* Verify epoch */ + uint64_t epoch; + ASSERT_OK(fsprg_get_epoch(&state, &epoch)); + ASSERT_EQ(epoch, (uint64_t) i); + + if (iovec_is_set(&states[i])) + ASSERT_EQ(iovec_memcmp(&state, &states[i]), 0); + else + ASSERT_NOT_NULL(iovec_memdup(&state, &states[i])); + } + + /* Generate state by fsprg_generate_state(0) */ + ASSERT_OK(fsprg_generate_state(FSPRG_RECOMMENDED_SECPAR, 0, &seed, &state)); + ASSERT_EQ(iovec_memcmp(&state, &states[0]), 0); + + /* Generate states by fsprg_evolve() */ + for (unsigned i = 1; i < n_states; i++) { + ASSERT_OK(fsprg_evolve(&state)); + ASSERT_EQ(iovec_memcmp(&state, &states[i]), 0); + } + } } TEST(hmac) { @@ -355,6 +399,8 @@ TEST(hmac) { * journal_file_maybe_append_tag(). */ size_t n_tags = 20; + struct iovec *tags = new0(struct iovec, n_tags); + CLEANUP_ARRAY(tags, n_tags, iovec_array_free); #if HAVE_GCRYPT if (initialize_libgcrypt(false) >= 0) { @@ -396,15 +442,67 @@ TEST(hmac) { if (i < ELEMENTSOF(expected_tag)) ASSERT_TRUE(iovec_equal(&tag, &IOVEC_MAKE(expected_tag[i], sizeof(expected_tag[i])))); + /* Save tag */ + ASSERT_NOT_NULL(iovec_memdup(&tag, &tags[i])); + /* Increment epoch */ ASSERT_OK(FSPRG_Evolve(state.iov_base)); } } #endif + +#if HAVE_OPENSSL + if (dlopen_libcrypto(LOG_DEBUG) >= 0) { + struct iovec seed = IOVEC_ALLOCA(FSPRG_RECOMMENDED_SEEDLEN), + state = IOVEC_ALLOCA(fsprg_state_size(FSPRG_RECOMMENDED_SECPAR)), + key = IOVEC_ALLOCA(KEY_LENGTH), + tag = IOVEC_ALLOCA(TAG_LENGTH); + + generate_seed(&seed); + + /* Calculate initial state */ + ASSERT_OK(fsprg_generate_state(FSPRG_RECOMMENDED_SECPAR, 0, &seed, &state)); + + /* Allocate HMAC */ + _cleanup_(EVP_MAC_freep) EVP_MAC *hmac = ASSERT_NOT_NULL(sym_EVP_MAC_fetch(NULL, "HMAC", NULL)); + _cleanup_(EVP_MAC_CTX_freep) EVP_MAC_CTX *hmac_ctx = ASSERT_NOT_NULL(sym_EVP_MAC_CTX_new(hmac)); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = ASSERT_NOT_NULL(sym_OSSL_PARAM_BLD_new()); + ASSERT_OK_POSITIVE(sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_MAC_PARAM_DIGEST, "SHA256", 0)); + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = ASSERT_NOT_NULL(sym_OSSL_PARAM_BLD_to_param(bld)); + + for (size_t i = 0; i < n_tags; i++) { + /* Initialize HMAC with fsprg key */ + ASSERT_OK(fsprg_get_key(&state, &key)); + ASSERT_OK_POSITIVE(sym_EVP_MAC_init(hmac_ctx, key.iov_base, key.iov_len, params)); + + /* Put dummy data */ + _cleanup_(iovec_done) struct iovec data = + IOVEC_MAKE_STRING(ASSERT_NOT_NULL(asprintf_safe("dummy data: %zu, hoge hoge hoge hoge hoge", i))); + ASSERT_OK_POSITIVE(sym_EVP_MAC_update(hmac_ctx, data.iov_base, data.iov_len)); + + /* Finalize HMAC and generate tag. */ + size_t len; + ASSERT_OK_POSITIVE(sym_EVP_MAC_final(hmac_ctx, tag.iov_base, &len, tag.iov_len)); + ASSERT_EQ(len, tag.iov_len); + + /* Verify tag */ + if (i < ELEMENTSOF(expected_tag)) + ASSERT_TRUE(iovec_equal(&tag, &IOVEC_MAKE(expected_tag[i], sizeof(expected_tag[i])))); + + /* Compare with the tag generated by gcrypt */ + if (iovec_is_set(&tags[i])) + ASSERT_TRUE(iovec_equal(&tag, &tags[i])); + + /* Increment epoch */ + ASSERT_OK(fsprg_evolve(&state)); + } + } +#endif } static int intro(void) { - if (DLOPEN_GCRYPT(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0) + if (DLOPEN_GCRYPT(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0 && + DLOPEN_LIBCRYPTO(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0) return EXIT_TEST_SKIP; return EXIT_SUCCESS;