/*
 * Copyright 2024-2025 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
 */

#include <openssl/core_names.h>
#include <openssl/evp.h>
#include <openssl/param_build.h>
#include <openssl/rand.h>
#include <openssl/pem.h>
#include "crypto/slh_dsa.h"
#include "internal/nelem.h"
#include "testutil.h"
#include "slh_dsa.inc"

typedef enum OPTION_choice {
    OPT_ERR = -1,
    OPT_EOF = 0,
    OPT_CONFIG_FILE,
    OPT_TEST_ENUM
} OPTION_CHOICE;

static OSSL_LIB_CTX *lib_ctx = NULL;
static OSSL_PROVIDER *null_prov = NULL;
static OSSL_PROVIDER *lib_prov = NULL;

static EVP_PKEY *slh_dsa_key_from_data(const char *alg,
    const unsigned char *data, size_t datalen,
    int public)
{
    int ret;
    EVP_PKEY_CTX *ctx = NULL;
    EVP_PKEY *key = NULL;
    OSSL_PARAM params[2];
    const char *keytype = public ? OSSL_PKEY_PARAM_PUB_KEY : OSSL_PKEY_PARAM_PRIV_KEY;
    int selection = public ? EVP_PKEY_PUBLIC_KEY : EVP_PKEY_KEYPAIR;

    params[0] = OSSL_PARAM_construct_octet_string(keytype, (uint8_t *)data, datalen);
    params[1] = OSSL_PARAM_construct_end();
    ret = TEST_ptr(ctx = EVP_PKEY_CTX_new_from_name(lib_ctx, alg, NULL))
        && TEST_int_eq(EVP_PKEY_fromdata_init(ctx), 1)
        && (EVP_PKEY_fromdata(ctx, &key, selection, params) == 1);
    if (ret == 0) {
        EVP_PKEY_free(key);
        key = NULL;
    }
    EVP_PKEY_CTX_free(ctx);
    return key;
}

static int slh_dsa_create_keypair(EVP_PKEY **pkey, const char *name,
    const uint8_t *priv, size_t priv_len,
    const uint8_t *pub, size_t pub_len)
{
    int ret = 0;
    EVP_PKEY_CTX *ctx = NULL;
    OSSL_PARAM_BLD *bld = NULL;
    OSSL_PARAM *params = NULL;
    const char *pub_name = OSSL_PKEY_PARAM_PUB_KEY;

    if (!TEST_ptr(bld = OSSL_PARAM_BLD_new())
        || !TEST_true(OSSL_PARAM_BLD_push_octet_string(bld,
                          OSSL_PKEY_PARAM_PRIV_KEY,
                          priv, priv_len)
            > 0)
        || !TEST_true(OSSL_PARAM_BLD_push_octet_string(bld,
                          pub_name,
                          pub, pub_len)
            > 0)
        || !TEST_ptr(params = OSSL_PARAM_BLD_to_param(bld))
        || !TEST_ptr(ctx = EVP_PKEY_CTX_new_from_name(lib_ctx, name, NULL))
        || !TEST_int_eq(EVP_PKEY_fromdata_init(ctx), 1)
        || !TEST_int_eq(EVP_PKEY_fromdata(ctx, pkey, EVP_PKEY_KEYPAIR,
                            params),
            1))
        goto err;

    ret = 1;
err:
    OSSL_PARAM_free(params);
    OSSL_PARAM_BLD_free(bld);
    EVP_PKEY_CTX_free(ctx);
    return ret;
}

