/*
 * Copyright (c) 2020-2023 [Ribose Inc](https://www.ribose.com).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1.  Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 * 2.  Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <fstream>
#include <vector>
#include <string>

#include <rnp/rnp.h>
#include "rnp_tests.h"
#include "support.h"
#include "librepgp/stream-common.h"
#include "librepgp/stream-packet.h"
#include "librepgp/stream-sig.h"
#include <json.h>
#include <vector>
#include <string>
#include "file-utils.h"
#include <librepgp/stream-ctx.h>
#include "pgp-key.h"
#include "ffi-priv-types.h"

static bool
getpasscb_once(rnp_ffi_t        ffi,
               void *           app_ctx,
               rnp_key_handle_t key,
               const char *     pgp_context,
               char *           buf,
               size_t           buf_len)
{
    const char **pass = (const char **) app_ctx;
    if (!*pass) {
        return false;
    }
    size_t pass_len = strlen(*pass);
    if (pass_len >= buf_len) {
        return false;
    }
    memcpy(buf, *pass, pass_len);
    *pass = NULL;
    return true;
}

static bool
getpasscb_inc(rnp_ffi_t        ffi,
              void *           app_ctx,
              rnp_key_handle_t key,
              const char *     pgp_context,
              char *           buf,
              size_t           buf_len)
{
    int *       num = (int *) app_ctx;
    std::string pass = "pass" + std::to_string(*num);
    (*num)++;
    strncpy(buf, pass.c_str(), buf_len - 1);
    return true;
}

#define TBL_MAX_USERIDS 4
typedef struct key_tbl_t {
    const uint8_t *key_data;
    size_t         key_data_size;
    bool           secret;
    const char *   keyid;
    const char *   grip;
    const char *   userids[TBL_MAX_USERIDS + 1];
} key_tbl_t;

static void
tbl_getkeycb(rnp_ffi_t   ffi,
             void *      app_ctx,
             const char *identifier_type,
             const char *identifier,
             bool        secret)
{
    key_tbl_t *found = NULL;
    for (key_tbl_t *tbl = (key_tbl_t *) app_ctx; tbl && tbl->key_data && !found; tbl++) {
        if (tbl->secret != secret) {
            continue;
        }
        if (!strcmp(identifier_type, "keyid") && !strcmp(identifier, tbl->keyid)) {
            found = tbl;
            break;
        } else if (!strcmp(identifier_type, "grip") && !strcmp(identifier, tbl->grip)) {
            found = tbl;
            break;
        } else if (!strcmp(identifier_type, "userid")) {
            for (size_t i = 0; i < TBL_MAX_USERIDS; i++) {
                if (!strcmp(identifier, tbl->userids[i])) {
                    found = tbl;
                    break;
                }
            }
        }
    }
    if (found) {
        char *format = NULL;
        assert_rnp_success(
          rnp_detect_key_format(found->key_data, found->key_data_size, &format));
        assert_non_null(format);
        uint32_t    flags = secret ? RNP_LOAD_SAVE_SECRET_KEYS : RNP_LOAD_SAVE_PUBLIC_KEYS;
        rnp_input_t input = NULL;
        assert_rnp_success(
          rnp_input_from_memory(&input, found->key_data, found->key_data_size, true));
        assert_non_null(input);
        assert_rnp_success(rnp_load_keys(ffi, format, input, flags));
        free(format);
        assert_rnp_success(rnp_input_destroy(input));
        input = NULL;
    }
}

TEST_F(rnp_tests, test_ffi_encrypt_pass)
{
    rnp_ffi_t        ffi = NULL;
    rnp_input_t      input = NULL;
    rnp_output_t     output = NULL;
    rnp_op_encrypt_t op = NULL;
    const char *     plaintext = "data1";

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));

    // load our keyrings
    assert_true(
      load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));

    // write out some data
    str_to_file("plaintext", plaintext);
    // create input+output w/ bad paths (should fail)
    input = NULL;
    assert_rnp_failure(rnp_input_from_path(&input, "noexist"));
    assert_null(input);
    assert_rnp_failure(rnp_output_to_path(&output, ""));
    assert_null(output);

    // create input+output
    assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
    assert_non_null(output);
    // create encrypt operation
    assert_rnp_failure(rnp_op_encrypt_create(NULL, ffi, input, output));
    assert_rnp_failure(rnp_op_encrypt_create(&op, NULL, input, output));
    assert_rnp_failure(rnp_op_encrypt_create(&op, ffi, NULL, output));
    assert_rnp_failure(rnp_op_encrypt_create(&op, ffi, input, NULL));
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    // add password (using all defaults)
    assert_rnp_failure(rnp_op_encrypt_add_password(NULL, "pass1", NULL, 0, NULL));
    assert_rnp_failure(rnp_op_encrypt_add_password(op, "", NULL, 0, NULL));
    assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass1", "WRONG", 0, NULL));
    assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass1", NULL, 0, "WRONG"));
    assert_rnp_success(rnp_op_encrypt_add_password(op, "pass1", NULL, 0, NULL));
    // add password
    if (!sm2_enabled() && !twofish_enabled()) {
        assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
        assert_rnp_failure(
          rnp_op_encrypt_add_password(op, "pass2", "SHA256", 12345, "TWOFISH"));
        const char *alg = blowfish_enabled() ? "BLOWFISH" : "AES256";
        assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SHA256", 12345, alg));
    } else if (!sm2_enabled() && twofish_enabled()) {
        assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
        assert_rnp_success(
          rnp_op_encrypt_add_password(op, "pass2", "SHA256", 12345, "TWOFISH"));
    } else if (sm2_enabled() && !twofish_enabled()) {
        assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
        const char *alg = blowfish_enabled() ? "BLOWFISH" : "AES256";
        assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, alg));
    } else {
        assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
    }
    // set the data encryption cipher
    assert_rnp_failure(rnp_op_encrypt_set_cipher(NULL, "CAST5"));
    assert_rnp_failure(rnp_op_encrypt_set_cipher(op, NULL));
    assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "WRONG"));
    if (cast5_enabled()) {
        assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
    } else {
        assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
        assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
    }
    // execute the operation
    assert_rnp_success(rnp_op_encrypt_execute(op));

    // make sure the output file was created
    assert_true(rnp_file_exists("encrypted"));

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_encrypt_destroy(op));
    op = NULL;

    /* decrypt */

    // decrypt (no pass provider, should fail)
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
    assert_rnp_failure(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;

    // decrypt (wrong pass, should fail)
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    const char *pass = "wrong1";
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_once, &pass));
    assert_rnp_failure(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;

    // decrypt (pass1)
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass1"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;
    // compare the decrypted file
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    unlink("decrypted");

    // decrypt (pass2)
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass2"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;
    // compare the decrypted file
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    // final cleanup
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_encrypt_pass_provider)
{
    rnp_ffi_t        ffi = NULL;
    rnp_input_t      input = NULL;
    rnp_output_t     output = NULL;
    rnp_op_encrypt_t op = NULL;
    const char *plaintext = "Data encrypted with password provided via password provider.";

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // write out some data
    str_to_file("plaintext", plaintext);
    // create input + output
    assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
    assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
    // create encrypt operation
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    // add password with NULL password provider set - should fail
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
    assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, NULL, 0, NULL));
    // add password with password provider set.
    int pswdnum = 1;
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_inc, &pswdnum));
    assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, NULL, 0, NULL));
    // add another password with different encryption parameters
    if (!sm2_enabled() && !twofish_enabled()) {
        assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
        assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SHA256", 12345, "TWOFISH"));
        assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, NULL, 12345, NULL));
    } else if (!sm2_enabled() && twofish_enabled()) {
        assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
        assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, "SHA256", 12345, "TWOFISH"));
    } else if (sm2_enabled() && !twofish_enabled()) {
        assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
        assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, NULL));
    } else {
        assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
    }
    // set the data encryption cipher
    assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAMELLIA256"));
    // execute the operation
    assert_rnp_success(rnp_op_encrypt_execute(op));
    // make sure the output file was created
    assert_true(rnp_file_exists("encrypted"));

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_encrypt_destroy(op));
    op = NULL;

    /* decrypt with pass1 */
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass1"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    unlink("decrypted");

    /* decrypt with pass2 via provider */
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    pswdnum = 2;
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_inc, &pswdnum));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    unlink("decrypted");

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_encrypt_set_cipher)
{
    /* setup FFI */
    rnp_ffi_t ffi = NULL;
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    /* create input + output */
    rnp_input_t input = NULL;
    const char *plaintext = "Data encrypted with password using different CEK/KEK.";
    assert_rnp_success(
      rnp_input_from_memory(&input, (const uint8_t *) plaintext, strlen(plaintext), false));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
    /* create encrypt operation */
    rnp_op_encrypt_t op = NULL;
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    /* use different sym algos */
    assert_rnp_success(rnp_op_encrypt_add_password(op, "password1", NULL, 0, "AES192"));
    assert_rnp_success(rnp_op_encrypt_add_password(op, "password2", NULL, 0, "AES128"));
    assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
    /* execute the operation */
    assert_rnp_success(rnp_op_encrypt_execute(op));
    assert_true(rnp_file_exists("encrypted"));
    /* cleanup */
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_op_encrypt_destroy(op));
    /* decrypt with password1 */
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password1"));
    rnp_op_verify_t verify;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    /* Check protection info */
    char *mode = NULL;
    char *cipher = NULL;
    bool  valid = false;
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "cfb-mdc");
    assert_string_equal(cipher, "AES256");
    assert_true(valid);
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    /* Check SESKs */
    size_t count = 0;
    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
    assert_int_equal(count, 2);
    /* First SESK: AES192 */
    rnp_symenc_handle_t symenc = NULL;
    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
    char *aalg = NULL;
    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
    assert_string_equal(aalg, "None");
    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
    assert_string_equal(cipher, "AES192");
    rnp_buffer_destroy(aalg);
    rnp_buffer_destroy(cipher);
    /* Second SESK: AES128 */
    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
    assert_string_equal(aalg, "None");
    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
    assert_string_equal(cipher, "AES128");
    rnp_buffer_destroy(aalg);
    rnp_buffer_destroy(cipher);
    unlink("decrypted");
    unlink("encrypted");
    rnp_op_verify_destroy(verify);

    /* Now use AEAD */
    assert_rnp_success(
      rnp_input_from_memory(&input, (const uint8_t *) plaintext, strlen(plaintext), false));
    assert_rnp_success(rnp_output_to_path(&output, "encrypted-aead"));
    /* create encrypt operation */
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    /* use different sym algos */
    assert_rnp_success(rnp_op_encrypt_add_password(op, "password1", NULL, 0, "AES256"));
    assert_rnp_success(rnp_op_encrypt_add_password(op, "password2", NULL, 0, "AES192"));
    assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES128"));
    assert_rnp_success(rnp_op_encrypt_set_aead(op, "OCB"));
    /* execute the operation */
    assert_rnp_success(rnp_op_encrypt_execute(op));
    assert_true(rnp_file_exists("encrypted-aead"));
    /* cleanup */
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_op_encrypt_destroy(op));
    /* decrypt with password2 */
    assert_rnp_success(rnp_input_from_path(&input, "encrypted-aead"));
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password2"));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    /* Check protection info */
    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
    assert_string_equal(mode, "aead-ocb");
    assert_string_equal(cipher, "AES128");
    assert_true(valid);
    rnp_buffer_destroy(mode);
    rnp_buffer_destroy(cipher);
    /* Check SESKs */
    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
    assert_int_equal(count, 2);
    /* First SESK: AES192 */
    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
    assert_string_equal(aalg, "OCB");
    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
    assert_string_equal(cipher, "AES256");
    rnp_buffer_destroy(aalg);
    rnp_buffer_destroy(cipher);
    /* Second SESK: AES128 */
    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
    assert_string_equal(aalg, "OCB");
    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
    assert_string_equal(cipher, "AES192");
    rnp_buffer_destroy(aalg);
    rnp_buffer_destroy(cipher);
    unlink("decrypted");
    unlink("encrypted-aead");
    rnp_op_verify_destroy(verify);

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_encrypt_pk)
{
    rnp_ffi_t        ffi = NULL;
    rnp_input_t      input = NULL;
    rnp_output_t     output = NULL;
    rnp_op_encrypt_t op = NULL;
    const char *     plaintext = "data1";

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));

    // load our keyrings
    assert_true(
      load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));

    // write out some data
    str_to_file("plaintext", plaintext);
    // create input+output
    assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
    assert_non_null(output);
    // create encrypt operation
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    // add recipients
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
    assert_rnp_failure(rnp_op_encrypt_add_recipient(NULL, key));
    assert_rnp_failure(rnp_op_encrypt_add_recipient(op, NULL));
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    rnp_key_handle_destroy(key);
    key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    rnp_key_handle_destroy(key);
    key = NULL;
    // set the data encryption cipher
    if (cast5_enabled()) {
        assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
    } else {
        assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
        assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
    }
    // execute the operation
    assert_rnp_success(rnp_op_encrypt_execute(op));

    // make sure the output file was created
    assert_true(rnp_file_exists("encrypted"));

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_encrypt_destroy(op));
    op = NULL;

    /* decrypt */

    // decrypt (no pass provider, should fail)
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
    assert_rnp_failure(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;

    // decrypt (wrong pass, should fail)
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    const char *pass = "wrong1";
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_once, &pass));
    assert_rnp_failure(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;

    // decrypt
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;
    // read in the decrypted file
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    // final cleanup
    rnp_ffi_destroy(ffi);
}

