/*
 * Copyright (c) 2017-2019 [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 "../librekey/key_store_pgp.h"
#include "../librepgp/stream-ctx.h"
#include "pgp-key.h"
#include "ffi-priv-types.h"
#include "rnp_tests.h"
#include "support.h"
#include <fstream>

TEST_F(rnp_tests, test_key_unlock_pgp)
{
    cli_rnp_t               rnp = {};
    const char *            data = "my test data";
    pgp_password_provider_t provider = {0};
    static const char *     keyids[] = {"7bc6709b15c23a4a", // primary
                                   "1ed63ee56fadc34d",
                                   "1d7e8a5393c997a8",
                                   "8a05b89fad5aded1",
                                   "2fcadf05ffa501bb", // primary
                                   "54505a936a4a970e",
                                   "326ef111425d14a5"};

    assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, "data/keyrings/1/", NULL));
    assert_true(rnp.load_keyrings(true));

    for (size_t i = 0; i < ARRAY_SIZE(keyids); i++) {
        rnp_key_handle_t handle = NULL;
        assert_rnp_success(rnp_locate_key(rnp.ffi, "keyid", keyids[i], &handle));
        assert_non_null(handle);
        bool locked = false;
        assert_rnp_success(rnp_key_is_locked(handle, &locked));
        // all keys in this keyring are encrypted and thus should be locked initially
        assert_true(locked);
        rnp_key_handle_destroy(handle);
    }

    std::ofstream out("dummyfile.dat");
    out << data;
    out.close();

    // try signing with a failing password provider (should fail)
    assert_rnp_success(
      rnp_ffi_set_pass_provider(rnp.ffi, ffi_failing_password_provider, NULL));
    rnp_cfg &cfg = rnp.cfg();
    cfg.load_defaults();
    cfg.set_bool(CFG_SIGN_NEEDED, true);
    cfg.set_str(CFG_HASH, "SHA256");
    cfg.set_int(CFG_ZLEVEL, 0);
    cfg.set_str(CFG_INFILE, "dummyfile.dat");
    cfg.set_str(CFG_OUTFILE, "dummyfile.dat.pgp");
    cfg.add_str(CFG_SIGNERS, keyids[0]);
    assert_false(cli_rnp_protect_file(&rnp));

    // grab the signing key to unlock
    rnp_key_handle_t key = NULL;
    assert_rnp_success(rnp_locate_key(rnp.ffi, "keyid", keyids[0], &key));
    assert_non_null(key);
    char *alg = NULL;
    // confirm that this key is indeed RSA first
    assert_rnp_success(rnp_key_get_alg(key, &alg));
    assert_int_equal(strcmp(alg, "RSA"), 0);
    rnp_buffer_destroy(alg);

    // confirm the secret MPIs are NULL
    assert_true(mpi_empty(key->sec->material().rsa.d));
    assert_true(mpi_empty(key->sec->material().rsa.p));
    assert_true(mpi_empty(key->sec->material().rsa.q));
    assert_true(mpi_empty(key->sec->material().rsa.u));

    // try to unlock with a failing password provider
    provider.callback = failing_password_callback;
    provider.userdata = NULL;
    assert_false(key->sec->unlock(provider));
    bool locked = false;
    assert_rnp_success(rnp_key_is_locked(key, &locked));
    assert_true(locked);

    // try to unlock with an incorrect password
    provider.callback = string_copy_password_callback;
    provider.userdata = (void *) "badpass";
    assert_false(key->sec->unlock(provider));
    assert_rnp_success(rnp_key_is_locked(key, &locked));
    assert_true(locked);

    // unlock the signing key
    provider.callback = string_copy_password_callback;
    provider.userdata = (void *) "password";
    assert_true(key->sec->unlock(provider));
    assert_rnp_success(rnp_key_is_locked(key, &locked));
    assert_false(locked);

    // confirm the secret MPIs are now filled in
    assert_false(mpi_empty(key->sec->material().rsa.d));
    assert_false(mpi_empty(key->sec->material().rsa.p));
    assert_false(mpi_empty(key->sec->material().rsa.q));
    assert_false(mpi_empty(key->sec->material().rsa.u));

    // now the signing key is unlocked, confirm that no password is required for signing
    assert_rnp_success(
      rnp_ffi_set_pass_provider(rnp.ffi, ffi_asserting_password_provider, NULL));
    cfg.clear();
    cfg.load_defaults();
    cfg.set_bool(CFG_SIGN_NEEDED, true);
    cfg.set_str(CFG_HASH, "SHA256");
    cfg.set_int(CFG_ZLEVEL, 0);
    cfg.set_str(CFG_INFILE, "dummyfile.dat");
    cfg.set_str(CFG_OUTFILE, "dummyfile.dat.pgp");
    cfg.add_str(CFG_SIGNERS, keyids[0]);
    assert_true(cli_rnp_protect_file(&rnp));
    cfg.clear();

    // verify
    cfg.load_defaults();
    cfg.set_bool(CFG_OVERWRITE, true);
    cfg.set_str(CFG_INFILE, "dummyfile.dat.pgp");
    cfg.set_str(CFG_OUTFILE, "dummyfile.verify");
    assert_true(cli_rnp_process_file(&rnp));

    // verify (negative)
    std::fstream verf("dummyfile.dat.pgp",
                      std::ios_base::binary | std::ios_base::out | std::ios_base::in);
    verf.seekg(-3, std::ios::end);
    char bt = verf.peek() ^ 0xff;
    verf.seekp(-3, std::ios::end);
    verf.write(&bt, 1);
    verf.close();
    assert_false(cli_rnp_process_file(&rnp));

    // lock the signing key
    assert_rnp_success(rnp_key_lock(key));
    assert_rnp_success(rnp_key_is_locked(key, &locked));
    assert_true(locked);

    // sign, with no password (should now fail)
    assert_rnp_success(
      rnp_ffi_set_pass_provider(rnp.ffi, ffi_failing_password_provider, NULL));
    cfg.clear();
    cfg.load_defaults();
    cfg.set_bool(CFG_SIGN_NEEDED, true);
    cfg.set_bool(CFG_OVERWRITE, true);
    cfg.set_str(CFG_HASH, "SHA1");
    cfg.set_int(CFG_ZLEVEL, 0);
    cfg.set_str(CFG_INFILE, "dummyfile.dat");
    cfg.set_str(CFG_OUTFILE, "dummyfile.dat.pgp");
    cfg.add_str(CFG_SIGNERS, keyids[0]);
    assert_false(cli_rnp_protect_file(&rnp));
    cfg.clear();

    // encrypt
    cfg.load_defaults();
    cfg.set_bool(CFG_ENCRYPT_PK, true);
    cfg.set_int(CFG_ZLEVEL, 0);
    cfg.set_str(CFG_INFILE, "dummyfile.dat");
    cfg.set_str(CFG_OUTFILE, "dummyfile.dat.pgp");
    cfg.set_bool(CFG_OVERWRITE, true);
    cfg.set_str(CFG_CIPHER, "AES256");
    cfg.add_str(CFG_RECIPIENTS, keyids[1]);
    assert_true(cli_rnp_protect_file(&rnp));
    cfg.clear();

    // try decrypting with a failing password provider (should fail)
    cfg.load_defaults();
    cfg.set_bool(CFG_OVERWRITE, true);
    cfg.set_str(CFG_INFILE, "dummyfile.dat.pgp");
    cfg.set_str(CFG_OUTFILE, "dummyfile.decrypt");
    assert_false(cli_rnp_process_file(&rnp));

    // grab the encrypting key to unlock
    rnp_key_handle_t subkey = NULL;
    assert_rnp_success(rnp_locate_key(rnp.ffi, "keyid", keyids[1], &subkey));
    assert_non_null(subkey);

    // unlock the encrypting key
    assert_rnp_success(rnp_key_unlock(subkey, "password"));
    assert_rnp_success(rnp_key_is_locked(subkey, &locked));
    assert_false(locked);

    // decrypt, with no password
    assert_true(cli_rnp_process_file(&rnp));

    std::string decrypt = file_to_str("dummyfile.decrypt");
    assert_true(decrypt == data);

    // lock the encrypting key
    assert_rnp_success(rnp_key_lock(subkey));
    assert_rnp_success(rnp_key_is_locked(subkey, &locked));
    assert_true(locked);

    // decrypt, with no password (should now fail)
    assert_false(cli_rnp_process_file(&rnp));
    // cleanup
    assert_rnp_success(rnp_key_handle_destroy(key));
    assert_rnp_success(rnp_key_handle_destroy(subkey));
    rnp.end();
    assert_int_equal(rnp_unlink("dummyfile.dat"), 0);
}