static int slh_dsa_bad_pub_len_test(void)
{
    int ret = 0;
    SLH_DSA_SIG_TEST_DATA *td = &slh_dsa_sig_testdata[0];
    EVP_PKEY *pkey = NULL;
    size_t pub_len = 0;
    unsigned char pubdata[64 + 1];

    if (!TEST_size_t_le(td->pub_len, sizeof(pubdata)))
        goto end;

    OPENSSL_cleanse(pubdata, sizeof(pubdata));
    memcpy(pubdata, td->pub, td->pub_len);

    if (!TEST_ptr_null(pkey = slh_dsa_key_from_data(td->alg, pubdata,
                           td->pub_len - 1, 1))
        || !TEST_ptr_null(pkey = slh_dsa_key_from_data(td->alg, pubdata,
                              td->pub_len + 1, 1)))
        goto end;

    ret = 1;
end:
    if (ret == 0)
        TEST_note("Incorrectly accepted public key of length %u (expected %u)",
            (unsigned)pub_len, (unsigned)td->pub_len);
    EVP_PKEY_free(pkey);
    return ret == 1;
}

static int slh_dsa_key_eq_test(void)
{
    int ret = 0;
    size_t i;
    EVP_PKEY *key[4] = { NULL, NULL, NULL, NULL };
    SLH_DSA_SIG_TEST_DATA *td1 = &slh_dsa_sig_testdata[0];
    SLH_DSA_SIG_TEST_DATA *td2 = &slh_dsa_sig_testdata[1];
#ifndef OPENSSL_NO_EC
    EVP_PKEY *eckey = NULL;
#endif

    if (!TEST_ptr(key[0] = slh_dsa_key_from_data(td1->alg, td1->pub, td1->pub_len, 1))
        || !TEST_ptr(key[1] = slh_dsa_key_from_data(td1->alg, td1->pub, td1->pub_len, 1))
        || !TEST_ptr(key[2] = slh_dsa_key_from_data(td2->alg, td2->pub, td2->pub_len, 1))
        || !TEST_ptr(key[3] = EVP_PKEY_dup(key[0])))
        goto end;

    if (!TEST_int_eq(EVP_PKEY_eq(key[0], key[1]), 1)
        || !TEST_int_ne(EVP_PKEY_eq(key[0], key[2]), 1)
        || !TEST_int_eq(EVP_PKEY_eq(key[0], key[3]), 1))
        goto end;

#ifndef OPENSSL_NO_EC
    if (!TEST_ptr(eckey = EVP_PKEY_Q_keygen(lib_ctx, NULL, "EC", "P-256")))
        goto end;
    ret = TEST_int_ne(EVP_PKEY_eq(key[0], eckey), 1);
    EVP_PKEY_free(eckey);
#else
    ret = 1;
#endif
end:
    for (i = 0; i < OSSL_NELEM(key); ++i)
        EVP_PKEY_free(key[i]);
    return ret;
}

static int slh_dsa_key_validate_test(void)
{
    int ret = 0;
    SLH_DSA_SIG_TEST_DATA *td = &slh_dsa_sig_testdata[0];
    EVP_PKEY_CTX *vctx = NULL;
    EVP_PKEY *key = NULL;

    if (!TEST_ptr(key = slh_dsa_key_from_data(td->alg, td->pub, td->pub_len, 1)))
        return 0;
    if (!TEST_ptr(vctx = EVP_PKEY_CTX_new_from_pkey(lib_ctx, key, NULL)))
        goto end;
    if (!TEST_int_eq(EVP_PKEY_public_check(vctx), 1))
        goto end;
    if (!TEST_int_eq(EVP_PKEY_private_check(vctx), 0))
        goto end;
    if (!TEST_int_eq(EVP_PKEY_pairwise_check(vctx), 0))
        goto end;
    ret = 1;
end:
    EVP_PKEY_CTX_free(vctx);
    EVP_PKEY_free(key);
    return ret;
}

