/* Copyright (c) (2014-2020) Apple Inc. All rights reserved.
 *
 * corecrypto is licensed under Apple Inc.’s Internal Use License Agreement (which
 * is contained in the License.txt file distributed with corecrypto) and only to
 * people who accept that license. IMPORTANT:  Any license rights granted to you by
 * Apple Inc. (if any) are limited to internal use within your organization only on
 * devices and computers you own or control, for the sole purpose of verifying the
 * security characteristics and correct functioning of the Apple Software.  You may
 * not, directly or indirectly, redistribute the Apple Software or any portions thereof.
 */

#include "testmore.h"
#include "testbyteBuffer.h"
#include "testccnBuffer.h"
#include <corecrypto/cc_macros.h>

#if (CCECIES == 0)
entryPoint(ccies, "ccies")
#else

#include "crypto_test_ecies.h"
#include "../ccec/crypto_test/crypto_test_ec.h"
#include <corecrypto/ccec.h>
#include <corecrypto/ccec_priv.h>
#include "ccec_internal.h"
#include <corecrypto/ccecies.h>
#include <corecrypto/ccecies_priv.h>
#include "ccecies_internal.h"
#include <corecrypto/ccaes.h>
#include <corecrypto/ccrng_test.h>
#include <corecrypto/ccrng_ecfips_test.h>
#include <corecrypto/ccsha1.h>
#include <corecrypto/ccsha2.h>

static const int verbose = 1;

static const uint8_t shared_info1[] = "test1";
static const uint8_t shared_info2[] = "test2";
static const uint8_t shared_info3[] = "test3";

#define di_SHA1 &ccsha1_eay_di
#define di_SHA224 &ccsha224_ltc_di
#define di_SHA256 &ccsha256_ltc_di
#define di_SHA384 &ccsha384_ltc_di
#define di_SHA512 &ccsha512_ltc_di

const struct ccecies_vector ccecies_aes_gcm_vectors[] = {
#include "../test_vectors/ecies_aes_gcm.inc"
#include "../test_vectors/ecies_aes_gcm_legacy.inc"
    // End
    {
        .di = NULL,
    }
};

static int is_zero(size_t nbytes, uint8_t *p)
{
    int rc = 0;
    for (size_t i = 0; i < nbytes; i++) {
        rc |= p[i];
    }
    return (rc == 0);
}

// Superset of the inputs necessary for ccecies_encrypt_gcm and ccecies_encrypt_gcm_from_shared_secret
// Execute both and verify they behave the same (error or valid cases)
static int test_ccecies_encrypt_gcm(ccec_pub_ctx_t public_key,
                                    const ccecies_gcm_t ecies,
                                    ccec_pub_ctx_t ephemeral_public_key,
                                    size_t shared_secret_nbytes,
                                    const uint8_t *shared_secret,
                                    size_t plaintext_nbytes,
                                    const uint8_t *plaintext,
                                    size_t sharedinfo1_nbytes,
                                    const void *sharedinfo1,
                                    size_t sharedinfo2_nbytes,
                                    const void *sharedinfo2,
                                    size_t *encrypted_blob_nbytes,
                                    uint8_t *encrypted_blob)
{
    int status1;
    int status2;
    // shadow output parameter
    byteBuffer encrypted_blob1 = mallocByteBuffer(*encrypted_blob_nbytes);
    byteBuffer encrypted_blob2 = mallocByteBuffer(*encrypted_blob_nbytes);
    size_t encrypted_blob_nbytes1 = *encrypted_blob_nbytes;
    size_t encrypted_blob_nbytes2 = *encrypted_blob_nbytes;

    status1 = ccecies_encrypt_gcm(public_key,
                                  ecies,
                                  plaintext_nbytes,
                                  plaintext,
                                  sharedinfo1_nbytes,
                                  sharedinfo1,
                                  sharedinfo2_nbytes,
                                  sharedinfo2,
                                  &encrypted_blob_nbytes1,
                                  encrypted_blob1->bytes);

    status2 = ccecies_encrypt_gcm_from_shared_secret(public_key,
                                                     ecies,
                                                     ephemeral_public_key,
                                                     shared_secret_nbytes,
                                                     shared_secret,
                                                     plaintext_nbytes,
                                                     plaintext,
                                                     sharedinfo1_nbytes,
                                                     sharedinfo1,
                                                     sharedinfo2_nbytes,
                                                     sharedinfo2,
                                                     &encrypted_blob_nbytes2,
                                                     encrypted_blob2->bytes);

    if (ecies->options & (ECIES_EPH_PUBKEY_IN_SHAREDINFO1 | ECIES_EPH_PUBKEY_AND_SHAREDINFO1)) {
        is(status1, status2, "Encrypt same error code");
        is(encrypted_blob_nbytes1, encrypted_blob_nbytes2, "Encrypt same output size");
        ok_memcmp(encrypted_blob1->bytes, encrypted_blob2->bytes, encrypted_blob2->len, "Encrypt same output value");
    }
    memcpy(encrypted_blob, encrypted_blob1->bytes, encrypted_blob1->len);
    *encrypted_blob_nbytes = encrypted_blob_nbytes1;
    return status1;
}

