/*
 * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the Apache License 2.0 (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 *
 * RFC 9106 Argon2 (see https://www.rfc-editor.org/rfc/rfc9106.txt)
 *
 */

#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <openssl/e_os2.h>
#include <openssl/evp.h>
#include <openssl/objects.h>
#include <openssl/crypto.h>
#include <openssl/kdf.h>
#include <openssl/err.h>
#include <openssl/core_names.h>
#include <openssl/params.h>
#include <openssl/thread.h>
#include <openssl/proverr.h>
#include "internal/thread.h"
#include "internal/numbers.h"
#include "internal/endian.h"
#include "crypto/evp.h"
#include "prov/implementations.h"
#include "prov/provider_ctx.h"
#include "prov/providercommon.h"
#include "prov/blake2.h"

#if defined(OPENSSL_NO_DEFAULT_THREAD_POOL) && defined(OPENSSL_NO_THREAD_POOL)
#define ARGON2_NO_THREADS
#endif

#if !defined(OPENSSL_THREADS)
#define ARGON2_NO_THREADS
#endif

#ifndef OPENSSL_NO_ARGON2

#define ARGON2_MIN_LANES 1u
#define ARGON2_MAX_LANES 0xFFFFFFu
#define ARGON2_MIN_THREADS 1u
#define ARGON2_MAX_THREADS 0xFFFFFFu
#define ARGON2_SYNC_POINTS 4u
#define ARGON2_MIN_OUT_LENGTH 4u
#define ARGON2_MAX_OUT_LENGTH 0xFFFFFFFFu
#define ARGON2_MIN_MEMORY (2 * ARGON2_SYNC_POINTS)
#define ARGON2_MIN(a, b) ((a) < (b) ? (a) : (b))
#define ARGON2_MAX_MEMORY 0xFFFFFFFFu
#define ARGON2_MIN_TIME 1u
#define ARGON2_MAX_TIME 0xFFFFFFFFu
#define ARGON2_MIN_PWD_LENGTH 0u
#define ARGON2_MAX_PWD_LENGTH 0xFFFFFFFFu
#define ARGON2_MIN_AD_LENGTH 0u
#define ARGON2_MAX_AD_LENGTH 0xFFFFFFFFu
#define ARGON2_MIN_SALT_LENGTH 8u
#define ARGON2_MAX_SALT_LENGTH 0xFFFFFFFFu
#define ARGON2_MIN_SECRET 0u
#define ARGON2_MAX_SECRET 0xFFFFFFFFu
#define ARGON2_BLOCK_SIZE 1024
#define ARGON2_QWORDS_IN_BLOCK ((ARGON2_BLOCK_SIZE) / 8)
#define ARGON2_OWORDS_IN_BLOCK ((ARGON2_BLOCK_SIZE) / 16)
#define ARGON2_HWORDS_IN_BLOCK ((ARGON2_BLOCK_SIZE) / 32)
#define ARGON2_512BIT_WORDS_IN_BLOCK ((ARGON2_BLOCK_SIZE) / 64)
#define ARGON2_ADDRESSES_IN_BLOCK 128
#define ARGON2_PREHASH_DIGEST_LENGTH 64
#define ARGON2_PREHASH_SEED_LENGTH \
    (ARGON2_PREHASH_DIGEST_LENGTH + (2 * sizeof(uint32_t)))

#define ARGON2_DEFAULT_OUTLEN 64u
#define ARGON2_DEFAULT_T_COST 3u
#define ARGON2_DEFAULT_M_COST ARGON2_MIN_MEMORY
#define ARGON2_DEFAULT_LANES 1u
#define ARGON2_DEFAULT_THREADS 1u
#define ARGON2_DEFAULT_VERSION ARGON2_VERSION_NUMBER

#undef G
#define G(a, b, c, d)                    \
    do {                                 \
        a = a + b + 2 * mul_lower(a, b); \
        d = rotr64(d ^ a, 32);           \
        c = c + d + 2 * mul_lower(c, d); \
        b = rotr64(b ^ c, 24);           \
        a = a + b + 2 * mul_lower(a, b); \
        d = rotr64(d ^ a, 16);           \
        c = c + d + 2 * mul_lower(c, d); \
        b = rotr64(b ^ c, 63);           \
    } while ((void)0, 0)

#undef PERMUTATION_P
#define PERMUTATION_P(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, \
    v12, v13, v14, v15)                                                 \
    do {                                                                \
        G(v0, v4, v8, v12);                                             \
        G(v1, v5, v9, v13);                                             \
        G(v2, v6, v10, v14);                                            \
        G(v3, v7, v11, v15);                                            \
        G(v0, v5, v10, v15);                                            \
        G(v1, v6, v11, v12);                                            \
        G(v2, v7, v8, v13);                                             \
        G(v3, v4, v9, v14);                                             \
    } while ((void)0, 0)

#undef PERMUTATION_P_COLUMN
#define PERMUTATION_P_COLUMN(x, i)                                   \
    do {                                                             \
        uint64_t *base = &x[16 * i];                                 \
        PERMUTATION_P(                                               \
            *base, *(base + 1), *(base + 2), *(base + 3),            \
            *(base + 4), *(base + 5), *(base + 6), *(base + 7),      \
            *(base + 8), *(base + 9), *(base + 10), *(base + 11),    \
            *(base + 12), *(base + 13), *(base + 14), *(base + 15)); \
    } while ((void)0, 0)

#undef PERMUTATION_P_ROW
#define PERMUTATION_P_ROW(x, i)                                        \
    do {                                                               \
        uint64_t *base = &x[2 * i];                                    \
        PERMUTATION_P(                                                 \
            *base, *(base + 1), *(base + 16), *(base + 17),            \
            *(base + 32), *(base + 33), *(base + 48), *(base + 49),    \
            *(base + 64), *(base + 65), *(base + 80), *(base + 81),    \
            *(base + 96), *(base + 97), *(base + 112), *(base + 113)); \
    } while ((void)0, 0)

typedef struct {
    uint64_t v[ARGON2_QWORDS_IN_BLOCK];
} BLOCK;

typedef enum {
    ARGON2_VERSION_10 = 0x10,
    ARGON2_VERSION_13 = 0x13,
    ARGON2_VERSION_NUMBER = ARGON2_VERSION_13
} ARGON2_VERSION;

typedef enum {
    ARGON2_D = 0,
    ARGON2_I = 1,
    ARGON2_ID = 2
} ARGON2_TYPE;

typedef struct {
    uint32_t pass;
    uint32_t lane;
    uint8_t slice;
    uint32_t index;
} ARGON2_POS;

typedef struct {
    void *provctx;
    uint32_t outlen;
    uint8_t *pwd;
    uint32_t pwdlen;
    uint8_t *salt;
    uint32_t saltlen;
    uint8_t *secret;
    uint32_t secretlen;
    uint8_t *ad;
    uint32_t adlen;
    uint32_t t_cost;
    uint32_t m_cost;
    uint32_t lanes;
    uint32_t threads;
    uint32_t version;
    uint32_t early_clean;
    ARGON2_TYPE type;
    BLOCK *memory;
    uint32_t passes;
    uint32_t memory_blocks;
    uint32_t segment_length;
    uint32_t lane_length;
    OSSL_LIB_CTX *libctx;
    EVP_MD *md;
    EVP_MAC *mac;
    char *propq;
} KDF_ARGON2;