static bool
first_key_password_provider(rnp_ffi_t        ffi,
                            void *           app_ctx,
                            rnp_key_handle_t key,
                            const char *     pgp_context,
                            char *           buf,
                            size_t           buf_len)
{
    if (!key) {
        throw std::invalid_argument("key");
    }
    char *keyid = NULL;
    rnp_key_get_keyid(key, &keyid);
    if (strcmp(keyid, "8A05B89FAD5ADED1")) {
        throw std::invalid_argument("keyid");
    }
    rnp_buffer_destroy(keyid);
    return false;
}

TEST_F(rnp_tests, test_ffi_decrypt_pk_unlocked)
{
    rnp_ffi_t        ffi = NULL;
    rnp_input_t      input = NULL;
    rnp_output_t     output = NULL;
    rnp_op_encrypt_t op = NULL;
    const char *     plaintext = "data1";

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));

    // load our keyrings
    assert_true(
      load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));

    // write out some data
    str_to_file("plaintext", plaintext);
    // create input+output
    assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
    assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
    // create encrypt operation
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    // add recipients
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    rnp_key_handle_destroy(key);
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    rnp_key_handle_destroy(key);
    // execute the operation
    assert_rnp_success(rnp_op_encrypt_execute(op));

    // make sure the output file was created
    assert_true(rnp_file_exists("encrypted"));

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_op_encrypt_destroy(op));

    /* decrypt (unlocked first key, no pass provider) */
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    rnp_key_handle_t defkey = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
    assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
    assert_non_null(defkey);
    assert_rnp_success(rnp_key_unlock(defkey, "password"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    assert_rnp_success(rnp_key_lock(defkey));
    rnp_key_handle_destroy(key);
    rnp_key_handle_destroy(defkey);
    // cleanup
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    assert_int_equal(unlink("decrypted"), 0);

    /* decrypt (unlocked second key, no pass provider) */
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
    assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
    assert_non_null(defkey);
    assert_rnp_success(rnp_key_unlock(defkey, "password"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    assert_rnp_success(rnp_key_lock(defkey));
    rnp_key_handle_destroy(key);
    rnp_key_handle_destroy(defkey);
    // cleanup
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    assert_int_equal(unlink("decrypted"), 0);

    /* decrypt (unlocked first key, pass provider should not be called) */
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, ffi_asserting_password_provider, NULL));
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
    assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
    assert_non_null(defkey);
    assert_rnp_success(rnp_key_unlock(defkey, "password"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    assert_rnp_success(rnp_key_lock(defkey));
    rnp_key_handle_destroy(key);
    rnp_key_handle_destroy(defkey);
    // cleanup
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    assert_int_equal(unlink("decrypted"), 0);

    /* decrypt (unlocked second key, pass provider should not be called) */
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, first_key_password_provider, NULL));
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
    assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
    assert_non_null(defkey);
    assert_rnp_success(rnp_key_unlock(defkey, "password"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    assert_rnp_success(rnp_key_lock(defkey));
    rnp_key_handle_destroy(key);
    rnp_key_handle_destroy(defkey);
    // cleanup
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    assert_int_equal(unlink("decrypted"), 0);

    // final cleanup
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_encrypt_pk_key_provider)
{
    rnp_ffi_t        ffi = NULL;
    rnp_input_t      input = NULL;
    rnp_output_t     output = NULL;
    rnp_op_encrypt_t op = NULL;
    const char *     plaintext = "data1";
    uint8_t *        primary_sec_key_data = NULL;
    size_t           primary_sec_size = 0;
    uint8_t *        sub_sec_key_data = NULL;
    size_t           sub_sec_size = 0;

    /* first, let's generate some encrypted data */
    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_non_null(ffi);
    // load our keyrings
    assert_true(
      load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));
    // write out some data
    str_to_file("plaintext", plaintext);
    // create input+output
    assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
    assert_non_null(output);
    // create encrypt operation
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    // add recipient 1
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
    assert_non_null(key);
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    // cleanup
    assert_rnp_success(rnp_key_handle_destroy(key));
    key = NULL;
    // add recipient 2
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
    assert_non_null(key);
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    // save the primary key data for later
    assert_rnp_success(rnp_get_secret_key_data(key, &primary_sec_key_data, &primary_sec_size));
    assert_non_null(primary_sec_key_data);
    assert_rnp_success(rnp_key_handle_destroy(key));
    key = NULL;
    // save the appropriate encrypting subkey for the key provider to use during decryption
    // later
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "8A05B89FAD5ADED1", &key));
    assert_non_null(key);
    assert_rnp_success(rnp_get_secret_key_data(key, &sub_sec_key_data, &sub_sec_size));
    assert_non_null(sub_sec_key_data);
    // cleanup
    assert_rnp_success(rnp_key_handle_destroy(key));
    key = NULL;
    // set the data encryption cipher
    if (cast5_enabled()) {
        assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
    } else {
        assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
        assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
    }
    // execute the operation
    assert_rnp_success(rnp_op_encrypt_execute(op));
    // make sure the output file was created
    assert_true(rnp_file_exists("encrypted"));
    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_encrypt_destroy(op));
    op = NULL;
    assert_rnp_success(rnp_ffi_destroy(ffi));
    ffi = NULL;

    /* decrypt */
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    // load the primary
    input = NULL;
    assert_rnp_success(
      rnp_input_from_memory(&input, primary_sec_key_data, primary_sec_size, true));
    assert_non_null(input);
    assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS));
    rnp_input_destroy(input);
    input = NULL;

    // decrypt (no key to decrypt, should fail)
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    assert_int_equal(RNP_ERROR_NO_SUITABLE_KEY, rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;

    // key_data key_data_size secret keyid grip userids
    const key_tbl_t keydb[] = {
      {sub_sec_key_data, sub_sec_size, true, "8A05B89FAD5ADED1", NULL, {NULL}}, {0}};

    // decrypt
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    assert_rnp_success(rnp_ffi_set_key_provider(ffi, tbl_getkeycb, (void *) keydb));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;
    // compare the decrypted file
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    // final cleanup
    rnp_ffi_destroy(ffi);
    free(sub_sec_key_data);
    free(primary_sec_key_data);
}