// Superset of the inputs necessary for ccecies_decrypt_gcm and ccecies_decrypt_gcm_from_shared_secret
// Execute both and verify they behave the same (error or valid cases)
static int test_ccecies_decrypt_gcm(ccec_full_ctx_t full_key,
                                    const ccecies_gcm_t ecies,
                                    size_t shared_secret_nbytes,
                                    const uint8_t *shared_secret,
                                    size_t encrypted_blob_nbytes,
                                    const uint8_t *encrypted_blob,
                                    size_t sharedinfo1_nbytes,
                                    const void *sharedinfo1,
                                    size_t sharedinfo2_nbytes,
                                    const void *sharedinfo2,
                                    size_t *plaintext_nbytes,
                                    uint8_t *plaintext)
{
    int status1;
    int status2;
    // shadow output parameter
    byteBuffer plaintext1 = mallocByteBuffer(*plaintext_nbytes);
    byteBuffer plaintext2 = mallocByteBuffer(*plaintext_nbytes);
    size_t plaintext_nbytes1 = *plaintext_nbytes;
    size_t plaintext_nbytes2 = *plaintext_nbytes;

    status1 = ccecies_decrypt_gcm(full_key,
                                  ecies,
                                  encrypted_blob_nbytes,
                                  encrypted_blob,
                                  sharedinfo1_nbytes,
                                  sharedinfo1,
                                  sharedinfo2_nbytes,
                                  sharedinfo2,
                                  &plaintext_nbytes1,
                                  plaintext1->bytes);

    status2 = ccecies_decrypt_gcm_from_shared_secret(ccec_ctx_cp(full_key),
                                                     ecies,
                                                     shared_secret_nbytes,
                                                     shared_secret,
                                                     encrypted_blob_nbytes,
                                                     encrypted_blob,
                                                     sharedinfo1_nbytes,
                                                     sharedinfo1,
                                                     sharedinfo2_nbytes,
                                                     sharedinfo2,
                                                     &plaintext_nbytes2,
                                                     plaintext2->bytes);

    if (ecies->options & (ECIES_EPH_PUBKEY_IN_SHAREDINFO1 | ECIES_EPH_PUBKEY_AND_SHAREDINFO1)) {
        is(!!status1, !!status2, "Same error code"); // slight variation of error code since elliptic curve operations are not
                                                     // performed in the case of ccecies_decrypt_gcm_from_shared_secret
        is(plaintext_nbytes1, plaintext_nbytes2, "Decrypt Same output size");
        ok_memcmp(plaintext1->bytes, plaintext2->bytes, plaintext2->len, "Decrypt Same output value");
    }
    memcpy(plaintext, plaintext1->bytes, plaintext1->len);
    *plaintext_nbytes = plaintext_nbytes1;
    return status1;
}