typedef struct {
    ARGON2_POS pos;
    KDF_ARGON2 *ctx;
} ARGON2_THREAD_DATA;

static OSSL_FUNC_kdf_newctx_fn kdf_argon2i_new;
static OSSL_FUNC_kdf_newctx_fn kdf_argon2d_new;
static OSSL_FUNC_kdf_newctx_fn kdf_argon2id_new;
static OSSL_FUNC_kdf_freectx_fn kdf_argon2_free;
static OSSL_FUNC_kdf_reset_fn kdf_argon2_reset;
static OSSL_FUNC_kdf_derive_fn kdf_argon2_derive;
static OSSL_FUNC_kdf_settable_ctx_params_fn kdf_argon2_settable_ctx_params;
static OSSL_FUNC_kdf_set_ctx_params_fn kdf_argon2_set_ctx_params;

static void kdf_argon2_init(KDF_ARGON2 *ctx, ARGON2_TYPE t);
static void *kdf_argon2d_new(void *provctx);
static void *kdf_argon2i_new(void *provctx);
static void *kdf_argon2id_new(void *provctx);
static void kdf_argon2_free(void *vctx);
static int kdf_argon2_derive(void *vctx, unsigned char *out, size_t outlen,
    const OSSL_PARAM params[]);
static void kdf_argon2_reset(void *vctx);
static int kdf_argon2_ctx_set_threads(KDF_ARGON2 *ctx, uint32_t threads);
static int kdf_argon2_ctx_set_lanes(KDF_ARGON2 *ctx, uint32_t lanes);
static int kdf_argon2_ctx_set_t_cost(KDF_ARGON2 *ctx, uint32_t t_cost);
static int kdf_argon2_ctx_set_m_cost(KDF_ARGON2 *ctx, uint32_t m_cost);
static int kdf_argon2_ctx_set_out_length(KDF_ARGON2 *ctx, uint32_t outlen);
static int kdf_argon2_ctx_set_secret(KDF_ARGON2 *ctx, const OSSL_PARAM *p);
static int kdf_argon2_ctx_set_pwd(KDF_ARGON2 *ctx, const OSSL_PARAM *p);
static int kdf_argon2_ctx_set_salt(KDF_ARGON2 *ctx, const OSSL_PARAM *p);
static int kdf_argon2_ctx_set_ad(KDF_ARGON2 *ctx, const OSSL_PARAM *p);
static int kdf_argon2_set_ctx_params(void *vctx, const OSSL_PARAM params[]);
static int kdf_argon2_get_ctx_params(void *vctx, OSSL_PARAM params[]);
static int kdf_argon2_ctx_set_version(KDF_ARGON2 *ctx, uint32_t version);
static const OSSL_PARAM *kdf_argon2_settable_ctx_params(ossl_unused void *ctx,
    ossl_unused void *p_ctx);
static const OSSL_PARAM *kdf_argon2_gettable_ctx_params(ossl_unused void *ctx,
    ossl_unused void *p_ctx);

static ossl_inline uint64_t load64(const uint8_t *src);
static ossl_inline void store32(uint8_t *dst, uint32_t w);
static ossl_inline void store64(uint8_t *dst, uint64_t w);
static ossl_inline uint64_t rotr64(const uint64_t w, const unsigned int c);
static ossl_inline uint64_t mul_lower(uint64_t x, uint64_t y);

static void init_block_value(BLOCK *b, uint8_t in);
static void copy_block(BLOCK *dst, const BLOCK *src);
static void xor_block(BLOCK *dst, const BLOCK *src);
static void load_block(BLOCK *dst, const void *input);
static void store_block(void *output, const BLOCK *src);
static void fill_first_blocks(uint8_t *blockhash, const KDF_ARGON2 *ctx);
static void fill_block(const BLOCK *prev, const BLOCK *ref, BLOCK *next,
    int with_xor);

static void next_addresses(BLOCK *address_block, BLOCK *input_block,
    const BLOCK *zero_block);
static int data_indep_addressing(const KDF_ARGON2 *ctx, uint32_t pass,
    uint8_t slice);
static uint32_t index_alpha(const KDF_ARGON2 *ctx, uint32_t pass,
    uint8_t slice, uint32_t index,
    uint32_t pseudo_rand, int same_lane);

static void fill_segment(const KDF_ARGON2 *ctx, uint32_t pass, uint32_t lane,
    uint8_t slice);

#if !defined(ARGON2_NO_THREADS)
static uint32_t fill_segment_thr(void *thread_data);
static int fill_mem_blocks_mt(KDF_ARGON2 *ctx);
#endif

static int fill_mem_blocks_st(KDF_ARGON2 *ctx);
static ossl_inline int fill_memory_blocks(KDF_ARGON2 *ctx);

static void initial_hash(uint8_t *blockhash, KDF_ARGON2 *ctx);
static int initialize(KDF_ARGON2 *ctx);
static void finalize(const KDF_ARGON2 *ctx, void *out);

static int blake2b(EVP_MD *md, EVP_MAC *mac, void *out, size_t outlen,
    const void *in, size_t inlen, const void *key,
    size_t keylen);
static int blake2b_long(EVP_MD *md, EVP_MAC *mac, unsigned char *out,
    size_t outlen, const void *in, size_t inlen);

static ossl_inline uint64_t load64(const uint8_t *src)
{
    return (((uint64_t)src[0]) << 0)
        | (((uint64_t)src[1]) << 8)
        | (((uint64_t)src[2]) << 16)
        | (((uint64_t)src[3]) << 24)
        | (((uint64_t)src[4]) << 32)
        | (((uint64_t)src[5]) << 40)
        | (((uint64_t)src[6]) << 48)
        | (((uint64_t)src[7]) << 56);
}

static ossl_inline void store32(uint8_t *dst, uint32_t w)
{
    dst[0] = (uint8_t)(w >> 0);
    dst[1] = (uint8_t)(w >> 8);
    dst[2] = (uint8_t)(w >> 16);
    dst[3] = (uint8_t)(w >> 24);
}

static ossl_inline void store64(uint8_t *dst, uint64_t w)
{
    dst[0] = (uint8_t)(w >> 0);
    dst[1] = (uint8_t)(w >> 8);
    dst[2] = (uint8_t)(w >> 16);
    dst[3] = (uint8_t)(w >> 24);
    dst[4] = (uint8_t)(w >> 32);
    dst[5] = (uint8_t)(w >> 40);
    dst[6] = (uint8_t)(w >> 48);
    dst[7] = (uint8_t)(w >> 56);
}

static ossl_inline uint64_t rotr64(const uint64_t w, const unsigned int c)
{
    return (w >> c) | (w << (64 - c));
}

static ossl_inline uint64_t mul_lower(uint64_t x, uint64_t y)
{
    const uint64_t m = 0xFFFFFFFFUL;
    return (x & m) * (y & m);
}

static void init_block_value(BLOCK *b, uint8_t in)
{
    memset(b->v, in, sizeof(b->v));
}

static void copy_block(BLOCK *dst, const BLOCK *src)
{
    memcpy(dst->v, src->v, sizeof(uint64_t) * ARGON2_QWORDS_IN_BLOCK);
}