static int slh_dsa_key_validate_failure_test(void)
{
    int ret = 0;
    EVP_PKEY_CTX *vctx = NULL;
    EVP_PKEY *key = NULL;

    /*
     * Loading 128s private key data into a 128f algorithm will have an incorrect
     * public key.
     */
    key = slh_dsa_key_from_data("SLH-DSA-SHA2-128f",
        slh_dsa_sha2_128s_0_keygen_priv,
        sizeof(slh_dsa_sha2_128s_0_keygen_priv), 0);
    if (!TEST_ptr(key))
        goto end;
    if (!TEST_ptr(vctx = EVP_PKEY_CTX_new_from_pkey(lib_ctx, key, NULL)))
        goto end;
    if (!TEST_int_eq(EVP_PKEY_pairwise_check(vctx), 0))
        goto end;
    ret = 1;
end:
    EVP_PKEY_CTX_free(vctx);
    EVP_PKEY_free(key);
    return ret;
}

/*
 * Rather than having to store the full signature into a file, we just do a
 * verify using the output of a sign. The sign test already does a Known answer
 * test (KAT) using the digest of the signature, so this should be sufficient to
 * run as a KAT for the verify.
 */
static int do_slh_dsa_verify(const SLH_DSA_SIG_TEST_DATA *td,
    uint8_t *sig, size_t sig_len)
{
    int ret = 0;
    EVP_PKEY_CTX *vctx = NULL;
    EVP_PKEY *key = NULL;
    EVP_SIGNATURE *sig_alg = NULL;
    OSSL_PARAM params[2], *p = params;
    int encode = 0;

    *p++ = OSSL_PARAM_construct_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode);
    *p = OSSL_PARAM_construct_end();

    if (!TEST_ptr(key = slh_dsa_key_from_data(td->alg, td->pub, td->pub_len, 1)))
        return 0;
    if (!TEST_ptr(vctx = EVP_PKEY_CTX_new_from_pkey(lib_ctx, key, NULL)))
        goto err;
    if (!TEST_ptr(sig_alg = EVP_SIGNATURE_fetch(lib_ctx, td->alg, NULL)))
        goto err;
    if (!TEST_int_eq(EVP_PKEY_verify_message_init(vctx, sig_alg, params), 1)
        || !TEST_int_eq(EVP_PKEY_verify(vctx, sig, sig_len,
                            td->msg, td->msg_len),
            1))
        goto err;
    ret = 1;
err:
    EVP_SIGNATURE_free(sig_alg);
    EVP_PKEY_free(key);
    EVP_PKEY_CTX_free(vctx);
    return ret;
}

static int slh_dsa_sign_verify_test(int tst_id)
{
    int ret = 0;
    SLH_DSA_SIG_TEST_DATA *td = &slh_dsa_sig_testdata[tst_id];
    EVP_PKEY_CTX *sctx = NULL;
    EVP_PKEY *pkey = NULL;
    EVP_SIGNATURE *sig_alg = NULL;
    OSSL_PARAM params[4], *p = params;
    uint8_t *psig = NULL;
    size_t psig_len = 0, sig_len2 = 0;
    uint8_t digest[32];
    size_t digest_len = sizeof(digest);
    int encode = 0, deterministic = 1;

    *p++ = OSSL_PARAM_construct_int(OSSL_SIGNATURE_PARAM_DETERMINISTIC, &deterministic);
    *p++ = OSSL_PARAM_construct_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode);
    if (td->add_random != NULL)
        *p++ = OSSL_PARAM_construct_octet_string(OSSL_SIGNATURE_PARAM_TEST_ENTROPY,
            (char *)td->add_random,
            td->add_random_len);
    *p = OSSL_PARAM_construct_end();

    /*
     * This just uses from data here, but keygen also works.
     * The keygen path is tested via slh_dsa_keygen_test
     */
    if (!slh_dsa_create_keypair(&pkey, td->alg, td->priv, td->priv_len,
            td->pub, td->pub_len))
        goto err;

    if (!TEST_ptr(sctx = EVP_PKEY_CTX_new_from_pkey(lib_ctx, pkey, NULL)))
        goto err;
    if (!TEST_ptr(sig_alg = EVP_SIGNATURE_fetch(lib_ctx, td->alg, NULL)))
        goto err;
    if (!TEST_int_eq(EVP_PKEY_sign_message_init(sctx, sig_alg, params), 1)
        || !TEST_int_eq(EVP_PKEY_sign(sctx, NULL, &psig_len,
                            td->msg, td->msg_len),
            1)
        || !TEST_true(EVP_PKEY_get_size_t_param(pkey, OSSL_PKEY_PARAM_MAX_SIZE,
            &sig_len2))
        || !TEST_size_t_eq(sig_len2, psig_len)
        || !TEST_ptr(psig = OPENSSL_zalloc(psig_len))
        || !TEST_int_eq(EVP_PKEY_sign(sctx, psig, &psig_len,
                            td->msg, td->msg_len),
            1))
        goto err;
    if (!TEST_int_eq(EVP_Q_digest(lib_ctx, "SHA256", NULL, psig, psig_len,
                         digest, &digest_len),
            1))
        goto err;
    if (!TEST_mem_eq(digest, digest_len, td->sig_digest, td->sig_digest_len))
        goto err;
    if (!do_slh_dsa_verify(td, psig, psig_len))
        goto err;
    ret = 1;
