/* SPDX-License-Identifier: LGPL-2.1-or-later */

#if HAVE_GCRYPT

#include "gcrypt-util.h"
#include "hexdecoct.h"

static void *gcrypt_dl = NULL;

static DLSYM_FUNCTION(gcry_control);
static DLSYM_FUNCTION(gcry_check_version);
DLSYM_FUNCTION(gcry_md_close);
DLSYM_FUNCTION(gcry_md_copy);
DLSYM_FUNCTION(gcry_md_ctl);
DLSYM_FUNCTION(gcry_md_get_algo_dlen);
DLSYM_FUNCTION(gcry_md_open);
DLSYM_FUNCTION(gcry_md_read);
DLSYM_FUNCTION(gcry_md_reset);
DLSYM_FUNCTION(gcry_md_setkey);
DLSYM_FUNCTION(gcry_md_write);
DLSYM_FUNCTION(gcry_mpi_add);
DLSYM_FUNCTION(gcry_mpi_add_ui);
DLSYM_FUNCTION(gcry_mpi_cmp);
DLSYM_FUNCTION(gcry_mpi_cmp_ui);
DLSYM_FUNCTION(gcry_mpi_get_nbits);
DLSYM_FUNCTION(gcry_mpi_invm);
DLSYM_FUNCTION(gcry_mpi_mod);
DLSYM_FUNCTION(gcry_mpi_mul);
DLSYM_FUNCTION(gcry_mpi_mulm);
DLSYM_FUNCTION(gcry_mpi_new);
DLSYM_FUNCTION(gcry_mpi_powm);
DLSYM_FUNCTION(gcry_mpi_print);
DLSYM_FUNCTION(gcry_mpi_release);
DLSYM_FUNCTION(gcry_mpi_scan);
DLSYM_FUNCTION(gcry_mpi_set_ui);
DLSYM_FUNCTION(gcry_mpi_sub);
DLSYM_FUNCTION(gcry_mpi_subm);
DLSYM_FUNCTION(gcry_mpi_sub_ui);
DLSYM_FUNCTION(gcry_prime_check);
DLSYM_FUNCTION(gcry_randomize);
DLSYM_FUNCTION(gcry_strerror);

static int dlopen_gcrypt(void) {
        ELF_NOTE_DLOPEN("gcrypt",
                        "Support for journald forward-sealing",
                        ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
                        "libgcrypt.so.20");

        return dlopen_many_sym_or_warn(
                        &gcrypt_dl,
                        "libgcrypt.so.20", LOG_DEBUG,
                        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));
}

int initialize_libgcrypt(bool secmem) {
        int r;

        r = dlopen_gcrypt();
        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;
}

#  if !PREFER_OPENSSL
int string_hashsum(const char *s, size_t len, int md_algorithm, char **out) {
        _cleanup_(sym_gcry_md_closep) gcry_md_hd_t md = NULL;
        gcry_error_t err;
        size_t hash_size;
        void *hash;
        char *enc;
        int r;

        r = initialize_libgcrypt(false);
        if (r < 0)
                return r;

        hash_size = sym_gcry_md_get_algo_dlen(md_algorithm);
        assert(hash_size > 0);

        err = sym_gcry_md_open(&md, md_algorithm, 0);
        if (gcry_err_code(err) != GPG_ERR_NO_ERROR || !md)
                return -EIO;

        sym_gcry_md_write(md, s, len);

        hash = sym_gcry_md_read(md, 0);
        if (!hash)
                return -EIO;

        enc = hexmem(hash, hash_size);
        if (!enc)
                return -ENOMEM;

        *out = enc;
        return 0;
}
#  endif
#endif