// Negative testing based on KATs
static int ccecies_gcm_kat_negative_test(const struct ccecies_vector *test, int test_counter)
{
    int status = 0; // fail
    byteBuffer plaintext = hexStringToBytes(test->message);
    byteBuffer expectedCiphertext = hexStringToBytes(
        ((test->options & ECIES_EXPORT_PUB_STANDARD) == ECIES_EXPORT_PUB_STANDARD) ? test->cipher : test->compact_cipher);
    size_t sharedInfo1_size = strlen(test->sharedInfo1);
    size_t sharedInfo2_size = strlen(test->sharedInfo2);
    byteBuffer eph_priv_key = hexStringToBytes(test->eph_priv_key);
    byteBuffer dec_priv_key = hexStringToBytes(test->dec_priv_key);
    byteBuffer shared_secret = hexStringToBytes(test->Z);
    struct ccrng_ecfips_test_state rng;
    struct ccecies_gcm ecies_enc;
    struct ccecies_gcm ecies_dec;
    ccec_const_cp_t cp = test->curve();
    size_t fake_size;
    size_t output_size;
    ccec_full_ctx_decl_cp(cp, remote_key);
    ccec_full_ctx_decl_cp(cp, ephemeral_key);
    // Buffer for outputs
    byteBuffer ciphertext = NULL;
    byteBuffer plaintext_bis = NULL;

    // Generate "remote" public key from private key
    ok_or_goto(
        ccec_recover_full_key(cp, dec_priv_key->len, dec_priv_key->bytes, remote_key) == 0, "Generated private Key", errout);
    ok_or_goto(
        ccec_recover_full_key(cp, eph_priv_key->len, eph_priv_key->bytes, ephemeral_key) == 0, "Generated ephemeral Key", errout);

    // Set RNG to control ephemeral key
    ccrng_ecfips_test_init(&rng, eph_priv_key->len, eph_priv_key->bytes);

    // Invalid setup
    is(ccecies_encrypt_gcm_setup(
           &ecies_enc, test->di, (struct ccrng_state *)&rng, ccaes_gcm_encrypt_mode(), 12, test->mac_nbytes, test->options),
       CCERR_CRYPTO_CONFIG,
       "Negative test encrypt setup");
    is(ccecies_decrypt_gcm_setup(&ecies_dec, test->di, ccaes_gcm_decrypt_mode(), 13, test->mac_nbytes, test->options),
       CCERR_CRYPTO_CONFIG,
       "Negative test decrypt setup");
    is(ccecies_encrypt_gcm_setup(
           &ecies_enc, test->di, (struct ccrng_state *)&rng, ccaes_gcm_encrypt_mode(), test->key_nbytes, 3, test->options),
       CCERR_CRYPTO_CONFIG,
       "Negative test encrypt setup");
    is(ccecies_decrypt_gcm_setup(&ecies_dec, test->di, ccaes_gcm_decrypt_mode(), test->key_nbytes, 7, test->options),
       CCERR_CRYPTO_CONFIG,
       "Negative test decrypt setup");

    // Actual setup
    is(ccecies_encrypt_gcm_setup(&ecies_enc,
                                 test->di,
                                 (struct ccrng_state *)&rng,
                                 ccaes_gcm_encrypt_mode(),
                                 test->key_nbytes,
                                 test->mac_nbytes,
                                 test->options),
       0,
       "Negative test encrypt setup");
    is(ccecies_decrypt_gcm_setup(
           &ecies_dec, test->di, ccaes_gcm_decrypt_mode(), test->key_nbytes, test->mac_nbytes, test->options),
       0,
       "Negative test decrypt setup");

    ccec_pub_ctx_t pub_remote_key = ccec_ctx_pub(remote_key);
    ciphertext = mallocByteBuffer(ccecies_encrypt_gcm_ciphertext_size(pub_remote_key, &ecies_enc, plaintext->len));
    plaintext_bis = mallocByteBuffer(ccecies_decrypt_gcm_plaintext_size_cp(cp, &ecies_dec, ciphertext->len));

    // Valid encrypted value
    ok_or_goto(test_ccecies_encrypt_gcm(pub_remote_key,
                                        &ecies_enc,
                                        ccec_ctx_pub(ephemeral_key),
                                        shared_secret->len,
                                        shared_secret->bytes,
                                        plaintext->len,
                                        plaintext->bytes,
                                        sharedInfo1_size,
                                        test->sharedInfo1,
                                        sharedInfo2_size,
                                        test->sharedInfo2,
                                        &ciphertext->len,
                                        ciphertext->bytes) == 0,
               "Encrypt",
               errout);

    // ------------------------------
    // Negative testing of decrypt
    // ------------------------------

    // Wrong size
    is(ccecies_decrypt_gcm_plaintext_size(remote_key, &ecies_dec, 0), (size_t)0, "Error case");

    // Corrupted public key
    ciphertext->bytes[2] ^= 1;
    memset(plaintext_bis->bytes, 0xAA, plaintext_bis->len);
    output_size = plaintext_bis->len;
    ok_or_goto(test_ccecies_decrypt_gcm(remote_key,
                                        &ecies_dec,
                                        shared_secret->len,
                                        shared_secret->bytes,
                                        ciphertext->len,
                                        ciphertext->bytes,
                                        sharedInfo1_size,
                                        test->sharedInfo1,
                                        sharedInfo2_size,
                                        test->sharedInfo2,
                                        &output_size,
                                        plaintext_bis->bytes) != 0,
               "Corrupted public key",
               errout);
    ok(is_zero(plaintext_bis->len, plaintext_bis->bytes), "Delete on error");
    ciphertext->bytes[2] ^= 1;

    // Corrupted encrypted data (first byte)
    size_t b = ((test->options & ECIES_EXPORT_PUB_STANDARD) == ECIES_EXPORT_PUB_STANDARD)
                   ? ccec_x963_export_size(0, pub_remote_key)
                   : ccec_compact_export_size(0, pub_remote_key);
    ciphertext->bytes[b] ^= 1;
    memset(plaintext_bis->bytes, 0xAA, plaintext_bis->len);
    output_size = plaintext_bis->len;
    ok_or_goto(test_ccecies_decrypt_gcm(remote_key,
                                        &ecies_dec,
                                        shared_secret->len,
                                        shared_secret->bytes,
                                        ciphertext->len,
                                        ciphertext->bytes,
                                        sharedInfo1_size,
                                        test->sharedInfo1,
                                        sharedInfo2_size,
                                        test->sharedInfo2,
                                        &output_size,
                                        plaintext_bis->bytes) != 0,
               "Corrupted encrypted data, first byte",
               errout);
    ok(is_zero(plaintext_bis->len, plaintext_bis->bytes), "Delete on error");
    ciphertext->bytes[b] ^= 1;

    // Corrupted encrypted data (last byte)
    ciphertext->bytes[ciphertext->len - test->mac_nbytes - 1] ^= 1;
    memset(plaintext_bis->bytes, 0xAA, plaintext_bis->len);
    output_size = plaintext_bis->len;
    ok_or_goto(test_ccecies_decrypt_gcm(remote_key,
                                        &ecies_dec,
                                        shared_secret->len,
                                        shared_secret->bytes,
                                        ciphertext->len,
                                        ciphertext->bytes,
                                        sharedInfo1_size,
                                        test->sharedInfo1,
                                        sharedInfo2_size,
                                        test->sharedInfo2,
                                        &output_size,
                                        plaintext_bis->bytes) != 0,
               "Corrupted encrypted data, last byte",
               errout);
    ok(is_zero(plaintext_bis->len, plaintext_bis->bytes), "Delete on error");
    ciphertext->bytes[ciphertext->len - test->mac_nbytes - 1] ^= 1;

    // Corrupted mac
    ciphertext->bytes[ciphertext->len - test->mac_nbytes] ^= 1;
    memset(plaintext_bis->bytes, 0xAA, plaintext_bis->len);
    output_size = plaintext_bis->len;
    ok_or_goto(test_ccecies_decrypt_gcm(remote_key,
                                        &ecies_dec,
                                        shared_secret->len,
                                        shared_secret->bytes,
                                        ciphertext->len,
                                        ciphertext->bytes,
                                        sharedInfo1_size,
                                        test->sharedInfo1,
                                        sharedInfo2_size,
                                        test->sharedInfo2,
                                        &output_size,
                                        plaintext_bis->bytes) != 0,
               "Corrupted mac",
               errout);
    ok(is_zero(plaintext_bis->len, plaintext_bis->bytes), "Delete on error");
    ciphertext->bytes[ciphertext->len - test->mac_nbytes] ^= 1;

    // Output buffer too small
    fake_size = plaintext_bis->len - 1;
    memset(plaintext_bis->bytes, 0xAA, plaintext_bis->len);
    output_size = fake_size;
    ok_or_goto(test_ccecies_decrypt_gcm(remote_key,
                                        &ecies_dec,
                                        shared_secret->len,
                                        shared_secret->bytes,
                                        ciphertext->len,
                                        ciphertext->bytes,
                                        sharedInfo1_size,
                                        test->sharedInfo1,
                                        sharedInfo2_size,
                                        test->sharedInfo2,
                                        &output_size,
                                        plaintext_bis->bytes) != 0,
               "Decrypt: output too small",
               errout);
    ok(is_zero(fake_size, plaintext_bis->bytes), "Delete on error");

    memset(plaintext_bis->bytes, 0xAA, plaintext_bis->len);
    output_size = plaintext_bis->len;
    if (test->options & ECIES_EPH_PUBKEY_IN_SHAREDINFO1) {
        // SharedInfo1 must not be passed
        ok_or_goto(test_ccecies_decrypt_gcm(remote_key,
                                            &ecies_dec,
                                            shared_secret->len,
                                            shared_secret->bytes,
                                            ciphertext->len,
                                            ciphertext->bytes,
                                            sizeof(shared_info3),
                                            shared_info3,
                                            sharedInfo2_size,
                                            test->sharedInfo2,
                                            &output_size,
                                            plaintext_bis->bytes) != 0,
                   "SharedInfo1 must not be passed",
                   errout);
        ok(is_zero(plaintext_bis->len, plaintext_bis->bytes), "Delete on error");
    } else if (test->options & ECIES_EPH_PUBKEY_AND_SHAREDINFO1) {
        // SharedInfo1 must be passed
        ok_or_goto(test_ccecies_decrypt_gcm(remote_key,
                                            &ecies_dec,
                                            shared_secret->len,
                                            shared_secret->bytes,
                                            ciphertext->len,
                                            ciphertext->bytes,
                                            0,
                                            NULL,
                                            sharedInfo2_size,
                                            test->sharedInfo2,
                                            &output_size,
                                            plaintext_bis->bytes) != 0,
                   "SharedInfo1 must be passed",
                   errout);
        ok(is_zero(plaintext_bis->len, plaintext_bis->bytes), "Delete on error");
    }

    if ((test->options & ECIES_EPH_PUBKEY_IN_SHAREDINFO1) == 0) {
        // SharedInfo1 mismatch
        ok_or_goto(test_ccecies_decrypt_gcm(remote_key,
                                            &ecies_dec,
                                            shared_secret->len,
                                            shared_secret->bytes,
                                            ciphertext->len,
                                            ciphertext->bytes,
                                            sizeof(shared_info3),
                                            shared_info3,
                                            sharedInfo2_size,
                                            test->sharedInfo2,
                                            &output_size,
                                            plaintext_bis->bytes) != 0,
                   "SharedInfo1 mismatch",
                   errout);
        ok(is_zero(plaintext_bis->len, plaintext_bis->bytes), "Delete on error");
    }

    // SharedInfo2 mismatch
    memset(plaintext_bis->bytes, 0xAA, plaintext_bis->len);
    output_size = plaintext_bis->len;
    ok_or_goto(test_ccecies_decrypt_gcm(remote_key,
                                        &ecies_dec,
                                        shared_secret->len,
                                        shared_secret->bytes,
                                        ciphertext->len,
                                        ciphertext->bytes,
                                        sharedInfo1_size,
                                        test->sharedInfo1,
                                        sizeof(shared_info3),
                                        shared_info3,
                                        &output_size,
                                        plaintext_bis->bytes) != 0,
               "SharedInfo2 mismatch",
               errout);
    ok_or_goto(is_zero(plaintext_bis->len, plaintext_bis->bytes), "Delete on error", errout);

    // ------------------------------
    // Negative testing of encrypt
    // ------------------------------

    // Wrong size
    struct ccecies_gcm ecies_enc_bogus;
    ecies_enc_bogus = ecies_enc;
    ecies_enc_bogus.options = 0;

    is_or_goto(
        ccecies_encrypt_gcm_ciphertext_size(pub_remote_key, &ecies_enc_bogus, plaintext->len), (size_t)0, "Error case", errout);

    // Bad random, does not apply to "from shared secret version" since
    // it does not generate ephemeral key
    ccrng_ecfips_test_init(&rng, 0, NULL);
    output_size = ciphertext->len;
    ok_or_goto(ccecies_encrypt_gcm(pub_remote_key,
                                   &ecies_enc,
                                   plaintext->len,
                                   plaintext->bytes,
                                   sharedInfo1_size,
                                   test->sharedInfo1,
                                   sharedInfo2_size,
                                   test->sharedInfo2,
                                   &output_size,
                                   ciphertext->bytes) != 0,
               "Bad random",
               errout);

    // Output size too small
    fake_size = ccecies_encrypt_gcm_ciphertext_size(pub_remote_key, &ecies_enc, plaintext->len) - 1;
    ok_or_goto(test_ccecies_encrypt_gcm(pub_remote_key,
                                        &ecies_enc,
                                        ccec_ctx_pub(ephemeral_key),
                                        shared_secret->len,
                                        shared_secret->bytes,
                                        plaintext->len,
                                        plaintext->bytes,
                                        sharedInfo1_size,
                                        test->sharedInfo1,
                                        sharedInfo2_size,
                                        test->sharedInfo2,
                                        &fake_size,
                                        ciphertext->bytes) != 0,
               "Encrypt: output too small",
               errout);

    status = 1; // pass
errout:
    ok(status == 1, "Error in test %i", test_counter);
    free(expectedCiphertext);
    free(eph_priv_key);
    free(dec_priv_key);
    free(plaintext);
    free(plaintext_bis);
    free(ciphertext);
    free(shared_secret);
    ccec_full_ctx_clear_cp(cp, remote_key);
    ccec_full_ctx_clear_cp(cp, ephemeral_key);
    return status;
}