static void xor_block(BLOCK *dst, const BLOCK *src)
{
    int i;

    for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i)
        dst->v[i] ^= src->v[i];
}

static void load_block(BLOCK *dst, const void *input)
{
    unsigned i;

    for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i)
        dst->v[i] = load64((const uint8_t *)input + i * sizeof(dst->v[i]));
}

static void store_block(void *output, const BLOCK *src)
{
    unsigned i;

    for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i)
        store64((uint8_t *)output + i * sizeof(src->v[i]), src->v[i]);
}

static void fill_first_blocks(uint8_t *blockhash, const KDF_ARGON2 *ctx)
{
    uint32_t l;
    uint8_t blockhash_bytes[ARGON2_BLOCK_SIZE];

    /*
     * Make the first and second block in each lane as G(H0||0||i)
     * or G(H0||1||i).
     */
    for (l = 0; l < ctx->lanes; ++l) {
        store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 0);
        store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH + 4, l);
        blake2b_long(ctx->md, ctx->mac, blockhash_bytes, ARGON2_BLOCK_SIZE,
            blockhash, ARGON2_PREHASH_SEED_LENGTH);
        load_block(&ctx->memory[l * ctx->lane_length + 0],
            blockhash_bytes);
        store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 1);
        blake2b_long(ctx->md, ctx->mac, blockhash_bytes, ARGON2_BLOCK_SIZE,
            blockhash, ARGON2_PREHASH_SEED_LENGTH);
        load_block(&ctx->memory[l * ctx->lane_length + 1],
            blockhash_bytes);
    }
    OPENSSL_cleanse(blockhash_bytes, ARGON2_BLOCK_SIZE);
}

static void fill_block(const BLOCK *prev, const BLOCK *ref,
    BLOCK *next, int with_xor)
{
    BLOCK blockR, tmp;
    unsigned i;

    copy_block(&blockR, ref);
    xor_block(&blockR, prev);
    copy_block(&tmp, &blockR);

    if (with_xor)
        xor_block(&tmp, next);

    for (i = 0; i < 8; ++i)
        PERMUTATION_P_COLUMN(blockR.v, i);

    for (i = 0; i < 8; ++i)
        PERMUTATION_P_ROW(blockR.v, i);

    copy_block(next, &tmp);
    xor_block(next, &blockR);
}

static void next_addresses(BLOCK *address_block, BLOCK *input_block,
    const BLOCK *zero_block)
{
    input_block->v[6]++;
    fill_block(zero_block, input_block, address_block, 0);
    fill_block(zero_block, address_block, address_block, 0);
}

static int data_indep_addressing(const KDF_ARGON2 *ctx, uint32_t pass,
    uint8_t slice)
{
    switch (ctx->type) {
    case ARGON2_I:
        return 1;
    case ARGON2_ID:
        return (pass == 0) && (slice < ARGON2_SYNC_POINTS / 2);
    case ARGON2_D:
    default:
        return 0;
    }
}

/*
 * Pass 0 (pass = 0):
 * This lane: all already finished segments plus already constructed blocks
 *            in this segment
 * Other lanes: all already finished segments
 *
 * Pass 1+:
 * This lane: (SYNC_POINTS - 1) last segments plus already constructed
 *            blocks in this segment
 * Other lanes: (SYNC_POINTS - 1) last segments
 */
static uint32_t index_alpha(const KDF_ARGON2 *ctx, uint32_t pass,
    uint8_t slice, uint32_t index,
    uint32_t pseudo_rand, int same_lane)
{
    uint32_t ref_area_sz;
    uint64_t rel_pos;
    uint32_t start_pos, abs_pos;

    start_pos = 0;
    switch (pass) {
    case 0:
        if (slice == 0)
            ref_area_sz = index - 1;
        else if (same_lane)
            ref_area_sz = slice * ctx->segment_length + index - 1;
        else
            ref_area_sz = slice * ctx->segment_length + ((index == 0) ? (-1) : 0);
        break;
    default:
        if (same_lane)
            ref_area_sz = ctx->lane_length - ctx->segment_length + index - 1;
        else
            ref_area_sz = ctx->lane_length - ctx->segment_length + ((index == 0) ? (-1) : 0);
        if (slice != ARGON2_SYNC_POINTS - 1)
            start_pos = (slice + 1) * ctx->segment_length;
        break;
    }

    rel_pos = pseudo_rand;
    rel_pos = rel_pos * rel_pos >> 32;
    rel_pos = ref_area_sz - 1 - (ref_area_sz * rel_pos >> 32);
    abs_pos = (start_pos + rel_pos) % ctx->lane_length;

    return abs_pos;
}

static void fill_segment(const KDF_ARGON2 *ctx, uint32_t pass, uint32_t lane,
    uint8_t slice)
{
    BLOCK *ref_block = NULL, *curr_block = NULL;
    BLOCK address_block, input_block, zero_block;
    uint64_t rnd, ref_index, ref_lane;
    uint32_t prev_offset;
    uint32_t start_idx;
    uint32_t j;
    uint32_t curr_offset; /* Offset of the current block */

    memset(&input_block, 0, sizeof(BLOCK));

    if (ctx == NULL)
        return;

    if (data_indep_addressing(ctx, pass, slice)) {
        init_block_value(&zero_block, 0);
        init_block_value(&input_block, 0);

        input_block.v[0] = pass;
        input_block.v[1] = lane;
        input_block.v[2] = slice;
        input_block.v[3] = ctx->memory_blocks;
        input_block.v[4] = ctx->passes;
        input_block.v[5] = ctx->type;
    }

    start_idx = 0;

    /* We've generated the first two blocks. Generate the 1st block of addrs. */
    if ((pass == 0) && (slice == 0)) {
        start_idx = 2;
        if (data_indep_addressing(ctx, pass, slice))
            next_addresses(&address_block, &input_block, &zero_block);
    }

    curr_offset = lane * ctx->lane_length + slice * ctx->segment_length
        + start_idx;

    if ((curr_offset % ctx->lane_length) == 0)
        prev_offset = curr_offset + ctx->lane_length - 1;
    else
        prev_offset = curr_offset - 1;

    for (j = start_idx; j < ctx->segment_length; ++j, ++curr_offset, ++prev_offset) {
        if (curr_offset % ctx->lane_length == 1)
            prev_offset = curr_offset - 1;

        /* Taking pseudo-random value from the previous block. */
        if (data_indep_addressing(ctx, pass, slice)) {
            if (j % ARGON2_ADDRESSES_IN_BLOCK == 0)
                next_addresses(&address_block, &input_block, &zero_block);
            rnd = address_block.v[j % ARGON2_ADDRESSES_IN_BLOCK];
        } else {
            rnd = ctx->memory[prev_offset].v[0];
        }

        /* Computing the lane of the reference block */
        ref_lane = ((rnd >> 32)) % ctx->lanes;
        /* Can not reference other lanes yet */
        if ((pass == 0) && (slice == 0))
            ref_lane = lane;

        /* Computing the number of possible reference block within the lane. */
        ref_index = index_alpha(ctx, pass, slice, j, rnd & 0xFFFFFFFF,
            ref_lane == lane);

        /* Creating a new block */
        ref_block = ctx->memory + ctx->lane_length * ref_lane + ref_index;
        curr_block = ctx->memory + curr_offset;
        if (ARGON2_VERSION_10 == ctx->version) {
            /* Version 1.2.1 and earlier: overwrite, not XOR */
            fill_block(ctx->memory + prev_offset, ref_block, curr_block, 0);
            continue;
        }

        fill_block(ctx->memory + prev_offset, ref_block, curr_block,
            pass == 0 ? 0 : 1);
    }
}