err:
    EVP_SIGNATURE_free(sig_alg);
    EVP_PKEY_free(pkey);
    EVP_PKEY_CTX_free(sctx);
    OPENSSL_free(psig);
    return ret;
}

static EVP_PKEY *do_gen_key(const char *alg,
    const uint8_t *seed, size_t seed_len)
{
    EVP_PKEY *pkey = NULL;
    EVP_PKEY_CTX *ctx = NULL;
    OSSL_PARAM params[2], *p = params;

    if (seed_len != 0)
        *p++ = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_SLH_DSA_SEED,
            (char *)seed, seed_len);
    *p = OSSL_PARAM_construct_end();

    if (!TEST_ptr(ctx = EVP_PKEY_CTX_new_from_name(lib_ctx, alg, NULL))
        || !TEST_int_eq(EVP_PKEY_keygen_init(ctx), 1)
        || !TEST_int_eq(EVP_PKEY_CTX_set_params(ctx, params), 1)
        || !TEST_int_eq(EVP_PKEY_generate(ctx, &pkey), 1))
        pkey = NULL;

    EVP_PKEY_CTX_free(ctx);
    return pkey;
}

static int slh_dsa_keygen_test(int tst_id)
{
    int ret = 0;
    const SLH_DSA_KEYGEN_TEST_DATA *tst = &slh_dsa_keygen_testdata[tst_id];
    EVP_PKEY *pkey = NULL;
    uint8_t priv[64 * 2], pub[32 * 2];
    size_t priv_len, pub_len;
    size_t key_len = tst->priv_len;
    size_t n = key_len / 4;
    int bits = 0, sec_bits = 0, sig_len = 0;

    if (!TEST_ptr(pkey = do_gen_key(tst->name, tst->priv, key_len - n)))
        goto err;

    if (!TEST_true(EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY,
            priv, sizeof(priv), &priv_len)))
        goto err;
    if (!TEST_true(EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY,
            pub, sizeof(pub), &pub_len)))
        goto err;
    if (!TEST_true(EVP_PKEY_get_int_param(pkey, OSSL_PKEY_PARAM_BITS, &bits))
        || !TEST_size_t_eq((size_t)bits, 8 * 2 * n)
        || !TEST_true(EVP_PKEY_get_int_param(pkey, OSSL_PKEY_PARAM_SECURITY_BITS,
            &sec_bits))
        || !TEST_size_t_eq((size_t)sec_bits, 8 * n)
        || !TEST_true(EVP_PKEY_get_int_param(pkey, OSSL_PKEY_PARAM_MAX_SIZE,
            &sig_len))
        || !TEST_int_ge(sig_len, 7856)
        || !TEST_int_le(sig_len, 49856))
        goto err;

    if (!TEST_size_t_eq(priv_len, key_len)
        || !TEST_size_t_eq(pub_len, key_len / 2))
        goto err;
    if (!TEST_mem_eq(pub, pub_len, tst->priv + 2 * n, 2 * n))
        goto err;
    ret = 1;