// Process one vector
static int ccecies_gcm_kat_test(const struct ccecies_vector *test, int test_counter)
{
    int status = 0; // fail
    byteBuffer plaintext = hexStringToBytes(test->message);
    byteBuffer expectedCiphertext = hexStringToBytes(
        ((test->options & ECIES_EXPORT_PUB_STANDARD) == ECIES_EXPORT_PUB_STANDARD) ? test->cipher : test->compact_cipher);
    size_t sharedInfo1_size = strlen(test->sharedInfo1);
    size_t sharedInfo2_size = strlen(test->sharedInfo2);
    byteBuffer eph_priv_key = hexStringToBytes(test->eph_priv_key);
    byteBuffer dec_priv_key = hexStringToBytes(test->dec_priv_key);
    byteBuffer shared_secret = hexStringToBytes(test->Z);
    struct ccrng_ecfips_test_state rng;
    struct ccecies_gcm ecies_enc;
    struct ccecies_gcm ecies_dec;
    ccec_const_cp_t cp = test->curve();

    ccec_full_ctx_decl_cp(cp, remote_key);
    ccec_full_ctx_decl_cp(cp, ephemeral_key);

    // Buffer for outputs
    byteBuffer ciphertext = NULL;
    byteBuffer plaintext_bis = NULL;

    // Generate "remote" public key from private key
    ok_or_goto(ccec_recover_full_key(cp, dec_priv_key->len, dec_priv_key->bytes, remote_key) == 0, "Generated Key", errout);
    ok_or_goto(ccec_recover_full_key(cp, eph_priv_key->len, eph_priv_key->bytes, ephemeral_key) == 0, "Generated Key", errout);

    // Set RNG to control ephemeral key
    ccrng_ecfips_test_init(&rng, eph_priv_key->len, eph_priv_key->bytes);
    is(ccecies_encrypt_gcm_setup(&ecies_enc,
                                 test->di,
                                 (struct ccrng_state *)&rng,
                                 ccaes_gcm_encrypt_mode(),
                                 test->key_nbytes,
                                 test->mac_nbytes,
                                 test->options),
       0,
       "KAT Encrypt setup");
    is(ccecies_decrypt_gcm_setup(
           &ecies_dec, test->di, ccaes_gcm_decrypt_mode(), test->key_nbytes, test->mac_nbytes, test->options),
       0,
       "KAT Decrypt setup");

    ccec_pub_ctx_t pub_remote_key = ccec_ctx_pub(remote_key);
    ciphertext = mallocByteBuffer(ccecies_encrypt_gcm_ciphertext_size(pub_remote_key, &ecies_enc, plaintext->len));
    plaintext_bis = mallocByteBuffer(ccecies_decrypt_gcm_plaintext_size_cp(cp, &ecies_dec, ciphertext->len));

    is_or_goto(test_ccecies_encrypt_gcm(pub_remote_key,
                                        &ecies_enc,
                                        ccec_ctx_pub(ephemeral_key),
                                        shared_secret->len,
                                        shared_secret->bytes,
                                        plaintext->len,
                                        plaintext->bytes,
                                        sharedInfo1_size,
                                        test->sharedInfo1,
                                        sharedInfo2_size,
                                        test->sharedInfo2,
                                        &ciphertext->len,
                                        ciphertext->bytes),
               0,
               "Encrypt",
               errout);
    is_or_goto(test_ccecies_decrypt_gcm(remote_key,
                                        &ecies_dec,
                                        shared_secret->len,
                                        shared_secret->bytes,
                                        ciphertext->len,
                                        ciphertext->bytes,
                                        sharedInfo1_size,
                                        test->sharedInfo1,
                                        sharedInfo2_size,
                                        test->sharedInfo2,
                                        &plaintext_bis->len,
                                        plaintext_bis->bytes),
               0,
               "Decrypt",
               errout);

    // Checks
    is(ciphertext->len, expectedCiphertext->len, "Ciphertext size correct");

    // Manually checks internal fields for debugging
    // Point
    size_t point_nbytes = expectedCiphertext->len - test->mac_nbytes - plaintext->len;
    ok_memcmp(ciphertext->bytes, expectedCiphertext->bytes, point_nbytes, "Point as expected");

    // Encrypted
    size_t ciphertext_offset = (expectedCiphertext->len - test->mac_nbytes - plaintext->len);
    ok_memcmp(ciphertext->bytes + ciphertext_offset,
              expectedCiphertext->bytes + ciphertext_offset,
              plaintext->len,
              "Encrypted data as expected");

    // Tag
    size_t tag_offset = (expectedCiphertext->len - test->mac_nbytes);
    ok_memcmp(ciphertext->bytes + tag_offset, expectedCiphertext->bytes + tag_offset, test->mac_nbytes, "Tag as expected");

    is(plaintext->len, plaintext_bis->len, "Decrypted plaintext size correct");
    ok_memcmp(plaintext->bytes, plaintext_bis->bytes, plaintext->len, "Recovered plaintext correct");

    status = 1;
errout:
    ok(status == 1, "Error in test %i", test_counter);
    free(expectedCiphertext);
    free(eph_priv_key);
    free(dec_priv_key);
    free(plaintext);
    free(plaintext_bis);
    free(ciphertext);
    free(shared_secret);
    ccec_full_ctx_clear_cp(cp, remote_key);
    ccec_full_ctx_clear_cp(cp, ephemeral_key);
    return status;
}