#if !defined(ARGON2_NO_THREADS)

static uint32_t fill_segment_thr(void *thread_data)
{
    ARGON2_THREAD_DATA *my_data;

    my_data = (ARGON2_THREAD_DATA *)thread_data;
    fill_segment(my_data->ctx, my_data->pos.pass, my_data->pos.lane,
        my_data->pos.slice);

    return 0;
}

static int fill_mem_blocks_mt(KDF_ARGON2 *ctx)
{
    uint32_t r, s, l, ll;
    void **t;
    ARGON2_THREAD_DATA *t_data;

    t = OPENSSL_zalloc(sizeof(void *) * ctx->lanes);
    t_data = OPENSSL_zalloc(ctx->lanes * sizeof(ARGON2_THREAD_DATA));

    if (t == NULL || t_data == NULL)
        goto fail;

    for (r = 0; r < ctx->passes; ++r) {
        for (s = 0; s < ARGON2_SYNC_POINTS; ++s) {
            for (l = 0; l < ctx->lanes; ++l) {
                ARGON2_POS p;
                if (l >= ctx->threads) {
                    if (ossl_crypto_thread_join(t[l - ctx->threads], NULL) == 0)
                        goto fail;
                    if (ossl_crypto_thread_clean(t[l - ctx->threads]) == 0)
                        goto fail;
                    t[l] = NULL;
                }

                p.pass = r;
                p.lane = l;
                p.slice = (uint8_t)s;
                p.index = 0;

                t_data[l].ctx = ctx;
                memcpy(&(t_data[l].pos), &p, sizeof(ARGON2_POS));
                t[l] = ossl_crypto_thread_start(ctx->libctx, &fill_segment_thr,
                    (void *)&t_data[l]);
                if (t[l] == NULL) {
                    for (ll = 0; ll < l; ++ll) {
                        if (ossl_crypto_thread_join(t[ll], NULL) == 0)
                            goto fail;
                        if (ossl_crypto_thread_clean(t[ll]) == 0)
                            goto fail;
                        t[ll] = NULL;
                    }
                    goto fail;
                }
            }
            for (l = ctx->lanes - ctx->threads; l < ctx->lanes; ++l) {
                if (ossl_crypto_thread_join(t[l], NULL) == 0)
                    goto fail;
                if (ossl_crypto_thread_clean(t[l]) == 0)
                    goto fail;
                t[l] = NULL;
            }
        }
    }

    OPENSSL_free(t_data);
    OPENSSL_free(t);

    return 1;

fail:
    if (t_data != NULL)
        OPENSSL_free(t_data);
    if (t != NULL)
        OPENSSL_free(t);
    return 0;
}

#endif /* !defined(ARGON2_NO_THREADS) */

static int fill_mem_blocks_st(KDF_ARGON2 *ctx)
{
    uint32_t r, s, l;

    for (r = 0; r < ctx->passes; ++r)
        for (s = 0; s < ARGON2_SYNC_POINTS; ++s)
            for (l = 0; l < ctx->lanes; ++l)
                fill_segment(ctx, r, l, s);
    return 1;
}

static ossl_inline int fill_memory_blocks(KDF_ARGON2 *ctx)
{
#if !defined(ARGON2_NO_THREADS)
    return ctx->threads == 1 ? fill_mem_blocks_st(ctx) : fill_mem_blocks_mt(ctx);
#else
    return ctx->threads == 1 ? fill_mem_blocks_st(ctx) : 0;
#endif
}

static void initial_hash(uint8_t *blockhash, KDF_ARGON2 *ctx)
{
    EVP_MD_CTX *mdctx;
    uint8_t value[sizeof(uint32_t)];
    unsigned int tmp;
    uint32_t args[7];

    if (ctx == NULL || blockhash == NULL)
        return;

    args[0] = ctx->lanes;
    args[1] = ctx->outlen;
    args[2] = ctx->m_cost;
    args[3] = ctx->t_cost;
    args[4] = ctx->version;
    args[5] = (uint32_t)ctx->type;
    args[6] = ctx->pwdlen;

    mdctx = EVP_MD_CTX_create();
    if (mdctx == NULL || EVP_DigestInit_ex(mdctx, ctx->md, NULL) != 1)
        goto fail;

    for (tmp = 0; tmp < sizeof(args) / sizeof(uint32_t); ++tmp) {
        store32((uint8_t *)&value, args[tmp]);
        if (EVP_DigestUpdate(mdctx, &value, sizeof(value)) != 1)
            goto fail;
    }

    if (ctx->pwd != NULL) {
        if (EVP_DigestUpdate(mdctx, ctx->pwd, ctx->pwdlen) != 1)
            goto fail;
        if (ctx->early_clean) {
            OPENSSL_cleanse(ctx->pwd, ctx->pwdlen);
            ctx->pwdlen = 0;
        }
    }

    store32((uint8_t *)&value, ctx->saltlen);

    if (EVP_DigestUpdate(mdctx, &value, sizeof(value)) != 1)
        goto fail;

    if (ctx->salt != NULL)
        if (EVP_DigestUpdate(mdctx, ctx->salt, ctx->saltlen) != 1)
            goto fail;

    store32((uint8_t *)&value, ctx->secretlen);
    if (EVP_DigestUpdate(mdctx, &value, sizeof(value)) != 1)
        goto fail;

    if (ctx->secret != NULL) {
        if (EVP_DigestUpdate(mdctx, ctx->secret, ctx->secretlen) != 1)
            goto fail;
        if (ctx->early_clean) {
            OPENSSL_cleanse(ctx->secret, ctx->secretlen);
            ctx->secretlen = 0;
        }
    }

    store32((uint8_t *)&value, ctx->adlen);
    if (EVP_DigestUpdate(mdctx, &value, sizeof(value)) != 1)
        goto fail;

    if (ctx->ad != NULL)
        if (EVP_DigestUpdate(mdctx, ctx->ad, ctx->adlen) != 1)
            goto fail;

    tmp = ARGON2_PREHASH_DIGEST_LENGTH;
    if (EVP_DigestFinal_ex(mdctx, blockhash, &tmp) != 1)
        goto fail;

fail:
    EVP_MD_CTX_destroy(mdctx);
}

static int initialize(KDF_ARGON2 *ctx)
{
    uint8_t blockhash[ARGON2_PREHASH_SEED_LENGTH];

    if (ctx == NULL)
        return 0;

    if (ctx->memory_blocks * sizeof(BLOCK) / sizeof(BLOCK) != ctx->memory_blocks)
        return 0;

    if (ctx->type != ARGON2_D)
        ctx->memory = OPENSSL_secure_zalloc(ctx->memory_blocks * sizeof(BLOCK));
    else
        ctx->memory = OPENSSL_zalloc(ctx->memory_blocks * sizeof(BLOCK));

    if (ctx->memory == NULL) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_MEMORY_SIZE,
            "cannot allocate required memory");
        return 0;
    }

    initial_hash(blockhash, ctx);
    OPENSSL_cleanse(blockhash + ARGON2_PREHASH_DIGEST_LENGTH,
        ARGON2_PREHASH_SEED_LENGTH - ARGON2_PREHASH_DIGEST_LENGTH);
    fill_first_blocks(blockhash, ctx);
    OPENSSL_cleanse(blockhash, ARGON2_PREHASH_SEED_LENGTH);

    return 1;
}