err:
    EVP_PKEY_free(pkey);
    return ret;
}

static int slh_dsa_usage_test(void)
{
    int ret = 0;
    EVP_CIPHER *cipher = NULL; /* Used to encrypt the private key */
    char *pass = "Password";
    BIO *pub_bio = NULL, *priv_bio = NULL;
    EVP_PKEY_CTX *gctx = NULL, *sctx = NULL, *vctx = NULL;
    EVP_PKEY *gkey = NULL, *pub = NULL, *priv = NULL;
    EVP_SIGNATURE *sig_alg = NULL;
    uint8_t *sig = NULL;
    size_t sig_len = 0;
    uint8_t msg[] = "Hello World";
    size_t msg_len = sizeof(msg) - 1;

    /* Generate a key */
    if (!TEST_ptr(gctx = EVP_PKEY_CTX_new_from_name(lib_ctx, "SLH-DSA-SHA2-128s", NULL))
        || !TEST_int_eq(EVP_PKEY_keygen_init(gctx), 1)
        || !TEST_int_eq(EVP_PKEY_keygen(gctx, &gkey), 1))
        goto err;

    /* Save it to a BIO - it uses a mem bio for testing */
    if (!TEST_ptr(pub_bio = BIO_new(BIO_s_mem()))
        || !TEST_ptr(priv_bio = BIO_new(BIO_s_mem()))
        || !TEST_ptr(cipher = EVP_CIPHER_fetch(lib_ctx, "AES-256-CBC", NULL))
        || !TEST_true(PEM_write_bio_PUBKEY_ex(pub_bio, gkey, lib_ctx, NULL))
        || !TEST_true(PEM_write_bio_PrivateKey_ex(priv_bio, gkey, cipher,
            NULL, 0, NULL, (void *)pass,
            lib_ctx, NULL)))
        goto err;

    /* Read the private key and add to a signing ctx */
    if (!TEST_ptr(PEM_read_bio_PrivateKey_ex(priv_bio, &priv, NULL, pass, lib_ctx, NULL))
        || !TEST_ptr(sctx = EVP_PKEY_CTX_new_from_pkey(lib_ctx, priv, NULL))
        || !TEST_ptr(sig_alg = EVP_SIGNATURE_fetch(lib_ctx, "SLH-DSA-SHA2-128s", NULL))
        || !TEST_int_eq(EVP_PKEY_sign_message_init(sctx, sig_alg, NULL), 1))
        goto err;
    /* Determine the size of the signature & allocate space */
    if (!TEST_int_eq(EVP_PKEY_sign(sctx, NULL, &sig_len, msg, msg_len), 1)
        || !TEST_ptr(sig = OPENSSL_malloc(sig_len))
        || !TEST_int_eq(EVP_PKEY_sign(sctx, sig, &sig_len, msg, msg_len), 1))
        goto err;
    if (!TEST_true(EVP_PKEY_pairwise_check(sctx))
        || !TEST_true(EVP_PKEY_public_check(sctx))
        || !TEST_true(EVP_PKEY_private_check(sctx)))
        goto err;
    /* Read the public key and add to a verify ctx */
    if (!TEST_ptr(PEM_read_bio_PUBKEY_ex(pub_bio, &pub, NULL, NULL, lib_ctx, NULL))
        || !TEST_ptr(vctx = EVP_PKEY_CTX_new_from_pkey(lib_ctx, pub, NULL)))
        goto err;
    /* verify the signature */
    if (!TEST_int_eq(EVP_PKEY_verify_message_init(vctx, sig_alg, NULL), 1)
        || !TEST_int_eq(EVP_PKEY_verify(vctx, sig, sig_len, msg, msg_len), 1))
        goto err;

    ret = 1;
err:
    EVP_CIPHER_free(cipher);
    EVP_SIGNATURE_free(sig_alg);
    EVP_PKEY_free(gkey);
    EVP_PKEY_free(pub);
    EVP_PKEY_free(priv);
    EVP_PKEY_CTX_free(gctx);
    EVP_PKEY_CTX_free(sctx);
    EVP_PKEY_CTX_free(vctx);
    BIO_free(pub_bio);
    BIO_free(priv_bio);
    OPENSSL_free(sig);
    return ret;
}