static int ecies_aes_gcm_crypt_decrypt(ccec_full_ctx_t key,
                                       size_t msg_size,
                                       const struct ccdigest_info *di,
                                       struct ccrng_state *rng,
                                       uint32_t options)
{
    int status = 0; // fail
    byteBuffer plaintext = mallocByteBuffer(msg_size);

    struct ccecies_gcm ecies_enc;
    struct ccecies_gcm ecies_dec;

    ccrng_generate(rng, msg_size, plaintext->bytes);

    is(ccecies_encrypt_gcm_setup(&ecies_enc, di, rng, ccaes_gcm_encrypt_mode(), 16, 16, options), 0, "Roundtrip encrypt setup");
    is(ccecies_decrypt_gcm_setup(&ecies_dec, di, ccaes_gcm_decrypt_mode(), 16, 16, options), 0, "Roundtrip decrypt setup");

    ccec_pub_ctx_t pub_key = ccec_ctx_pub(key);

    byteBuffer ciphertext = mallocByteBuffer(ccecies_encrypt_gcm_ciphertext_size(pub_key, &ecies_enc, msg_size));

    byteBuffer plaintext_bis = mallocByteBuffer(ccecies_decrypt_gcm_plaintext_size(key, &ecies_dec, ciphertext->len));

    // With shared info
    if ((options & ECIES_EPH_PUBKEY_IN_SHAREDINFO1) == 0) {
        ok_or_goto(ccecies_encrypt_gcm(pub_key,
                                       &ecies_enc,
                                       plaintext->len,
                                       plaintext->bytes,
                                       sizeof(shared_info1),
                                       shared_info1,
                                       sizeof(shared_info2),
                                       shared_info2,
                                       &ciphertext->len,
                                       ciphertext->bytes) == 0,
                   "Encrypt",
                   errout);

        ok_or_goto(ccecies_decrypt_gcm(key,
                                       &ecies_dec,
                                       ciphertext->len,
                                       ciphertext->bytes,
                                       sizeof(shared_info1),
                                       shared_info1,
                                       sizeof(shared_info2),
                                       shared_info2,
                                       &plaintext_bis->len,
                                       plaintext_bis->bytes) == 0,
                   "Decrypt",
                   errout);

        ok_or_goto(plaintext->len == plaintext_bis->len, "Encrypt/Decrypt correct keysize", errout);
        ok_or_goto(memcmp(plaintext->bytes, plaintext_bis->bytes, plaintext->len) == 0, "Shared secrets match", errout);
    }

    // Without shared info
    if ((options & ECIES_EPH_PUBKEY_AND_SHAREDINFO1) == 0) {
        ok_or_goto(
            ccecies_encrypt_gcm(
                pub_key, &ecies_enc, plaintext->len, plaintext->bytes, 0, NULL, 0, NULL, &ciphertext->len, ciphertext->bytes) ==
                0,
            "Encrypt",
            errout);

        ok_or_goto(ccecies_decrypt_gcm(key,
                                       &ecies_dec,
                                       ciphertext->len,
                                       ciphertext->bytes,
                                       0,
                                       NULL,
                                       0,
                                       NULL,
                                       &plaintext_bis->len,
                                       plaintext_bis->bytes) == 0,
                   "Decrypt",
                   errout);

        ok_or_goto(plaintext->len == plaintext_bis->len, "Encrypt/Decrypt correct keysize", errout);
        ok_or_goto(memcmp(plaintext->bytes, plaintext_bis->bytes, plaintext->len) == 0, "Shared secrets match", errout);
    }

    status = 1; // Success

errout:
    free(plaintext);
    free(plaintext_bis);
    free(ciphertext);
    return status;
}