TEST_F(rnp_tests, test_ffi_encrypt_and_sign)
{
    rnp_ffi_t               ffi = NULL;
    rnp_input_t             input = NULL;
    rnp_output_t            output = NULL;
    rnp_op_encrypt_t        op = NULL;
    rnp_op_sign_signature_t signsig = NULL;
    const char *            plaintext = "data1";
    rnp_key_handle_t        key = NULL;
    const uint32_t          issued = 1516211899;   // Unix epoch, nowish
    const uint32_t          expires = 1000000000;  // expires later
    const uint32_t          issued2 = 1516211900;  // Unix epoch, nowish
    const uint32_t          expires2 = 2000000000; // expires later

    // setup FFI
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));

    // load our keyrings
    assert_true(
      load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));

    // write out some data
    str_to_file("plaintext", plaintext);
    // create input+output
    assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
    assert_non_null(output);
    // create encrypt operation
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    // add recipients
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    rnp_key_handle_destroy(key);
    key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    rnp_key_handle_destroy(key);
    key = NULL;
    // set the data encryption cipher
    if (cast5_enabled()) {
        assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
    } else {
        assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
        assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
    }
    // enable armoring
    assert_rnp_failure(rnp_op_encrypt_set_armor(NULL, true));
    assert_rnp_success(rnp_op_encrypt_set_armor(op, true));
    // add signature
    assert_rnp_failure(rnp_op_encrypt_set_hash(NULL, "SHA256"));
    assert_rnp_failure(rnp_op_encrypt_set_hash(op, NULL));
    assert_rnp_failure(rnp_op_encrypt_set_hash(op, "WRONG"));
    assert_rnp_success(rnp_op_encrypt_set_hash(op, "SHA1"));
    assert_rnp_failure(rnp_op_encrypt_set_creation_time(NULL, 0));
    assert_rnp_success(rnp_op_encrypt_set_creation_time(op, 0));
    assert_rnp_failure(rnp_op_encrypt_set_expiration_time(NULL, 0));
    assert_rnp_success(rnp_op_encrypt_set_expiration_time(op, 0));
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
    assert_rnp_failure(rnp_op_encrypt_add_signature(NULL, key, NULL));
    assert_rnp_failure(rnp_op_encrypt_add_signature(op, NULL, NULL));
    assert_rnp_success(rnp_op_encrypt_add_signature(op, key, NULL));
    rnp_key_handle_destroy(key);
    key = NULL;
    // attempt to add signature from the public key
    assert_true(import_pub_keys(ffi, "data/test_stream_key_load/ecc-p256-pub.asc"));
    assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &key));
    assert_rnp_failure(rnp_op_encrypt_add_signature(op, key, &signsig));
    rnp_key_handle_destroy(key);
    key = NULL;
    // attempt to add signature by the offline secret key
    assert_true(
      import_pub_keys(ffi, "data/test_key_edge_cases/alice-s2k-101-no-sign-sub.pgp"));
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
    assert_rnp_failure(rnp_op_encrypt_add_signature(op, key, &signsig));
    rnp_key_handle_destroy(key);
    key = NULL;
    // add second signature with different hash/issued/expiration
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid2", &key));
    assert_rnp_success(rnp_op_encrypt_add_signature(op, key, &signsig));
    assert_rnp_success(rnp_op_sign_signature_set_creation_time(signsig, issued2));
    assert_rnp_success(rnp_op_sign_signature_set_expiration_time(signsig, expires2));
    assert_rnp_failure(rnp_op_sign_signature_set_hash(signsig, NULL));
    assert_rnp_failure(rnp_op_sign_signature_set_hash(NULL, "SHA512"));
    assert_rnp_failure(rnp_op_sign_signature_set_hash(signsig, "UNKNOWN"));
    assert_rnp_success(rnp_op_sign_signature_set_hash(signsig, "SHA512"));
    rnp_key_handle_destroy(key);
    key = NULL;
    // set default sig parameters after the signature is added - those should be picked up
    assert_rnp_success(rnp_op_encrypt_set_hash(op, "SHA256"));
    assert_rnp_success(rnp_op_encrypt_set_creation_time(op, issued));
    assert_rnp_success(rnp_op_encrypt_set_expiration_time(op, expires));
    // execute the operation
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_op_encrypt_execute(op));

    // make sure the output file was created
    assert_true(rnp_file_exists("encrypted"));

    // check whether keys are locked
    rnp_identifier_iterator_t it = NULL;
    assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "fingerprint"));
    const char *fprint = NULL;
    while (!rnp_identifier_iterator_next(it, &fprint)) {
        if (!fprint) {
            break;
        }
        SCOPED_TRACE(fprint);
        rnp_key_handle_t skey = NULL;
        assert_rnp_success(rnp_locate_key(ffi, "fingerprint", fprint, &skey));
        bool secret = true;
        assert_rnp_success(rnp_key_have_secret(skey, &secret));
        if (secret) {
            bool locked = false;
            assert_rnp_success(rnp_key_is_locked(skey, &locked));
            assert_true(locked);
        }
        rnp_key_handle_destroy(skey);
    }
    rnp_identifier_iterator_destroy(it);

    // cleanup
    assert_rnp_success(rnp_input_destroy(input));
    input = NULL;
    assert_rnp_success(rnp_output_destroy(output));
    output = NULL;
    assert_rnp_success(rnp_op_encrypt_destroy(op));
    op = NULL;

    /* decrypt */

    // decrypt (no pass provider, should fail)
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
    assert_rnp_failure(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;

    // decrypt (wrong pass, should fail)
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    const char *pass = "wrong1";
    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_once, &pass));
    assert_rnp_failure(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;

    // decrypt
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_non_null(output);
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    // cleanup
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;
    // compare the decrypted file
    assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
    // verify and check signatures
    rnp_op_verify_t verify;
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_non_null(input);
    assert_rnp_success(rnp_output_to_path(&output, "verified"));
    assert_non_null(output);
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));

    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    // check signatures
    rnp_op_verify_signature_t sig;
    size_t                    sig_count;
    uint32_t                  sig_create;
    uint32_t                  sig_expires;
    char *                    hname = NULL;

    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
    assert_int_equal(sig_count, 2);
    // signature 1
    assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
    assert_rnp_success(rnp_op_verify_signature_get_status(sig));
    assert_rnp_success(rnp_op_verify_signature_get_times(sig, &sig_create, &sig_expires));
    assert_int_equal(sig_create, issued);
    assert_int_equal(sig_expires, expires);
    assert_rnp_success(rnp_op_verify_signature_get_hash(sig, &hname));
    assert_string_equal(hname, "SHA256");
    rnp_buffer_destroy(hname);
    hname = NULL;
    // signature 2
    assert_rnp_success(rnp_op_verify_get_signature_at(verify, 1, &sig));
    assert_rnp_success(rnp_op_verify_signature_get_status(sig));
    assert_rnp_success(rnp_op_verify_signature_get_times(sig, &sig_create, &sig_expires));
    assert_int_equal(sig_create, issued2);
    assert_int_equal(sig_expires, expires2);
    assert_rnp_success(rnp_op_verify_signature_get_hash(sig, &hname));
    assert_string_equal(hname, "SHA512");
    rnp_buffer_destroy(hname);
    hname = NULL;
    // make sure keys are locked
    assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "fingerprint"));
    while (!rnp_identifier_iterator_next(it, &fprint)) {
        if (!fprint) {
            break;
        }
        SCOPED_TRACE(fprint);
        rnp_key_handle_t skey = NULL;
        assert_rnp_success(rnp_locate_key(ffi, "fingerprint", fprint, &skey));
        bool secret = true;
        assert_rnp_success(rnp_key_have_secret(skey, &secret));
        if (secret) {
            bool locked = false;
            assert_rnp_success(rnp_key_is_locked(skey, &locked));
            assert_true(locked);
        }
        rnp_key_handle_destroy(skey);
    }
    rnp_identifier_iterator_destroy(it);
    // cleanup
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    input = NULL;
    rnp_output_destroy(output);
    output = NULL;
    // compare the decrypted file
    assert_string_equal(file_to_str("verified").c_str(), plaintext);
    // final cleanup
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_encrypt_pk_subkey_selection)
{
    rnp_ffi_t        ffi = NULL;
    rnp_input_t      input = NULL;
    rnp_output_t     output = NULL;
    rnp_op_encrypt_t op = NULL;
    const char *     plaintext = "data1";

    /* check whether a latest subkey is selected for encryption */
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));

    /* case 1: three encryption subkeys, second expired, third has later creation time */
    assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/key0-sub02.pgp"));

    assert_rnp_success(
      rnp_input_from_memory(&input, (uint8_t *) plaintext, strlen(plaintext), false));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    /* create encrypt operation, add recipient and execute */
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid0", &key));
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    rnp_key_handle_destroy(key);
    assert_rnp_success(rnp_op_encrypt_execute(op));
    /* get output */
    uint8_t *buf = NULL;
    size_t   len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, true));
    assert_true(buf && len);
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    rnp_op_encrypt_destroy(op);
    /* decrypt */
    assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
    assert_rnp_success(rnp_input_from_memory(&input, buf, len, true));
    rnp_buffer_destroy(buf);
    assert_rnp_success(rnp_output_to_memory(&output, 0));

    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));

    /* check whether we used correct subkey */
    size_t count = 0;
    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
    assert_int_equal(count, 1);
    rnp_recipient_handle_t recipient = NULL;
    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
    assert_non_null(recipient);
    char *keyid = NULL;
    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
    assert_non_null(keyid);
    assert_string_equal(keyid, "8A05B89FAD5ADED1");
    rnp_buffer_destroy(keyid);

    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* case 2: only subkeys 1-2, make sure that latest but expired subkey is not selected */
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
    assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/key0-sub01.pgp"));
    assert_rnp_success(
      rnp_input_from_memory(&input, (uint8_t *) plaintext, strlen(plaintext), false));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    /* create encrypt operation, add recipient and execute */
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    rnp_key_handle_destroy(key);
    assert_rnp_success(rnp_op_encrypt_execute(op));
    /* get output */
    buf = NULL;
    len = 0;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, true));
    assert_true(buf && len);
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    rnp_op_encrypt_destroy(op);
    /* decrypt */
    assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
    assert_rnp_success(rnp_input_from_memory(&input, buf, len, true));
    rnp_buffer_destroy(buf);
    assert_rnp_success(rnp_output_to_memory(&output, 0));

    verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));

    /* check whether we used correct subkey */
    count = 0;
    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
    assert_int_equal(count, 1);
    recipient = NULL;
    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
    assert_non_null(recipient);
    keyid = NULL;
    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
    assert_non_null(keyid);
    assert_string_equal(keyid, "1ED63EE56FADC34D");
    rnp_buffer_destroy(keyid);

    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    /* case 3: only expired subkey, make sure encryption operation fails */
    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
    assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/key0-sub1.pgp"));

    assert_rnp_success(
      rnp_input_from_memory(&input, (uint8_t *) plaintext, strlen(plaintext), false));
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    /* create encrypt operation, add recipient and execute */
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
    assert_int_equal(rnp_op_encrypt_add_recipient(op, key), RNP_ERROR_NO_SUITABLE_KEY);
    rnp_key_handle_destroy(key);
    assert_rnp_failure(rnp_op_encrypt_execute(op));
    rnp_op_encrypt_destroy(op);
    rnp_input_destroy(input);
    rnp_output_destroy(output);

    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_decrypt_small_rsa)
{
    rnp_ffi_t   ffi = NULL;
    const char *plaintext = "data1";

    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_true(import_all_keys(ffi, "data/test_key_validity/rsa_key_small_sig-sec.asc"));
    rnp_input_t input = NULL;
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/data.enc.small-rsa"));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_memory(&output, 0));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    size_t   len = 0;
    uint8_t *buf = NULL;
    assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
    assert_int_equal(len, 5);
    assert_int_equal(memcmp(plaintext, buf, 5), 0);
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_decrypt_small_eg)
{
    /* make sure unlock and decrypt fails with invalid key */
    rnp_ffi_t ffi = NULL;
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_true(
      import_all_keys(ffi, "data/test_key_edge_cases/key-eg-small-subgroup-sec.pgp"));
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "3b8dda452b9f69b4", &key));
    assert_non_null(key);
    /* key is not encrypted */
    assert_rnp_success(rnp_key_unlock(key, NULL));
    rnp_key_handle_destroy(key);
    rnp_input_t input = NULL;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-eg-bad"));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_failure(rnp_decrypt(ffi, input, output));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    rnp_ffi_destroy(ffi);
    /* make sure unlock and decrypt fails with invalid encrypted key */
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_true(
      import_all_keys(ffi, "data/test_key_edge_cases/key-eg-small-subgroup-sec-enc.pgp"));
    assert_rnp_success(rnp_locate_key(ffi, "keyid", "3b072c3bb2d1a8b2", &key));
    assert_non_null(key);
    assert_rnp_success(rnp_key_unlock(key, "password"));
    assert_rnp_success(rnp_key_lock(key));
    rnp_key_handle_destroy(key);
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-eg-bad2"));
    assert_rnp_success(rnp_output_to_null(&output));
    assert_rnp_failure(rnp_decrypt(ffi, input, output));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_encrypt_no_wrap)
{
    rnp_ffi_t ffi = NULL;
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_true(
      load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));

    rnp_input_t input = NULL;
    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed"));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
    rnp_op_encrypt_t op = NULL;
    assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
    assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
    rnp_key_handle_destroy(key);
    /* set nowrap flag */
    assert_rnp_failure(rnp_op_encrypt_set_flags(NULL, RNP_ENCRYPT_NOWRAP));
    assert_rnp_failure(rnp_op_encrypt_set_flags(op, 17));
    assert_rnp_success(rnp_op_encrypt_set_flags(op, RNP_ENCRYPT_NOWRAP));
    assert_rnp_success(rnp_op_encrypt_execute(op));

    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    assert_rnp_success(rnp_op_encrypt_destroy(op));

    /* decrypt via rnp_decrypt() */
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
    assert_rnp_success(
      rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
    assert_rnp_success(rnp_decrypt(ffi, input, output));
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    assert_string_equal(file_to_str("decrypted").c_str(),
                        file_to_str("data/test_messages/message.txt").c_str());
    unlink("decrypted");

    /* decrypt and verify signatures */
    rnp_op_verify_t verify;
    assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
    assert_rnp_success(rnp_output_to_path(&output, "verified"));
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    /* check signature */
    rnp_op_verify_signature_t sig;
    size_t                    sig_count;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
    assert_int_equal(sig_count, 1);
    assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
    assert_rnp_success(rnp_op_verify_signature_get_status(sig));
    assert_rnp_success(rnp_input_destroy(input));
    assert_rnp_success(rnp_output_destroy(output));
    rnp_op_verify_destroy(verify);
    assert_string_equal(file_to_str("verified").c_str(),
                        file_to_str("data/test_messages/message.txt").c_str());
    unlink("verified");

    // final cleanup
    rnp_ffi_destroy(ffi);
}

TEST_F(rnp_tests, test_ffi_mimemode_signature)
{
    rnp_ffi_t ffi = NULL;
    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
    assert_true(import_pub_keys(ffi, "data/test_stream_key_load/ecc-25519-pub.asc"));

    rnp_input_t input = NULL;
    assert_rnp_success(
      rnp_input_from_path(&input, "data/test_messages/message.txt.signed-mimemode"));
    rnp_output_t output = NULL;
    assert_rnp_success(rnp_output_to_null(&output));
    rnp_op_verify_t verify = NULL;
    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
    assert_rnp_success(rnp_op_verify_execute(verify));
    size_t sigcount = 255;
    assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
    assert_int_equal(sigcount, 1);
    rnp_op_verify_signature_t sig = NULL;
    assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
    assert_rnp_success(rnp_op_verify_signature_get_status(sig));
    rnp_op_verify_destroy(verify);
    rnp_input_destroy(input);
    rnp_output_destroy(output);
    rnp_ffi_destroy(ffi);
}