static int slh_dsa_deterministic_usage_test(void)
{
    int ret = 0;
    EVP_CIPHER *cipher = NULL; /* Used to encrypt the private key */
    char *pass = "Password";
    BIO *pub_bio = NULL, *priv_bio = NULL;
    EVP_PKEY_CTX *gctx = NULL, *sctx = NULL, *vctx = NULL, *dupctx = NULL;
    EVP_PKEY *gkey = NULL, *pub = NULL, *priv = NULL;
    EVP_SIGNATURE *sig_alg = NULL;
    uint8_t *sig = NULL;
    size_t sig_len = 0, len = 0;
    uint8_t msg[] = { 0x01, 0x02, 0x03, 0x04 };
    size_t msg_len = sizeof(msg);
    const SLH_DSA_KEYGEN_TEST_DATA *tst = &slh_dsa_keygen_testdata[0];
    size_t key_len = tst->priv_len / 2;
    size_t n = key_len / 2;
    int deterministic = 1;
    OSSL_PARAM params[2], *p = params;

    /* Generate a key */
    if (!TEST_ptr(gkey = do_gen_key(tst->name, tst->priv, key_len + n)))
        goto err;

    /* Save it to a BIO - it uses a mem bio for testing */
    if (!TEST_ptr(pub_bio = BIO_new(BIO_s_mem()))
        || !TEST_ptr(priv_bio = BIO_new(BIO_s_mem()))
        || !TEST_ptr(cipher = EVP_CIPHER_fetch(lib_ctx, "AES-256-CBC", NULL))
        || !TEST_true(PEM_write_bio_PUBKEY_ex(pub_bio, gkey, lib_ctx, NULL))
        || !TEST_true(PEM_write_bio_PrivateKey_ex(priv_bio, gkey, cipher,
            NULL, 0, NULL, (void *)pass,
            lib_ctx, NULL)))
        goto err;

    *p++ = OSSL_PARAM_construct_int(OSSL_SIGNATURE_PARAM_DETERMINISTIC, &deterministic);
    *p = OSSL_PARAM_construct_end();

    /* Read the private key and add to a signing ctx */
    if (!TEST_ptr(PEM_read_bio_PrivateKey_ex(priv_bio, &priv, NULL, pass, lib_ctx, NULL))
        || !TEST_ptr(sctx = EVP_PKEY_CTX_new_from_pkey(lib_ctx, priv, NULL))
        /* Init the signature */
        || !TEST_ptr(sig_alg = EVP_SIGNATURE_fetch(lib_ctx, tst->name, NULL))
        || !TEST_int_eq(EVP_PKEY_sign_message_init(sctx, sig_alg, params), 1))
        goto err;

    if (!TEST_ptr(dupctx = EVP_PKEY_CTX_dup(sctx)))
        goto err;

    /* Determine the size of the signature & allocate space */
    if (!TEST_int_eq(EVP_PKEY_sign(sctx, NULL, &sig_len, msg, msg_len), 1))
        goto err;
    len = sig_len;
    if (!TEST_ptr(sig = OPENSSL_calloc(2, sig_len))
        || !TEST_int_eq(EVP_PKEY_sign(sctx, sig, &len, msg, msg_len), 1)
        || !TEST_size_t_eq(sig_len, len)
        || !TEST_int_eq(EVP_PKEY_sign(dupctx, sig + sig_len, &len,
                            msg, msg_len),
            1)
        || !TEST_size_t_eq(sig_len, len))
        goto err;
    /* Read the public key and add to a verify ctx */
    if (!TEST_ptr(PEM_read_bio_PUBKEY_ex(pub_bio, &pub, NULL, NULL, lib_ctx, NULL))
        || !TEST_ptr(vctx = EVP_PKEY_CTX_new_from_pkey(lib_ctx, pub, NULL)))
        goto err;
    EVP_PKEY_CTX_free(dupctx);

    /* verify the signature */
    if (!TEST_int_eq(EVP_PKEY_verify_message_init(vctx, sig_alg, NULL), 1)
        || !TEST_ptr(dupctx = EVP_PKEY_CTX_dup(vctx))
        || !TEST_int_eq(EVP_PKEY_verify(vctx, sig, sig_len, msg, msg_len), 1)
        || !TEST_int_eq(EVP_PKEY_verify(dupctx, sig + sig_len, sig_len,
                            msg, msg_len),
            1))
        goto err;
    ret = 1;
err:
    EVP_CIPHER_free(cipher);
    EVP_SIGNATURE_free(sig_alg);
    EVP_PKEY_free(gkey);
    EVP_PKEY_free(pub);
    EVP_PKEY_free(priv);
    EVP_PKEY_CTX_free(gctx);
    EVP_PKEY_CTX_free(sctx);
    EVP_PKEY_CTX_free(vctx);
    EVP_PKEY_CTX_free(dupctx);
    BIO_free(pub_bio);
    BIO_free(priv_bio);
    OPENSSL_free(sig);
    return ret;
}

