/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

#include "crypto/s2n_evp_signing.h"

#include "crypto/s2n_ecdsa.h"
#include "crypto/s2n_fips.h"
#include "crypto/s2n_rsa_pss.h"
#include "crypto/s2n_rsa_signing.h"
#include "s2n_test.h"
#include "testlib/s2n_testlib.h"

/* The ecdsa sign/verify methods are static */
#include "crypto/s2n_ecdsa.c"
#include "crypto/s2n_rsa.c"

#define INPUT_DATA_SIZE  100
#define OUTPUT_DATA_SIZE 1000

#define EXPECT_PKEY_USES_EVP_SIGNING(pkey)   \
    EXPECT_EQUAL(pkey->sign, &s2n_evp_sign); \
    EXPECT_EQUAL(pkey->verify, &s2n_evp_verify)

const uint8_t input_data[INPUT_DATA_SIZE] = "hello hash";

static bool s2n_hash_alg_is_supported(s2n_signature_algorithm sig_alg, s2n_hash_algorithm hash_alg)
{
    return (hash_alg != S2N_HASH_NONE) && (hash_alg != S2N_HASH_MD5)
            && (hash_alg != S2N_HASH_MD5_SHA1 || sig_alg == S2N_SIGNATURE_RSA);
}

static S2N_RESULT s2n_test_hash_init(struct s2n_hash_state *hash_state, s2n_hash_algorithm hash_alg)
{
    RESULT_GUARD_POSIX(s2n_hash_init(hash_state, hash_alg));
    RESULT_GUARD_POSIX(s2n_hash_allow_md5_for_fips(hash_state));
    RESULT_GUARD_POSIX(s2n_hash_update(hash_state, input_data, s2n_array_len(input_data)));
    return S2N_RESULT_OK;
}

static S2N_RESULT s2n_setup_public_key(struct s2n_pkey *public_key, struct s2n_cert_chain_and_key *chain)
{
    s2n_pkey_type pkey_type = S2N_PKEY_TYPE_UNKNOWN;
    EXPECT_OK(s2n_asn1der_to_public_key_and_type(public_key, &pkey_type,
            &chain->cert_chain->head->raw));
    EXPECT_EQUAL(pkey_type, chain->cert_chain->head->pkey_type);
    return S2N_RESULT_OK;
}

static S2N_RESULT s2n_test_evp_sign(s2n_signature_algorithm sig_alg, s2n_hash_algorithm hash_alg,
        struct s2n_pkey *private_key, struct s2n_blob *evp_signature_out)
{
    DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free);
    RESULT_GUARD_POSIX(s2n_hash_new(&hash_state));
    RESULT_GUARD(s2n_test_hash_init(&hash_state, hash_alg));
    RESULT_GUARD_POSIX(s2n_evp_sign(private_key, sig_alg, &hash_state, evp_signature_out));
    return S2N_RESULT_OK;
}

static S2N_RESULT s2n_test_evp_verify(s2n_signature_algorithm sig_alg, s2n_hash_algorithm hash_alg,
        struct s2n_pkey *public_key,
        struct s2n_blob *evp_signature, struct s2n_blob *expected_signature)
{
    DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free);
    RESULT_GUARD_POSIX(s2n_hash_new(&hash_state));

    /* Verify that the EVP methods can verify their own signature */
    RESULT_GUARD(s2n_test_hash_init(&hash_state, hash_alg));
    RESULT_GUARD_POSIX(s2n_evp_verify(public_key, sig_alg, &hash_state, evp_signature));

    /* Verify that using the pkey directly can verify own signature */
    RESULT_GUARD(s2n_test_hash_init(&hash_state, hash_alg));
    RESULT_GUARD_POSIX(s2n_pkey_verify(public_key, sig_alg, &hash_state, evp_signature));

    /* Verify that the EVP methods can verify the known good signature */
    RESULT_GUARD(s2n_test_hash_init(&hash_state, hash_alg));
    RESULT_GUARD_POSIX(s2n_evp_verify(public_key, sig_alg, &hash_state, expected_signature));

    return S2N_RESULT_OK;
}