static void finalize(const KDF_ARGON2 *ctx, void *out)
{
    BLOCK blockhash;
    uint8_t blockhash_bytes[ARGON2_BLOCK_SIZE];
    uint32_t last_block_in_lane;
    uint32_t l;

    if (ctx == NULL)
        return;

    copy_block(&blockhash, ctx->memory + ctx->lane_length - 1);

    /* XOR the last blocks */
    for (l = 1; l < ctx->lanes; ++l) {
        last_block_in_lane = l * ctx->lane_length + (ctx->lane_length - 1);
        xor_block(&blockhash, ctx->memory + last_block_in_lane);
    }

    /* Hash the result */
    store_block(blockhash_bytes, &blockhash);
    blake2b_long(ctx->md, ctx->mac, out, ctx->outlen, blockhash_bytes,
        ARGON2_BLOCK_SIZE);
    OPENSSL_cleanse(blockhash.v, ARGON2_BLOCK_SIZE);
    OPENSSL_cleanse(blockhash_bytes, ARGON2_BLOCK_SIZE);

    if (ctx->type != ARGON2_D)
        OPENSSL_secure_clear_free(ctx->memory,
            ctx->memory_blocks * sizeof(BLOCK));
    else
        OPENSSL_clear_free(ctx->memory,
            ctx->memory_blocks * sizeof(BLOCK));
}

static int blake2b_mac(EVP_MAC *mac, void *out, size_t outlen, const void *in,
    size_t inlen, const void *key, size_t keylen)
{
    int ret = 0;
    size_t par_n = 0, out_written;
    EVP_MAC_CTX *ctx = NULL;
    OSSL_PARAM par[3];

    if ((ctx = EVP_MAC_CTX_new(mac)) == NULL)
        goto fail;

    par[par_n++] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY,
        (void *)key, keylen);
    par[par_n++] = OSSL_PARAM_construct_size_t(OSSL_MAC_PARAM_SIZE, &outlen);
    par[par_n++] = OSSL_PARAM_construct_end();

    ret = EVP_MAC_CTX_set_params(ctx, par) == 1
        && EVP_MAC_init(ctx, NULL, 0, NULL) == 1
        && EVP_MAC_update(ctx, in, inlen) == 1
        && EVP_MAC_final(ctx, out, (size_t *)&out_written, outlen) == 1;

fail:
    EVP_MAC_CTX_free(ctx);
    return ret;
}

static int blake2b_md(EVP_MD *md, void *out, size_t outlen, const void *in,
    size_t inlen)
{
    int ret = 0;
    EVP_MD_CTX *ctx = NULL;
    OSSL_PARAM par[2];

    if ((ctx = EVP_MD_CTX_create()) == NULL)
        return 0;

    par[0] = OSSL_PARAM_construct_size_t(OSSL_DIGEST_PARAM_SIZE, &outlen);
    par[1] = OSSL_PARAM_construct_end();

    ret = EVP_DigestInit_ex2(ctx, md, par) == 1
        && EVP_DigestUpdate(ctx, in, inlen) == 1
        && EVP_DigestFinal_ex(ctx, out, NULL) == 1;

    EVP_MD_CTX_free(ctx);
    return ret;
}

static int blake2b(EVP_MD *md, EVP_MAC *mac, void *out, size_t outlen,
    const void *in, size_t inlen, const void *key, size_t keylen)
{
    if (out == NULL || outlen == 0)
        return 0;

    if (key == NULL || keylen == 0)
        return blake2b_md(md, out, outlen, in, inlen);

    return blake2b_mac(mac, out, outlen, in, inlen, key, keylen);
}

static int blake2b_long(EVP_MD *md, EVP_MAC *mac, unsigned char *out,
    size_t outlen, const void *in, size_t inlen)
{
    int ret = 0;
    EVP_MD_CTX *ctx = NULL;
    uint32_t outlen_curr;
    uint8_t outbuf[BLAKE2B_OUTBYTES];
    uint8_t inbuf[BLAKE2B_OUTBYTES];
    uint8_t outlen_bytes[sizeof(uint32_t)] = { 0 };
    OSSL_PARAM par[2];
    size_t outlen_md;

    if (out == NULL || outlen == 0)
        return 0;

    /* Ensure little-endian byte order */
    store32(outlen_bytes, (uint32_t)outlen);

    if ((ctx = EVP_MD_CTX_create()) == NULL)
        return 0;

    outlen_md = (outlen <= BLAKE2B_OUTBYTES) ? outlen : BLAKE2B_OUTBYTES;
    par[0] = OSSL_PARAM_construct_size_t(OSSL_DIGEST_PARAM_SIZE, &outlen_md);
    par[1] = OSSL_PARAM_construct_end();

    ret = EVP_DigestInit_ex2(ctx, md, par) == 1
        && EVP_DigestUpdate(ctx, outlen_bytes, sizeof(outlen_bytes)) == 1
        && EVP_DigestUpdate(ctx, in, inlen) == 1
        && EVP_DigestFinal_ex(ctx, (outlen > BLAKE2B_OUTBYTES) ? outbuf : out,
               NULL)
            == 1;

    if (ret == 0)
        goto fail;

    if (outlen > BLAKE2B_OUTBYTES) {
        memcpy(out, outbuf, BLAKE2B_OUTBYTES / 2);
        out += BLAKE2B_OUTBYTES / 2;
        outlen_curr = (uint32_t)outlen - BLAKE2B_OUTBYTES / 2;

        while (outlen_curr > BLAKE2B_OUTBYTES) {
            memcpy(inbuf, outbuf, BLAKE2B_OUTBYTES);
            if (blake2b(md, mac, outbuf, BLAKE2B_OUTBYTES, inbuf,
                    BLAKE2B_OUTBYTES, NULL, 0)
                != 1)
                goto fail;
            memcpy(out, outbuf, BLAKE2B_OUTBYTES / 2);
            out += BLAKE2B_OUTBYTES / 2;
            outlen_curr -= BLAKE2B_OUTBYTES / 2;
        }

        memcpy(inbuf, outbuf, BLAKE2B_OUTBYTES);
        if (blake2b(md, mac, outbuf, outlen_curr, inbuf, BLAKE2B_OUTBYTES,
                NULL, 0)
            != 1)
            goto fail;
        memcpy(out, outbuf, outlen_curr);
    }
    ret = 1;

fail:
    EVP_MD_CTX_free(ctx);
    return ret;
}

static void kdf_argon2_init(KDF_ARGON2 *c, ARGON2_TYPE type)
{
    OSSL_LIB_CTX *libctx;

    libctx = c->libctx;
    memset(c, 0, sizeof(*c));

    c->libctx = libctx;
    c->outlen = ARGON2_DEFAULT_OUTLEN;
    c->t_cost = ARGON2_DEFAULT_T_COST;
    c->m_cost = ARGON2_DEFAULT_M_COST;
    c->lanes = ARGON2_DEFAULT_LANES;
    c->threads = ARGON2_DEFAULT_THREADS;
    c->version = ARGON2_DEFAULT_VERSION;
    c->type = type;
}