static int ecies_test(struct ccrng_state *rng, size_t expected_keysize, ccec_const_cp_t cp, uint32_t options)
{
    int status = 0;
    ccec_full_ctx_decl_cp(cp, full_key);

    ok_or_fail(ccec_generate_key_fips(cp, rng, full_key) == 0, "Generated Key");

    if (verbose)
        diag("Test with keysize %u", expected_keysize);
    ok_or_goto(ccec_ctx_bitlen(full_key) == expected_keysize, "Generated correct keysize", errout);

    // SHA-1
    ok_or_goto(ecies_aes_gcm_crypt_decrypt(full_key, 16, ccsha1_di(), rng, options), "ECIES AES GCM SHA1, Msg length 16", errout);
    ok_or_goto(ecies_aes_gcm_crypt_decrypt(full_key, 1, ccsha1_di(), rng, options), "ECIES AES GCM SHA1, Msg length 1", errout);
    ok_or_goto(
        ecies_aes_gcm_crypt_decrypt(full_key, 4096, ccsha1_di(), rng, options), "ECIES AES GCM SHA1, Msg length 4096", errout);
    ok_or_goto(
        ecies_aes_gcm_crypt_decrypt(full_key, 4097, ccsha1_di(), rng, options), "ECIES AES GCM SHA1, Msg length 4097", errout);

    // SHA-256
    ok_or_goto(
        ecies_aes_gcm_crypt_decrypt(full_key, 16, ccsha256_di(), rng, options), "ECIES AES GCM SHA256, Msg length 16", errout);
    ok_or_goto(
        ecies_aes_gcm_crypt_decrypt(full_key, 1, ccsha256_di(), rng, options), "ECIES AES GCM SHA256, Msg length 1", errout);
    ok_or_goto(ecies_aes_gcm_crypt_decrypt(full_key, 4096, ccsha256_di(), rng, options),
               "ECIES AES GCM SHA256, Msg length 4096",
               errout);
    ok_or_goto(ecies_aes_gcm_crypt_decrypt(full_key, 4097, ccsha256_di(), rng, options),
               "ECIES AES GCM SHA256, Msg length 4097",
               errout);
    status = 1;
errout:
    ccec_full_ctx_clear_cp(cp, full_key);
    return status;
}