int main(int argc, char **argv)
{
    BEGIN_TEST();

    /* Sanity check that we're enabling evp signing properly.
     * awslc-fips is known to require evp signing.
     */
    if (s2n_is_in_fips_mode() && s2n_libcrypto_is_awslc()) {
        EXPECT_TRUE(s2n_evp_signing_supported());
    }

    if (!s2n_evp_signing_supported()) {
        END_TEST();
    }

    DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free);
    EXPECT_SUCCESS(s2n_hash_new(&hash_state));

    struct s2n_cert_chain_and_key *rsa_cert_chain = NULL;
    EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&rsa_cert_chain,
            S2N_RSA_2048_PKCS1_CERT_CHAIN, S2N_RSA_2048_PKCS1_KEY));

    /* Test that unsupported hash algs are treated as invalid.
     * Later tests will ignore unsupported algs, so ensure they are actually invalid. */
    {
        /* This pkey should never actually be needed -- any pkey will do */
        struct s2n_pkey *pkey = rsa_cert_chain->private_key;

        for (s2n_signature_algorithm sig_alg = 0; sig_alg <= UINT8_MAX; sig_alg++) {
            for (s2n_hash_algorithm hash_alg = 0; hash_alg < S2N_HASH_SENTINEL; hash_alg++) {
                if (s2n_hash_alg_is_supported(sig_alg, hash_alg)) {
                    continue;
                }

                s2n_stack_blob(evp_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE);
                EXPECT_ERROR_WITH_ERRNO(s2n_test_evp_sign(sig_alg, hash_alg, pkey, &evp_signature),
                        S2N_ERR_HASH_INVALID_ALGORITHM);
                EXPECT_ERROR_WITH_ERRNO(s2n_test_evp_verify(sig_alg, hash_alg, pkey, &evp_signature, &evp_signature),
                        S2N_ERR_HASH_INVALID_ALGORITHM);
            }
        }
    };

    /* EVP signing must match RSA signing */
    {
        s2n_signature_algorithm sig_alg = S2N_SIGNATURE_RSA;

        DEFER_CLEANUP(struct s2n_pkey public_key_parsed = { 0 }, s2n_pkey_free);
        EXPECT_OK(s2n_setup_public_key(&public_key_parsed, rsa_cert_chain));

        struct s2n_pkey *private_key = rsa_cert_chain->private_key;
        struct s2n_pkey *public_key = &public_key_parsed;
        EXPECT_PKEY_USES_EVP_SIGNING(private_key);
        EXPECT_PKEY_USES_EVP_SIGNING(public_key);

        for (s2n_hash_algorithm hash_alg = 0; hash_alg < S2N_HASH_SENTINEL; hash_alg++) {
            if (!s2n_hash_alg_is_supported(sig_alg, hash_alg)) {
                continue;
            }

            /* Calculate the signature using EVP methods */
            s2n_stack_blob(evp_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE);
            EXPECT_OK(s2n_test_evp_sign(sig_alg, hash_alg, private_key, &evp_signature));

            /* Calculate the signature using RSA methods */
            s2n_stack_blob(rsa_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE);
            EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg));
            EXPECT_SUCCESS(s2n_rsa_pkcs1v15_sign(private_key, &hash_state, &rsa_signature));

            /* Verify that the EVP methods can verify both signatures */
            EXPECT_OK(s2n_test_evp_verify(sig_alg, hash_alg, public_key, &evp_signature, &rsa_signature));

            /* Verify that the RSA methods can verify the EVP signature */
            EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg));
            EXPECT_SUCCESS(s2n_rsa_pkcs1v15_verify(public_key, &hash_state, &evp_signature));
        }
    };

    /* EVP signing must match ECDSA signing */
    {
        s2n_signature_algorithm sig_alg = S2N_SIGNATURE_ECDSA;

        struct s2n_cert_chain_and_key *ecdsa_cert_chain = NULL;
        EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&ecdsa_cert_chain,
                S2N_ECDSA_P384_PKCS1_CERT_CHAIN, S2N_ECDSA_P384_PKCS1_KEY));
        DEFER_CLEANUP(struct s2n_pkey public_key_parsed = { 0 }, s2n_pkey_free);
        EXPECT_OK(s2n_setup_public_key(&public_key_parsed, ecdsa_cert_chain));

        struct s2n_pkey *private_key = ecdsa_cert_chain->private_key;
        struct s2n_pkey *public_key = &public_key_parsed;
        EXPECT_PKEY_USES_EVP_SIGNING(private_key);
        EXPECT_PKEY_USES_EVP_SIGNING(public_key);

        for (s2n_hash_algorithm hash_alg = 0; hash_alg < S2N_HASH_SENTINEL; hash_alg++) {
            if (!s2n_hash_alg_is_supported(sig_alg, hash_alg)) {
                continue;
            }

            /* Calculate the signature using EVP methods */
            s2n_stack_blob(evp_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE);
            EXPECT_OK(s2n_test_evp_sign(sig_alg, hash_alg, private_key, &evp_signature));

            /* Calculate the signature using ECDSA methods */
            s2n_stack_blob(ecdsa_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE);
            EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg));
            EXPECT_SUCCESS(s2n_ecdsa_sign(private_key, sig_alg, &hash_state, &ecdsa_signature));

            /* Verify that the EVP methods can verify both signatures */
            EXPECT_OK(s2n_test_evp_verify(sig_alg, hash_alg, public_key, &evp_signature, &ecdsa_signature));

            /* Verify that the ECDSA methods can verify the EVP signature */
            EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg));
            EXPECT_SUCCESS(s2n_ecdsa_verify(public_key, sig_alg, &hash_state, &evp_signature));
        }

        EXPECT_SUCCESS(s2n_cert_chain_and_key_free(ecdsa_cert_chain));
    };

    /* EVP signing must match RSA-PSS-RSAE signing */
    if (s2n_is_rsa_pss_signing_supported()) {
        s2n_signature_algorithm sig_alg = S2N_SIGNATURE_RSA_PSS_RSAE;

        DEFER_CLEANUP(struct s2n_pkey public_key_parsed = { 0 }, s2n_pkey_free);
        EXPECT_OK(s2n_setup_public_key(&public_key_parsed, rsa_cert_chain));

        struct s2n_pkey *private_key = rsa_cert_chain->private_key;
        struct s2n_pkey *public_key = &public_key_parsed;
        EXPECT_PKEY_USES_EVP_SIGNING(private_key);
        EXPECT_PKEY_USES_EVP_SIGNING(public_key);

        for (s2n_hash_algorithm hash_alg = 0; hash_alg < S2N_HASH_SENTINEL; hash_alg++) {
            if (!s2n_hash_alg_is_supported(sig_alg, hash_alg)) {
                continue;
            }

            /* Calculate the signature using EVP methods */
            s2n_stack_blob(evp_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE);
            EXPECT_OK(s2n_test_evp_sign(sig_alg, hash_alg, private_key, &evp_signature));

            /* Calculate the signature using RSA-PSS methods */
            s2n_stack_blob(rsa_pss_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE);
            EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg));
            EXPECT_SUCCESS(s2n_rsa_pss_sign(private_key, &hash_state, &rsa_pss_signature));

            /* Verify that the EVP methods can verify both signatures */
            EXPECT_OK(s2n_test_evp_verify(sig_alg, hash_alg, public_key, &evp_signature, &rsa_pss_signature));

            /* Verify that the RSA-PSS methods can verify the EVP signature */
            EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg));
            EXPECT_SUCCESS(s2n_rsa_pss_verify(public_key, &hash_state, &evp_signature));
        }
    }

    /* EVP signing must match RSA-PSS-PSS signing */
    if (s2n_is_rsa_pss_certs_supported()) {
        s2n_signature_algorithm sig_alg = S2N_SIGNATURE_RSA_PSS_PSS;

        struct s2n_cert_chain_and_key *rsa_pss_cert_chain = NULL;
        EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&rsa_pss_cert_chain,
                S2N_RSA_PSS_2048_SHA256_LEAF_CERT, S2N_RSA_PSS_2048_SHA256_LEAF_KEY));
        DEFER_CLEANUP(struct s2n_pkey public_key_parsed = { 0 }, s2n_pkey_free);
        EXPECT_OK(s2n_setup_public_key(&public_key_parsed, rsa_pss_cert_chain));

        struct s2n_pkey *private_key = rsa_pss_cert_chain->private_key;
        struct s2n_pkey *public_key = &public_key_parsed;
        EXPECT_PKEY_USES_EVP_SIGNING(private_key);
        EXPECT_PKEY_USES_EVP_SIGNING(public_key);

        for (s2n_hash_algorithm hash_alg = 0; hash_alg < S2N_HASH_SENTINEL; hash_alg++) {
            if (!s2n_hash_alg_is_supported(sig_alg, hash_alg)) {
                continue;
            }

            /* Calculate the signature using EVP methods */
            s2n_stack_blob(evp_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE);
            EXPECT_OK(s2n_test_evp_sign(sig_alg, hash_alg, private_key, &evp_signature));

            /* Calculate the signature using RSA-PSS methods */
            s2n_stack_blob(rsa_pss_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE);
            EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg));
            EXPECT_SUCCESS(s2n_rsa_pss_sign(private_key, &hash_state, &rsa_pss_signature));

            /* Verify that the EVP methods can verify both signatures */
            EXPECT_OK(s2n_test_evp_verify(sig_alg, hash_alg, public_key, &evp_signature, &rsa_pss_signature));

            /* Verify that the RSA-PSS methods can verify the EVP signature */
            EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg));
            EXPECT_SUCCESS(s2n_rsa_pss_verify(public_key, &hash_state, &evp_signature));
        }

        EXPECT_SUCCESS(s2n_cert_chain_and_key_free(rsa_pss_cert_chain));
    }

    EXPECT_SUCCESS(s2n_cert_chain_and_key_free(rsa_cert_chain));
    END_TEST();
}