static void *kdf_argon2d_new(void *provctx)
{
    KDF_ARGON2 *ctx;

    if (!ossl_prov_is_running())
        return NULL;

    ctx = OPENSSL_zalloc(sizeof(*ctx));
    if (ctx == NULL) {
        ERR_raise(ERR_LIB_PROV, ERR_R_MALLOC_FAILURE);
        return NULL;
    }

    ctx->libctx = PROV_LIBCTX_OF(provctx);

    kdf_argon2_init(ctx, ARGON2_D);
    return ctx;
}

static void *kdf_argon2i_new(void *provctx)
{
    KDF_ARGON2 *ctx;

    if (!ossl_prov_is_running())
        return NULL;

    ctx = OPENSSL_zalloc(sizeof(*ctx));
    if (ctx == NULL) {
        ERR_raise(ERR_LIB_PROV, ERR_R_MALLOC_FAILURE);
        return NULL;
    }

    ctx->libctx = PROV_LIBCTX_OF(provctx);

    kdf_argon2_init(ctx, ARGON2_I);
    return ctx;
}

static void *kdf_argon2id_new(void *provctx)
{
    KDF_ARGON2 *ctx;

    if (!ossl_prov_is_running())
        return NULL;

    ctx = OPENSSL_zalloc(sizeof(*ctx));
    if (ctx == NULL) {
        ERR_raise(ERR_LIB_PROV, ERR_R_MALLOC_FAILURE);
        return NULL;
    }

    ctx->libctx = PROV_LIBCTX_OF(provctx);

    kdf_argon2_init(ctx, ARGON2_ID);
    return ctx;
}

static void kdf_argon2_free(void *vctx)
{
    KDF_ARGON2 *ctx = (KDF_ARGON2 *)vctx;

    if (ctx == NULL)
        return;

    if (ctx->pwd != NULL)
        OPENSSL_clear_free(ctx->pwd, ctx->pwdlen);

    if (ctx->salt != NULL)
        OPENSSL_clear_free(ctx->salt, ctx->saltlen);

    if (ctx->secret != NULL)
        OPENSSL_clear_free(ctx->secret, ctx->secretlen);

    if (ctx->ad != NULL)
        OPENSSL_clear_free(ctx->ad, ctx->adlen);

    EVP_MD_free(ctx->md);
    EVP_MAC_free(ctx->mac);

    OPENSSL_free(ctx->propq);

    memset(ctx, 0, sizeof(*ctx));

    OPENSSL_free(ctx);
}

static int kdf_argon2_derive(void *vctx, unsigned char *out, size_t outlen,
    const OSSL_PARAM params[])
{
    KDF_ARGON2 *ctx;
    uint32_t memory_blocks, segment_length;

    ctx = (KDF_ARGON2 *)vctx;

    if (!ossl_prov_is_running() || !kdf_argon2_set_ctx_params(vctx, params))
        return 0;

    if (ctx->mac == NULL)
        ctx->mac = EVP_MAC_fetch(ctx->libctx, "blake2bmac", ctx->propq);
    if (ctx->mac == NULL) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_MAC,
            "cannot fetch blake2bmac");
        return 0;
    }

    if (ctx->md == NULL)
        ctx->md = EVP_MD_fetch(ctx->libctx, "blake2b512", ctx->propq);
    if (ctx->md == NULL) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_MESSAGE_DIGEST,
            "cannot fetch blake2b512");
        return 0;
    }

    if (ctx->salt == NULL || ctx->saltlen == 0) {
        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_SALT);
        return 0;
    }

    if (outlen != ctx->outlen) {
        if (OSSL_PARAM_locate((OSSL_PARAM *)params, "size") != NULL) {
            ERR_raise(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL);
            return 0;
        }
        if (!kdf_argon2_ctx_set_out_length(ctx, (uint32_t)outlen))
            return 0;
    }

    switch (ctx->type) {
    case ARGON2_D:
    case ARGON2_I:
    case ARGON2_ID:
        break;
    default:
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_MODE, "invalid Argon2 type");
        return 0;
    }

    if (ctx->threads > 1) {
#ifdef ARGON2_NO_THREADS
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_THREAD_POOL_SIZE,
            "requested %u threads, single-threaded mode supported only",
            ctx->threads);
        return 0;
#else
        if (ctx->threads > ossl_get_avail_threads(ctx->libctx)) {
            ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_THREAD_POOL_SIZE,
                "requested %u threads, available: %u",
                ctx->threads, ossl_get_avail_threads(ctx->libctx));
            return 0;
        }
#endif
        if (ctx->threads > ctx->lanes) {
            ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_THREAD_POOL_SIZE,
                "requested more threads (%u) than lanes (%u)",
                ctx->threads, ctx->lanes);
            return 0;
        }
    }

    if (ctx->m_cost < 8 * ctx->lanes) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_MEMORY_SIZE,
            "m_cost must be greater or equal than 8 times the number of lanes");
        return 0;
    }

    memory_blocks = ctx->m_cost;
    if (memory_blocks < 2 * ARGON2_SYNC_POINTS * ctx->lanes)
        memory_blocks = 2 * ARGON2_SYNC_POINTS * ctx->lanes;

    /* Ensure that all segments have equal length */
    segment_length = memory_blocks / (ctx->lanes * ARGON2_SYNC_POINTS);
    memory_blocks = segment_length * (ctx->lanes * ARGON2_SYNC_POINTS);

    ctx->memory = NULL;
    ctx->memory_blocks = memory_blocks;
    ctx->segment_length = segment_length;
    ctx->passes = ctx->t_cost;
    ctx->lane_length = segment_length * ARGON2_SYNC_POINTS;

    if (initialize(ctx) != 1)
        return 0;

    if (fill_memory_blocks(ctx) != 1)
        return 0;

    finalize(ctx, out);

    return 1;
}

static void kdf_argon2_reset(void *vctx)
{
    OSSL_LIB_CTX *libctx;
    KDF_ARGON2 *ctx;
    ARGON2_TYPE type;

    ctx = (KDF_ARGON2 *)vctx;
    type = ctx->type;
    libctx = ctx->libctx;

    EVP_MD_free(ctx->md);
    EVP_MAC_free(ctx->mac);

    OPENSSL_free(ctx->propq);

    if (ctx->pwd != NULL)
        OPENSSL_clear_free(ctx->pwd, ctx->pwdlen);

    if (ctx->salt != NULL)
        OPENSSL_clear_free(ctx->salt, ctx->saltlen);

    if (ctx->secret != NULL)
        OPENSSL_clear_free(ctx->secret, ctx->secretlen);

    if (ctx->ad != NULL)
        OPENSSL_clear_free(ctx->ad, ctx->adlen);

    memset(ctx, 0, sizeof(*ctx));
    ctx->libctx = libctx;
    kdf_argon2_init(ctx, type);
}

static int kdf_argon2_ctx_set_threads(KDF_ARGON2 *ctx, uint32_t threads)
{
    if (threads < ARGON2_MIN_THREADS) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_THREAD_POOL_SIZE,
            "min threads: %u", ARGON2_MIN_THREADS);
        return 0;
    }

    if (threads > ARGON2_MAX_THREADS) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_THREAD_POOL_SIZE,
            "max threads: %u", ARGON2_MAX_THREADS);
        return 0;
    }

    ctx->threads = threads;
    return 1;
}