static int slh_dsa_digest_sign_verify_test(void)
{
    int ret = 0;
    EVP_PKEY *key = NULL;
    uint8_t *sig = NULL;
    size_t sig_len = 0;
    OSSL_PARAM params[3], *p = params;
    const char *alg = "SLH-DSA-SHA2-128s";
    EVP_MD_CTX *mctx = NULL;
    static uint8_t context[] = "A context String";
    static uint8_t msg[] = "Hello World";
    size_t msg_len = sizeof(msg);

    if (!TEST_ptr(key = do_gen_key(alg, NULL, 0)))
        goto err;

    *p++ = OSSL_PARAM_construct_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING,
        context, sizeof(context));
    *p++ = OSSL_PARAM_construct_end();

    if (!TEST_ptr(mctx = EVP_MD_CTX_new())
        || !TEST_int_eq(EVP_DigestSignInit_ex(mctx, NULL, "SHA256",
                            lib_ctx, "?fips=true",
                            key, params),
            0)
        || !TEST_int_eq(EVP_DigestSignInit_ex(mctx, NULL, NULL, lib_ctx,
                            "?fips=true", key, params),
            1))
        goto err;
    if (!TEST_int_eq(EVP_DigestSign(mctx, NULL, &sig_len, msg, msg_len), 1)
        || !TEST_ptr(sig = OPENSSL_zalloc(sig_len)))
        goto err;
    sig_len--;
    if (!TEST_int_eq(EVP_DigestSign(mctx, sig, &sig_len, msg, msg_len), 0))
        goto err;
    sig_len++;
    if (!TEST_int_eq(EVP_DigestSignInit_ex(mctx, NULL, NULL, lib_ctx, "?fips=true",
                         key, params),
            1)
        || !TEST_int_eq(EVP_DigestSign(mctx, sig, &sig_len, msg, msg_len), 1)
        || !TEST_int_eq(EVP_DigestVerifyInit_ex(mctx, NULL, "SHA256",
                            lib_ctx, "?fips=true",
                            key, params),
            0)
        || !TEST_int_eq(EVP_DigestVerifyInit_ex(mctx, NULL, NULL,
                            lib_ctx, "?fips=true",
                            key, params),
            1)
        || !TEST_int_eq(EVP_DigestVerify(mctx, sig, sig_len, msg, msg_len), 1))
        goto err;
    ret = 1;