typedef int (*test_func_t)(const struct ccecies_vector *test, int test_counter);
static int ecies_gcm_vector_tests(test_func_t func, const struct ccecies_vector *test)
{
    int test_counter = 0;
    int test_status = 1;
    const struct ccecies_vector *current_test = &test[test_counter++];
    while (current_test->di != NULL && test_status) {
        struct ccecies_vector ct = *current_test;
        test_status = func(&ct,test_counter);
        ct.options &= ~(unsigned int)ECIES_EXPORT_PUB_STANDARD; // kill this option
        ct.options |= ECIES_EXPORT_PUB_COMPACT;   // add this option instead
        test_status &= func(&ct,test_counter);    // and redo the test
        current_test = &test[test_counter++];
    }
    return test_status;
}

static char *option_name(uint32_t option)
{
    if (ECIES_EXPORT_PUB_STANDARD == (option & ECIES_EXPORT_PUB_STANDARD)) {
        return "ECIES_EXPORT_PUB_STANDARD";
    } else if (ECIES_EXPORT_PUB_COMPACT == (option & ECIES_EXPORT_PUB_COMPACT)) {
        return "ECIES_EXPORT_PUB_COMPACT";
    } else
        return NULL;
}

static int options_test(struct ccrng_state *rng, uint32_t options)
{
    if (verbose)
        diag(option_name(options));

    ok(ecies_test(rng, 192, ccec_cp_192(), options), "ECIES with 192 bit EC Key");
    ok(ecies_test(rng, 224, ccec_cp_224(), options), "ECIES with 224 bit EC Key");
    ok(ecies_test(rng, 256, ccec_cp_256(), options), "ECIES with 256 bit EC Key");
    ok(ecies_test(rng, 384, ccec_cp_384(), options), "ECIES with 384 bit EC Key");
    ok(ecies_test(rng, 521, ccec_cp_521(), options), "ECIES with 521 bit EC Key");
    if (verbose)
        diag_linereturn();

    char buf[134 + 1];
    snprintf(buf, 133, "%s | ECIES_EPH_PUBKEY_IN_SHAREDINFO1", option_name(options));
    buf[133] = 0;
    if (verbose) {
        diag(buf);
    }
    ok(ecies_test(rng, 192, ccec_cp_192(), options | ECIES_EPH_PUBKEY_IN_SHAREDINFO1),
       "ECIES with 192 bit EC Key, public key in sharedInfo1");
    ok(ecies_test(rng, 224, ccec_cp_224(), options | ECIES_EPH_PUBKEY_IN_SHAREDINFO1),
       "ECIES with 224 bit EC Key, public key in sharedInfo1");
    ok(ecies_test(rng, 256, ccec_cp_256(), options | ECIES_EPH_PUBKEY_IN_SHAREDINFO1),
       "ECIES with 256 bit EC Key, public key in sharedInfo1");
    ok(ecies_test(rng, 384, ccec_cp_384(), options | ECIES_EPH_PUBKEY_IN_SHAREDINFO1),
       "ECIES with 384 bit EC Key, public key in sharedInfo1");
    ok(ecies_test(rng, 521, ccec_cp_521(), options | ECIES_EPH_PUBKEY_IN_SHAREDINFO1),
       "ECIES with 521 bit EC Key, public key in sharedInfo1");
    if (verbose) {
        diag_linereturn();
    }

    snprintf(buf, 134, "%s | ECIES_EPH_PUBKEY_AND_SHAREDINFO1", option_name(options));
    buf[134] = 0;
    if (verbose) {
        diag(buf);
    }
    ok(ecies_test(rng, 192, ccec_cp_192(), options | ECIES_EPH_PUBKEY_AND_SHAREDINFO1),
       "ECIES with 192 bit EC Key, public key in sharedInfo1");
    ok(ecies_test(rng, 224, ccec_cp_224(), options | ECIES_EPH_PUBKEY_AND_SHAREDINFO1),
       "ECIES with 224 bit EC Key, public key in sharedInfo1");
    ok(ecies_test(rng, 256, ccec_cp_256(), options | ECIES_EPH_PUBKEY_AND_SHAREDINFO1),
       "ECIES with 256 bit EC Key, public key in sharedInfo1");
    ok(ecies_test(rng, 384, ccec_cp_384(), options | ECIES_EPH_PUBKEY_AND_SHAREDINFO1),
       "ECIES with 384 bit EC Key, public key in sharedInfo1");
    ok(ecies_test(rng, 521, ccec_cp_521(), options | ECIES_EPH_PUBKEY_AND_SHAREDINFO1),
       "ECIES with 521 bit EC Key, public key in sharedInfo1");
    if (verbose) {
        diag_linereturn();
    }

    return 0;
}