static int kdf_argon2_ctx_set_lanes(KDF_ARGON2 *ctx, uint32_t lanes)
{
    if (lanes > ARGON2_MAX_LANES) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER,
            "max lanes: %u", ARGON2_MAX_LANES);
        return 0;
    }

    if (lanes < ARGON2_MIN_LANES) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER,
            "min lanes: %u", ARGON2_MIN_LANES);
        return 0;
    }

    ctx->lanes = lanes;
    return 1;
}

static int kdf_argon2_ctx_set_t_cost(KDF_ARGON2 *ctx, uint32_t t_cost)
{
    /* ARGON2_MAX_MEMORY == max m_cost value, so skip check  */

    if (t_cost < ARGON2_MIN_TIME) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_ITERATION_COUNT,
            "min: %u", ARGON2_MIN_TIME);
        return 0;
    }

    ctx->t_cost = t_cost;
    return 1;
}

static int kdf_argon2_ctx_set_m_cost(KDF_ARGON2 *ctx, uint32_t m_cost)
{
    /* ARGON2_MAX_MEMORY == max m_cost value, so skip check */

    if (m_cost < ARGON2_MIN_MEMORY) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_MEMORY_SIZE, "min: %u",
            ARGON2_MIN_MEMORY);
        return 0;
    }

    ctx->m_cost = m_cost;
    return 1;
}

static int kdf_argon2_ctx_set_out_length(KDF_ARGON2 *ctx, uint32_t outlen)
{
    /*
     * ARGON2_MAX_OUT_LENGTH == max outlen value, so upper bounds checks
     * are always satisfied; to suppress compiler if statement tautology
     * warnings, these checks are skipped.
     */

    if (outlen < ARGON2_MIN_OUT_LENGTH) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_OUTPUT_LENGTH, "min: %u",
            ARGON2_MIN_OUT_LENGTH);
        return 0;
    }

    ctx->outlen = outlen;
    return 1;
}

static int kdf_argon2_ctx_set_secret(KDF_ARGON2 *ctx, const OSSL_PARAM *p)
{
    size_t buflen;

    if (p->data == NULL)
        return 0;

    if (ctx->secret != NULL) {
        OPENSSL_clear_free(ctx->secret, ctx->secretlen);
        ctx->secret = NULL;
        ctx->secretlen = 0U;
    }

    if (!OSSL_PARAM_get_octet_string(p, (void **)&ctx->secret, 0, &buflen))
        return 0;

    if (buflen > ARGON2_MAX_SECRET) {
        OPENSSL_free(ctx->secret);
        ctx->secret = NULL;
        ctx->secretlen = 0U;
        return 0;
    }

    ctx->secretlen = (uint32_t)buflen;
    return 1;
}

static int kdf_argon2_ctx_set_pwd(KDF_ARGON2 *ctx, const OSSL_PARAM *p)
{
    size_t buflen;

    if (p->data == NULL)
        return 0;

    if (ctx->pwd != NULL) {
        OPENSSL_clear_free(ctx->pwd, ctx->pwdlen);
        ctx->pwd = NULL;
        ctx->pwdlen = 0U;
    }

    if (!OSSL_PARAM_get_octet_string(p, (void **)&ctx->pwd, 0, &buflen))
        return 0;

    if (buflen > ARGON2_MAX_PWD_LENGTH) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_SALT_LENGTH, "max: %u",
            ARGON2_MAX_PWD_LENGTH);
        goto fail;
    }

    ctx->pwdlen = (uint32_t)buflen;
    return 1;

fail:
    OPENSSL_free(ctx->pwd);
    ctx->pwd = NULL;
    ctx->pwdlen = 0U;
    return 0;
}

static int kdf_argon2_ctx_set_salt(KDF_ARGON2 *ctx, const OSSL_PARAM *p)
{
    size_t buflen;

    if (p->data == NULL)
        return 0;

    if (ctx->salt != NULL) {
        OPENSSL_clear_free(ctx->salt, ctx->saltlen);
        ctx->salt = NULL;
        ctx->saltlen = 0U;
    }

    if (!OSSL_PARAM_get_octet_string(p, (void **)&ctx->salt, 0, &buflen))
        return 0;

    if (buflen < ARGON2_MIN_SALT_LENGTH) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_SALT_LENGTH, "min: %u",
            ARGON2_MIN_SALT_LENGTH);
        goto fail;
    }

    if (buflen > ARGON2_MAX_SALT_LENGTH) {
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_SALT_LENGTH, "max: %u",
            ARGON2_MAX_SALT_LENGTH);
        goto fail;
    }

    ctx->saltlen = (uint32_t)buflen;
    return 1;

fail:
    OPENSSL_free(ctx->salt);
    ctx->salt = NULL;
    ctx->saltlen = 0U;
    return 0;
}

static int kdf_argon2_ctx_set_ad(KDF_ARGON2 *ctx, const OSSL_PARAM *p)
{
    size_t buflen;

    if (p->data == NULL)
        return 0;

    if (ctx->ad != NULL) {
        OPENSSL_clear_free(ctx->ad, ctx->adlen);
        ctx->ad = NULL;
        ctx->adlen = 0U;
    }

    if (!OSSL_PARAM_get_octet_string(p, (void **)&ctx->ad, 0, &buflen))
        return 0;

    if (buflen > ARGON2_MAX_AD_LENGTH) {
        OPENSSL_free(ctx->ad);
        ctx->ad = NULL;
        ctx->adlen = 0U;
        return 0;
    }

    ctx->adlen = (uint32_t)buflen;
    return 1;
}

static void kdf_argon2_ctx_set_flag_early_clean(KDF_ARGON2 *ctx, uint32_t f)
{
    ctx->early_clean = !!(f);
}

static int kdf_argon2_ctx_set_version(KDF_ARGON2 *ctx, uint32_t version)
{
    switch (version) {
    case ARGON2_VERSION_10:
    case ARGON2_VERSION_13:
        ctx->version = version;
        return 1;
    default:
        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_MODE,
            "invalid Argon2 version");
        return 0;
    }
}

static int set_property_query(KDF_ARGON2 *ctx, const char *propq)
{
    OPENSSL_free(ctx->propq);
    ctx->propq = NULL;
    if (propq != NULL) {
        ctx->propq = OPENSSL_strdup(propq);
        if (ctx->propq == NULL)
            return 0;
    }
    EVP_MD_free(ctx->md);
    ctx->md = NULL;
    EVP_MAC_free(ctx->mac);
    ctx->mac = NULL;
    return 1;
}