err:
    EVP_PKEY_free(key);
    EVP_MD_CTX_free(mctx);
    OPENSSL_free(sig);
    return ret;
}

static int slh_dsa_keygen_invalid_test(void)
{
    int ret = 0;
    const SLH_DSA_KEYGEN_TEST_DATA *tst = &slh_dsa_keygen_testdata[0];
    EVP_PKEY *pkey = NULL;
    EVP_PKEY_CTX *ctx = NULL;
    OSSL_PARAM params[2], *p = params;
    size_t key_len = tst->priv_len;
    size_t n = key_len / 4;
    uint8_t seed[128] = { 0 };

    if (!TEST_ptr(ctx = EVP_PKEY_CTX_new_from_name(lib_ctx, tst->name, NULL))
        || !TEST_int_eq(EVP_PKEY_keygen_init(ctx), 1))
        goto err;

    /* Test the set fails if the seed is larger than the internal buffer */
    p[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_SLH_DSA_SEED,
        seed, 97);
    p[1] = OSSL_PARAM_construct_end();
    if (!TEST_int_eq(EVP_PKEY_CTX_set_params(ctx, params), 0))
        goto err;

    /* Test the generate fails if the seed is not the correct size */
    p[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_SLH_DSA_SEED,
        seed, n * 3 - 1);
    p[1] = OSSL_PARAM_construct_end();

    if (!TEST_int_eq(EVP_PKEY_CTX_set_params(ctx, params), 1)
        || !TEST_int_eq(EVP_PKEY_generate(ctx, &pkey), 0))
        goto err;

    /* Test the generate fails if the seed is not the correct size */
    p[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_SLH_DSA_SEED,
        seed, n * 3 + 1);
    p[1] = OSSL_PARAM_construct_end();
    if (!TEST_int_eq(EVP_PKEY_CTX_set_params(ctx, params), 1)
        || !TEST_int_eq(EVP_PKEY_generate(ctx, &pkey), 0))
        goto err;
    ret = 1;
err:
    EVP_PKEY_free(pkey);
    EVP_PKEY_CTX_free(ctx);
    return ret;
}

const OPTIONS *test_get_options(void)
{
    static const OPTIONS options[] = {
        OPT_TEST_OPTIONS_DEFAULT_USAGE,
        { "config", OPT_CONFIG_FILE, '<',
            "The configuration file to use for the libctx" },
        { NULL }
    };
    return options;
}

int setup_tests(void)
{
    OPTION_CHOICE o;
    char *config_file = NULL;

    while ((o = opt_next()) != OPT_EOF) {
        switch (o) {
        case OPT_CONFIG_FILE:
            config_file = opt_arg();
            break;
        case OPT_TEST_CASES:
            break;
        default:
        case OPT_ERR:
            return 0;
        }
    }
    if (!test_get_libctx(&lib_ctx, &null_prov, config_file, &lib_prov, NULL))
        return 0;

    ADD_TEST(slh_dsa_bad_pub_len_test);
    ADD_TEST(slh_dsa_key_validate_test);
    ADD_TEST(slh_dsa_key_validate_failure_test);
    ADD_TEST(slh_dsa_key_eq_test);
    ADD_TEST(slh_dsa_usage_test);
    ADD_TEST(slh_dsa_deterministic_usage_test);
    ADD_ALL_TESTS(slh_dsa_sign_verify_test, OSSL_NELEM(slh_dsa_sig_testdata));
    ADD_ALL_TESTS(slh_dsa_keygen_test, OSSL_NELEM(slh_dsa_keygen_testdata));
    ADD_TEST(slh_dsa_digest_sign_verify_test);
    ADD_TEST(slh_dsa_keygen_invalid_test);
    return 1;
}

void cleanup_tests(void)
{
    OSSL_PROVIDER_unload(null_prov);
    OSSL_PROVIDER_unload(lib_prov);
    OSSL_LIB_CTX_free(lib_ctx);
}