static int options_conflict_test(struct ccrng_state *rng)
{
    const struct ccdigest_info *di = ccsha256_di();
    ccec_const_cp_t cp = ccec_cp_256();
    ccec_full_ctx_decl_cp(cp, key);

    uint32_t options = ECIES_EPH_PUBKEY_IN_SHAREDINFO1 | ECIES_EPH_PUBKEY_AND_SHAREDINFO1;

    size_t plaintext_len = 32;
    uint8_t plaintext[plaintext_len];
    ccrng_generate(rng, 32, plaintext);

    struct ccecies_gcm ecies_enc;
    struct ccecies_gcm ecies_dec;
    is(ccecies_encrypt_gcm_setup(&ecies_enc, di, rng, ccaes_gcm_encrypt_mode(), 16, 16, options), 0, "Conflict encrypt setup");
    is(ccecies_decrypt_gcm_setup(&ecies_dec, di, ccaes_gcm_decrypt_mode(), 16, 16, options), 0, "Conflict decrypt setup");

    ok_or_fail(ccec_generate_key_fips(cp, rng, key) == 0, "Generated Key");
    ccec_pub_ctx_t pub_key = ccec_ctx_pub(key);

    size_t ciphertext_len = ccecies_encrypt_gcm_ciphertext_size(pub_key, &ecies_enc, 32);
    uint8_t ciphertext[ciphertext_len];

    ok_or_fail(ccecies_encrypt_gcm(pub_key,
                                   &ecies_enc,
                                   plaintext_len,
                                   plaintext,
                                   sizeof(shared_info1),
                                   shared_info1,
                                   sizeof(shared_info2),
                                   shared_info2,
                                   &ciphertext_len,
                                   ciphertext) != 0,
               "Setting both ECIES_EPH_PUBKEY_IN_SHAREDINFO1 and "
               "ECIES_EPH_PUBKEY_AND_SHAREDINFO1 should fail");

    ok_or_fail(ccecies_decrypt_gcm(key,
                                   &ecies_dec,
                                   ciphertext_len,
                                   ciphertext,
                                   sizeof(shared_info1),
                                   NULL,
                                   sizeof(shared_info2),
                                   NULL,
                                   &plaintext_len,
                                   plaintext) != 0,
               "Setting both ECIES_EPH_PUBKEY_IN_SHAREDINFO1 and "
               "ECIES_EPH_PUBKEY_AND_SHAREDINFO1 should fail");
    ccec_full_ctx_clear_cp(cp, key);
    return 0;
}

int ccecies_tests(TM_UNUSED int argc, TM_UNUSED char *const *argv)
{
    struct ccrng_state *rng = global_test_rng;

    plan_tests(6961);

    if (verbose)
        diag("KATs");
    ok(ecies_gcm_vector_tests(ccecies_gcm_kat_test, ccecies_aes_gcm_vectors), "AES GCM KAT");

    if (verbose)
        diag("Negative tests");
    ok(ecies_gcm_vector_tests(ccecies_gcm_kat_negative_test, ccecies_aes_gcm_vectors), "AES GCM Negative tests");

    options_test(rng, ECIES_EXPORT_PUB_STANDARD);
    options_test(rng, ECIES_EXPORT_PUB_COMPACT);

    options_conflict_test(rng);

    return 0;
}

#endif