static int kdf_argon2_set_ctx_params(void *vctx, const OSSL_PARAM params[])
{
    const OSSL_PARAM *p;
    KDF_ARGON2 *ctx;
    uint32_t u32_value;

    if (ossl_param_is_empty(params))
        return 1;

    ctx = (KDF_ARGON2 *)vctx;
    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_PASSWORD)) != NULL)
        if (!kdf_argon2_ctx_set_pwd(ctx, p))
            return 0;

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_SALT)) != NULL)
        if (!kdf_argon2_ctx_set_salt(ctx, p))
            return 0;

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_SECRET)) != NULL)
        if (!kdf_argon2_ctx_set_secret(ctx, p))
            return 0;

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_ARGON2_AD)) != NULL)
        if (!kdf_argon2_ctx_set_ad(ctx, p))
            return 0;

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_SIZE)) != NULL) {
        if (!OSSL_PARAM_get_uint32(p, &u32_value))
            return 0;
        if (!kdf_argon2_ctx_set_out_length(ctx, u32_value))
            return 0;
    }

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_ITER)) != NULL) {
        if (!OSSL_PARAM_get_uint32(p, &u32_value))
            return 0;
        if (!kdf_argon2_ctx_set_t_cost(ctx, u32_value))
            return 0;
    }

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_THREADS)) != NULL) {
        if (!OSSL_PARAM_get_uint32(p, &u32_value))
            return 0;
        if (!kdf_argon2_ctx_set_threads(ctx, u32_value))
            return 0;
    }

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_ARGON2_LANES)) != NULL) {
        if (!OSSL_PARAM_get_uint32(p, &u32_value))
            return 0;
        if (!kdf_argon2_ctx_set_lanes(ctx, u32_value))
            return 0;
    }

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_ARGON2_MEMCOST)) != NULL) {
        if (!OSSL_PARAM_get_uint32(p, &u32_value))
            return 0;
        if (!kdf_argon2_ctx_set_m_cost(ctx, u32_value))
            return 0;
    }

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_EARLY_CLEAN)) != NULL) {
        if (!OSSL_PARAM_get_uint32(p, &u32_value))
            return 0;
        kdf_argon2_ctx_set_flag_early_clean(ctx, u32_value);
    }

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_ARGON2_VERSION)) != NULL) {
        if (!OSSL_PARAM_get_uint32(p, &u32_value))
            return 0;
        if (!kdf_argon2_ctx_set_version(ctx, u32_value))
            return 0;
    }

    if ((p = OSSL_PARAM_locate_const(params, OSSL_KDF_PARAM_PROPERTIES)) != NULL) {
        if (p->data_type != OSSL_PARAM_UTF8_STRING
            || !set_property_query(ctx, p->data))
            return 0;
    }

    return 1;
}

static const OSSL_PARAM *kdf_argon2_settable_ctx_params(ossl_unused void *ctx,
    ossl_unused void *p_ctx)
{
    static const OSSL_PARAM known_settable_ctx_params[] = {
        OSSL_PARAM_octet_string(OSSL_KDF_PARAM_PASSWORD, NULL, 0),
        OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, NULL, 0),
        OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SECRET, NULL, 0),
        OSSL_PARAM_octet_string(OSSL_KDF_PARAM_ARGON2_AD, NULL, 0),
        OSSL_PARAM_uint32(OSSL_KDF_PARAM_SIZE, NULL),
        OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, NULL),
        OSSL_PARAM_uint32(OSSL_KDF_PARAM_THREADS, NULL),
        OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_LANES, NULL),
        OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, NULL),
        OSSL_PARAM_uint32(OSSL_KDF_PARAM_EARLY_CLEAN, NULL),
        OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_VERSION, NULL),
        OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_PROPERTIES, NULL, 0),
        OSSL_PARAM_END
    };

    return known_settable_ctx_params;
}

static int kdf_argon2_get_ctx_params(void *vctx, OSSL_PARAM params[])
{
    OSSL_PARAM *p;

    (void)vctx;
    if ((p = OSSL_PARAM_locate(params, OSSL_KDF_PARAM_SIZE)) != NULL)
        return OSSL_PARAM_set_size_t(p, SIZE_MAX);

    return -2;
}

static const OSSL_PARAM *kdf_argon2_gettable_ctx_params(ossl_unused void *ctx,
    ossl_unused void *p_ctx)
{
    static const OSSL_PARAM known_gettable_ctx_params[] = {
        OSSL_PARAM_size_t(OSSL_KDF_PARAM_SIZE, NULL),
        OSSL_PARAM_END
    };

    return known_gettable_ctx_params;
}

const OSSL_DISPATCH ossl_kdf_argon2i_functions[] = {
    { OSSL_FUNC_KDF_NEWCTX, (void (*)(void))kdf_argon2i_new },
    { OSSL_FUNC_KDF_FREECTX, (void (*)(void))kdf_argon2_free },
    { OSSL_FUNC_KDF_RESET, (void (*)(void))kdf_argon2_reset },
    { OSSL_FUNC_KDF_DERIVE, (void (*)(void))kdf_argon2_derive },
    { OSSL_FUNC_KDF_SETTABLE_CTX_PARAMS,
        (void (*)(void))kdf_argon2_settable_ctx_params },
    { OSSL_FUNC_KDF_SET_CTX_PARAMS, (void (*)(void))kdf_argon2_set_ctx_params },
    { OSSL_FUNC_KDF_GETTABLE_CTX_PARAMS,
        (void (*)(void))kdf_argon2_gettable_ctx_params },
    { OSSL_FUNC_KDF_GET_CTX_PARAMS, (void (*)(void))kdf_argon2_get_ctx_params },
    OSSL_DISPATCH_END
};

const OSSL_DISPATCH ossl_kdf_argon2d_functions[] = {
    { OSSL_FUNC_KDF_NEWCTX, (void (*)(void))kdf_argon2d_new },
    { OSSL_FUNC_KDF_FREECTX, (void (*)(void))kdf_argon2_free },
    { OSSL_FUNC_KDF_RESET, (void (*)(void))kdf_argon2_reset },
    { OSSL_FUNC_KDF_DERIVE, (void (*)(void))kdf_argon2_derive },
    { OSSL_FUNC_KDF_SETTABLE_CTX_PARAMS,
        (void (*)(void))kdf_argon2_settable_ctx_params },
    { OSSL_FUNC_KDF_SET_CTX_PARAMS, (void (*)(void))kdf_argon2_set_ctx_params },
    { OSSL_FUNC_KDF_GETTABLE_CTX_PARAMS,
        (void (*)(void))kdf_argon2_gettable_ctx_params },
    { OSSL_FUNC_KDF_GET_CTX_PARAMS, (void (*)(void))kdf_argon2_get_ctx_params },
    OSSL_DISPATCH_END
};

const OSSL_DISPATCH ossl_kdf_argon2id_functions[] = {
    { OSSL_FUNC_KDF_NEWCTX, (void (*)(void))kdf_argon2id_new },
    { OSSL_FUNC_KDF_FREECTX, (void (*)(void))kdf_argon2_free },
    { OSSL_FUNC_KDF_RESET, (void (*)(void))kdf_argon2_reset },
    { OSSL_FUNC_KDF_DERIVE, (void (*)(void))kdf_argon2_derive },
    { OSSL_FUNC_KDF_SETTABLE_CTX_PARAMS,
        (void (*)(void))kdf_argon2_settable_ctx_params },
    { OSSL_FUNC_KDF_SET_CTX_PARAMS, (void (*)(void))kdf_argon2_set_ctx_params },
    { OSSL_FUNC_KDF_GETTABLE_CTX_PARAMS,
        (void (*)(void))kdf_argon2_gettable_ctx_params },
    { OSSL_FUNC_KDF_GET_CTX_PARAMS, (void (*)(void))kdf_argon2_get_ctx_params },
    OSSL_DISPATCH_END
};

#endif
