/*
 * Copyright (c) 2018-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 "pgp-key.h"

#include "rnp_tests.h"
#include "support.h"
#include "../librepgp/stream-packet.h"
#include "../librepgp/stream-armor.h"

static bool
all_keys_valid(const rnp_key_store_t *keyring, pgp_key_t *except = NULL)
{
    char keyid[PGP_KEY_ID_SIZE * 2 + 3] = {0};

    for (auto &key : keyring->keys) {
        if ((!key.valid() || key.expired()) && (&key != except)) {
            if (!rnp::hex_encode(key.keyid().data(),
                                 key.keyid().size(),
                                 keyid,
                                 sizeof(keyid),
                                 rnp::HEX_LOWERCASE)) {
                throw std::exception();
            }
            RNP_LOG("key %s is not valid", keyid);
            return false;
        }
    }
    return true;
}

TEST_F(rnp_tests, test_key_validate)
{
    rnp_key_store_t *pubring;
    rnp_key_store_t *secring;
    pgp_key_t *      key = NULL;

    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/1/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    /* this keyring has one expired subkey */
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "1d7e8a5393c997a8"));
    assert_false(key->valid());
    assert_true(key->expired());
    assert_true(all_keys_valid(pubring, key));
    delete pubring;

    /* secret key is marked is expired as well */
    secring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/1/secring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(secring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(secring, "1d7e8a5393c997a8"));
    assert_false(key->valid());
    assert_true(key->expired());
    assert_true(all_keys_valid(secring, key));
    delete secring;

    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/2/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_true(all_keys_valid(pubring));

    /* secret keyring doesn't have signatures - so keys are marked as invalid */
    secring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/2/secring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(secring, NULL));
    assert_false(all_keys_valid(secring));
    /* but after adding signatures from public it is marked as valid */
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "dc70c124a50283f1"));
    assert_non_null(rnp_key_store_import_key(secring, key, true, NULL));
    assert_true(all_keys_valid(secring));
    delete pubring;
    delete secring;

    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_KBX, "data/keyrings/3/pubring.kbx", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_true(all_keys_valid(pubring));

    secring =
      new rnp_key_store_t(PGP_KEY_STORE_G10, "data/keyrings/3/private-keys-v1.d", global_ctx);
    pgp_key_provider_t key_provider(rnp_key_provider_store, pubring);
    assert_true(rnp_key_store_load_from_path(secring, &key_provider));
    assert_true(all_keys_valid(secring));
    delete pubring;
    delete secring;

    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/4/pubring.pgp", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    /* certification has signature with MD5 hash algorithm */
    assert_false(all_keys_valid(pubring));

    rnp_key_store_clear(pubring);
    /* add rule which allows MD5 */
    rnp::SecurityRule allow_md5(
      rnp::FeatureType::Hash, PGP_HASH_MD5, rnp::SecurityLevel::Default);
    allow_md5.override = true;
    global_ctx.profile.add_rule(allow_md5);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_true(all_keys_valid(pubring));
    rnp_key_store_clear(pubring);
    /* remove rule */
    assert_true(global_ctx.profile.del_rule(allow_md5));
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_false(all_keys_valid(pubring));
    delete pubring;

    /* secret keyring doesn't have certifications - so marked as invalid */
    secring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/4/secring.pgp", global_ctx);
    assert_true(rnp_key_store_load_from_path(secring, NULL));
    assert_false(all_keys_valid(secring));
    delete secring;

    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/5/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_true(all_keys_valid(pubring));
    delete pubring;

    secring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/5/secring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(secring, NULL));
    assert_true(all_keys_valid(secring));
    delete secring;
}

#define DATA_PATH "data/test_forged_keys/"

static void
key_store_add(rnp_key_store_t *keyring, const char *keypath)
{
    pgp_source_t           keysrc = {};
    pgp_transferable_key_t tkey = {};

    assert_rnp_success(init_file_src(&keysrc, keypath));
    assert_rnp_success(process_pgp_key(keysrc, tkey, false));
    assert_true(rnp_key_store_add_transferable_key(keyring, &tkey));
    src_close(&keysrc);
}

static bool
key_check(rnp_key_store_t *keyring, const std::string &keyid, bool valid, bool expired = false)
{
    pgp_key_t *key = rnp_tests_get_key_by_id(keyring, keyid);
    return key && (key->validated()) && (key->valid() == valid) && (key->expired() == expired);
}

TEST_F(rnp_tests, test_forged_key_validate)
{
    rnp_key_store_t *pubring;
    pgp_key_t *      key = NULL;

    pubring = new rnp_key_store_t(PGP_KEY_STORE_GPG, "", global_ctx);

    /* load valid dsa-eg key */
    key_store_add(pubring, DATA_PATH "dsa-eg-pub.pgp");
    assert_true(key_check(pubring, "C8A10A7D78273E10", true));
    rnp_key_store_clear(pubring);

    /* load dsa-eg key with forged self-signature and binding. Subkey will not be valid as
     * well. */
    key_store_add(pubring, DATA_PATH "dsa-eg-pub-forged-key.pgp");
    assert_true(key_check(pubring, "C8A10A7D78273E10", false));
    assert_true(key_check(pubring, "02A5715C3537717E", false));
    rnp_key_store_clear(pubring);

    /* load dsa-eg key with forged key material */
    key_store_add(pubring, DATA_PATH "dsa-eg-pub-forged-material.pgp");
    key = rnp_tests_get_key_by_id(pubring, "C8A10A7D78273E10");
    assert_null(key);
    /* malformed key material causes keyid change */
    key = rnp_tests_get_key_by_id(pubring, "C258AB3B54097B9B");
    assert_non_null(key);
    assert_false(key->valid());
    assert_false(key->expired());
    rnp_key_store_clear(pubring);

    /* load dsa-eg keypair with forged subkey binding signature */
    key_store_add(pubring, DATA_PATH "dsa-eg-pub-forged-subkey.pgp");
    assert_true(key_check(pubring, "02A5715C3537717E", false));
    assert_true(key_check(pubring, "C8A10A7D78273E10", true));
    rnp_key_store_clear(pubring);

    /* load valid eddsa key */
    key_store_add(pubring, DATA_PATH "ecc-25519-pub.pgp");
    assert_true(key_check(pubring, "CC786278981B0728", true));
    rnp_key_store_clear(pubring);

    /* load eddsa key with forged self-signature */
    key_store_add(pubring, DATA_PATH "ecc-25519-pub-forged-key.pgp");
    assert_true(key_check(pubring, "CC786278981B0728", false));
    rnp_key_store_clear(pubring);

    /* load eddsa key with forged key material */
    key_store_add(pubring, DATA_PATH "ecc-25519-pub-forged-material.pgp");
    key = rnp_tests_get_key_by_id(pubring, "1BEF78DF765B79A2");
    assert_non_null(key);
    assert_false(key->valid());
    assert_false(key->expired());
    rnp_key_store_clear(pubring);

    /* load valid ecdsa/ecdh p-256 keypair */
    key_store_add(pubring, DATA_PATH "ecc-p256-pub.pgp");
    assert_true(key_check(pubring, "23674F21B2441527", true));
    assert_true(key_check(pubring, "37E285E9E9851491", true));
    rnp_key_store_clear(pubring);

    /* load ecdsa/ecdh key with forged self-signature. Both valid since there is valid binding.
     */
    key_store_add(pubring, DATA_PATH "ecc-p256-pub-forged-key.pgp");
    assert_true(key_check(pubring, "23674F21B2441527", true));
    assert_true(key_check(pubring, "37E285E9E9851491", true));
    rnp_key_store_clear(pubring);

    /* load ecdsa/ecdh key with forged key material. Subkey is not valid as well. */
    key_store_add(pubring, DATA_PATH "ecc-p256-pub-forged-material.pgp");
    key = rnp_tests_get_key_by_id(pubring, "23674F21B2441527");
    assert_null(key);
    key = rnp_tests_get_key_by_id(pubring, "41DEA786D18E5184");
    assert_non_null(key);
    assert_false(key->valid());
    assert_false(key->expired());
    assert_true(key_check(pubring, "37E285E9E9851491", false));
    rnp_key_store_clear(pubring);

    /* load ecdsa/ecdh keypair with forged subkey binding signature */
    key_store_add(pubring, DATA_PATH "ecc-p256-pub-forged-subkey.pgp");
    assert_true(key_check(pubring, "37E285E9E9851491", false));
    assert_true(key_check(pubring, "23674F21B2441527", true));
    rnp_key_store_clear(pubring);

    /* load ecdsa/ecdh keypair without certification: valid since have binding */
    key_store_add(pubring, DATA_PATH "ecc-p256-pub-no-certification.pgp");
    assert_true(key_check(pubring, "23674F21B2441527", true));
    assert_true(key_check(pubring, "37E285E9E9851491", true));
    rnp_key_store_clear(pubring);

    /* load ecdsa/ecdh keypair without certification and invalid binding */
    key_store_add(pubring, DATA_PATH "ecc-p256-pub-no-cert-malf-binding.pgp");
    assert_true(key_check(pubring, "23674F21B2441527", false));
    assert_true(key_check(pubring, "37E285E9E9851491", false));
    rnp_key_store_clear(pubring);

    /* load ecdsa/ecdh keypair without subkey binding */
    key_store_add(pubring, DATA_PATH "ecc-p256-pub-no-binding.pgp");
    assert_true(key_check(pubring, "23674F21B2441527", true));
    assert_true(key_check(pubring, "37E285E9E9851491", false));
    rnp_key_store_clear(pubring);

    /* load valid rsa/rsa keypair */
    key_store_add(pubring, DATA_PATH "rsa-rsa-pub.pgp");
    /* it is valid only till year 2024 since SHA1 hash is used for signatures */
    assert_true(key_check(pubring, "2FB9179118898E8B", true));
    assert_true(key_check(pubring, "6E2F73008F8B8D6E", true));
    rnp_key_store_clear(pubring);
    /* load eddsa key which uses SHA1 signature and is created after the cutoff date */
    global_ctx.set_time(SHA1_KEY_FROM + 10);
    key_store_add(pubring, DATA_PATH "eddsa-2024-pub.pgp");
    assert_false(key_check(pubring, "980E3741F632212C", true));
    assert_false(key_check(pubring, "6DA00BF7F8B59B53", true));
    global_ctx.set_time(0);
    rnp_key_store_clear(pubring);

    /* load rsa/rsa key with forged self-signature. Valid because of valid binding. */
    key_store_add(pubring, DATA_PATH "rsa-rsa-pub-forged-key.pgp");
    assert_true(key_check(pubring, "2FB9179118898E8B", true));
    assert_true(key_check(pubring, "6E2F73008F8B8D6E", true));
    rnp_key_store_clear(pubring);

    /* load rsa/rsa key with forged key material. Subkey is not valid as well. */
    key_store_add(pubring, DATA_PATH "rsa-rsa-pub-forged-material.pgp");
    key = rnp_tests_get_key_by_id(pubring, "2FB9179118898E8B");
    assert_null(key);
    key = rnp_tests_get_key_by_id(pubring, "791B14952D8F906C");
    assert_non_null(key);
    assert_false(key->valid());
    assert_false(key->expired());
    assert_true(key_check(pubring, "6E2F73008F8B8D6E", false));
    rnp_key_store_clear(pubring);

    /* load rsa/rsa keypair with forged subkey binding signature */
    key_store_add(pubring, DATA_PATH "rsa-rsa-pub-forged-subkey.pgp");
    assert_true(key_check(pubring, "2FB9179118898E8B", true));
    assert_true(key_check(pubring, "6E2F73008F8B8D6E", false));
    rnp_key_store_clear(pubring);

    /* load rsa/rsa keypair with future creation date */
    key_store_add(pubring, DATA_PATH "rsa-rsa-pub-future-key.pgp");
    assert_true(key_check(pubring, "3D032D00EE1EC3F5", false));
    assert_true(key_check(pubring, "021085B640CE8DCE", false));
    rnp_key_store_clear(pubring);

    /* load eddsa/rsa keypair with certification with future creation date - valid because of
     * binding. */
    key_store_add(pubring, DATA_PATH "ecc-25519-pub-future-cert.pgp");
    assert_true(key_check(pubring, "D3B746FA852C2BE8", true));
    assert_true(key_check(pubring, "EB8C21ACDC15CA14", true));
    rnp_key_store_clear(pubring);

    /* load eddsa/rsa keypair with certification with future creation date - invalid because of
     * invalid binding. */
    key_store_add(pubring, DATA_PATH "ecc-25519-pub-future-cert-malf-bind.pgp");
    assert_true(key_check(pubring, "D3B746FA852C2BE8", false));
    assert_true(key_check(pubring, "EB8C21ACDC15CA14", false));
    rnp_key_store_clear(pubring);

    /* load ecdsa/rsa keypair with expired subkey */
    key_store_add(pubring, DATA_PATH "ecc-p256-pub-expired-subkey.pgp");
    assert_true(key_check(pubring, "23674F21B2441527", true));
    assert_true(key_check(pubring, "37E285E9E9851491", false, true));
    rnp_key_store_clear(pubring);

    /* load ecdsa/ecdh keypair with expired key */
    key_store_add(pubring, DATA_PATH "ecc-p256-pub-expired-key.pgp");
    assert_true(key_check(pubring, "23674F21B2441527", false, true));
    assert_true(key_check(pubring, "37E285E9E9851491", false));
    rnp_key_store_clear(pubring);

    delete pubring;
}

#define KEYSIG_PATH "data/test_key_validity/"

TEST_F(rnp_tests, test_key_validity)
{
    rnp_key_store_t *pubring;
    pgp_key_t *      key = NULL;

    /* Case1:
     * Keys: Alice [pub]
     * Alice is signed by Basil, but without the Basil's key.
     * Result: Alice [valid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case1/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
    assert_true(key->valid());
    assert_false(key->expired());
    delete pubring;

    /* Case2:
     * Keys: Alice [pub], Basil [pub]
     * Alice is signed by Basil, Basil is signed by Alice, but Alice's self-signature is
     * corrupted.
     * Result: Alice [invalid], Basil [valid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case2/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
    assert_false(key->valid());
    assert_false(key->expired());
    assert_non_null(key = rnp_tests_key_search(pubring, "Basil <basil@rnp>"));
    assert_true(key->valid());
    assert_false(key->expired());
    delete pubring;

    /* Case3:
     * Keys: Alice [pub], Basil [pub]
     * Alice is signed by Basil, but doesn't have self-signature
     * Result: Alice [invalid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case3/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
    assert_false(key->valid());
    assert_false(key->expired());
    assert_non_null(key = rnp_tests_key_search(pubring, "Basil <basil@rnp>"));
    assert_true(key->valid());
    assert_false(key->expired());
    delete pubring;

    /* Case4:
     * Keys Alice [pub, sub]
     * Alice subkey has invalid binding signature
     * Result: Alice [valid], Alice sub [invalid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case4/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
    assert_true(key->valid());
    assert_false(key->expired());
    assert_int_equal(key->subkey_count(), 1);
    pgp_key_t *subkey = NULL;
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_false(subkey->valid());
    assert_false(subkey->expired());
    delete pubring;

    /* Case5:
     * Keys Alice [pub, sub], Basil [pub]
     * Alice subkey has valid binding signature, but from the key Basil
     * Result: Alice [valid], Alice sub [invalid]
     *
     * Note: to re-generate keyring file, use generate.cpp from case5 folder.
     *       To build it, feed -DBUILD_TESTING_GENERATORS=On to the cmake.
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case5/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
    assert_true(key->valid());
    assert_false(key->expired());
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_false(subkey->valid());
    assert_false(subkey->expired());
    delete pubring;

    /* Case6:
     * Keys Alice [pub, sub]
     * Key Alice has revocation signature by Alice, and subkey doesn't
     * Result: Alice [invalid], Alice sub [invalid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case6/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
    assert_false(key->valid());
    assert_false(key->expired());
    assert_true(key->revoked());
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_false(subkey->valid());
    assert_false(subkey->expired());
    assert_false(subkey->revoked());
    delete pubring;

    /* Case7:
     * Keys Alice [pub, sub]
     * Alice subkey has revocation signature by Alice
     * Result: Alice [valid], Alice sub [invalid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case7/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
    assert_true(key->valid());
    assert_false(key->expired());
    assert_false(key->revoked());
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_false(subkey->valid());
    assert_false(subkey->expired());
    assert_true(subkey->revoked());
    delete pubring;

    /* Case8:
     * Keys Alice [pub, sub]
     * Userid is stripped from the key, but it still has valid subkey binding
     * Result: Alice [valid], Alice sub[valid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case8/pubring.gpg", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
    assert_true(key->valid());
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_true(subkey->valid());
    delete pubring;

    /* Case9:
     * Keys Alice [pub, sub]
     * Alice key has two self-signatures, one which expires key and second without key
     * expiration.
     * Result: Alice [valid], Alice sub[valid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case9/pubring.gpg", global_ctx);
    assert_non_null(pubring);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
    assert_true(key->valid());
    assert_false(key->expired());
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_true(subkey->valid());
    assert_false(subkey->expired());
    delete pubring;

    /* Case10:
     * Keys Alice [pub, sub]
     * Alice key has expiring direct-key signature and non-expiring self-certification.
     * Result: Alice [invalid], Alice sub[invalid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case10/pubring.gpg", global_ctx);
    assert_non_null(pubring);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
    assert_false(key->valid());
    assert_true(key->expired());
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_false(subkey->valid());
    assert_false(subkey->expired());
    delete pubring;

    /* Case11:
     * Keys Alice [pub, sub]
     * Alice key has expiring direct-key signature, non-expiring self-certification and
     * expiring primary userid certification. Result: Alice [invalid], Alice sub[invalid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case11/pubring.gpg", global_ctx);
    assert_non_null(pubring);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
    assert_false(key->valid());
    assert_true(key->expired());
    assert_int_equal(key->expiration(), 100);
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_false(subkey->valid());
    assert_false(subkey->expired());
    delete pubring;

    /* Case12:
     * Keys Alice [pub, sub]
     * Alice key has non-expiring direct-key signature, non-expiring self-certification and
     * expiring primary userid certification. Result: Alice [invalid], Alice sub[invalid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case12/pubring.gpg", global_ctx);
    assert_non_null(pubring);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
    assert_false(key->valid());
    assert_true(key->expired());
    assert_int_equal(key->expiration(), 2000);
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_false(subkey->valid());
    assert_false(subkey->expired());
    delete pubring;

    /* Case13:
     * Keys Alice [pub, sub]
     * Alice key has expiring direct-key signature, non-expiring self-certification and
     * non-expiring primary userid certification. Result: Alice [invalid], Alice sub[invalid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case13/pubring.gpg", global_ctx);
    assert_non_null(pubring);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
    assert_false(key->valid());
    assert_true(key->expired());
    assert_int_equal(key->expiration(), 6);
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_false(subkey->valid());
    assert_false(subkey->expired());
    delete pubring;

    /* Case14:
     * Keys Alice [pub, sub]
     * Alice key has expiring direct-key signature, non-expiring self-certification and
     * non-expiring primary userid certification (with 0 key expiration subpacket). Result:
     * Alice [invalid], Alice sub[invalid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case14/pubring.gpg", global_ctx);
    assert_non_null(pubring);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
    assert_false(key->valid());
    assert_true(key->expired());
    assert_int_equal(key->expiration(), 6);
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_false(subkey->valid());
    assert_false(subkey->expired());
    delete pubring;

    /* Case15:
     * Keys [pub, sub]
     * Signing subkey has expired primary-key signature embedded into the subkey binding.
     * Result: primary [valid], sub[invalid]
     */
    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case15/pubring.gpg", global_ctx);
    assert_non_null(pubring);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(key = rnp_tests_get_key_by_id(pubring, "E863072D3E9042EE"));
    assert_true(key->valid());
    assert_false(key->expired());
    assert_int_equal(key->expiration(), 0);
    assert_int_equal(key->subkey_count(), 1);
    assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
    assert_false(subkey->valid());
    assert_false(subkey->expired());
    delete pubring;
}

TEST_F(rnp_tests, test_key_expiry_direct_sig)
{
    /* this test was mainly used to generate test data for cases 10-12 in test_key_validity */
    rnp_key_store_t *secring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "alice-sub-sec.pgp", global_ctx);
    assert_true(rnp_key_store_load_from_path(secring, NULL));
    pgp_key_t *key = NULL;
    assert_non_null(key = rnp_tests_key_search(secring, "Alice <alice@rnp>"));
    assert_true(key->valid());
    assert_false(key->expired());
    /* create direct-key signature */
    pgp_signature_t sig;

    sig.version = PGP_V4;
    sig.halg = PGP_HASH_SHA256;
    sig.palg = key->alg();
    sig.set_type(PGP_SIG_DIRECT);
    sig.set_creation(key->creation());
    sig.set_key_expiration(1000);
    sig.set_keyfp(key->fp());
    sig.set_keyid(key->keyid());

    pgp_password_provider_t pprov(string_copy_password_callback, (void *) "password");
    key->unlock(pprov);
    key->sign_direct(key->pkt(), sig, global_ctx);
    key->add_sig(sig, PGP_UID_NONE);
    key->revalidate(*secring);

    /* key becomsed invalid even since it is secret */
    assert_int_equal(key->expiration(), 1000);
    assert_false(key->valid());
    assert_true(key->expired());

    rnp_key_store_t *pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "alice-sub-pub.pgp", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    pgp_key_t *pubkey = NULL;
    assert_non_null(pubkey = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
    assert_int_equal(pubkey->expiration(), 0);
    assert_true(pubkey->valid());
    assert_false(pubkey->expired());
    pgp_key_t *subpub = NULL;
    assert_non_null(subpub = rnp_tests_get_key_by_id(pubring, "dd23ceb7febeff17"));
    assert_int_equal(subpub->expiration(), 0);
    assert_true(subpub->valid());
    assert_false(subpub->expired());

    pubkey->add_sig(sig, PGP_UID_NONE);
    pubkey->revalidate(*pubring);
    assert_int_equal(pubkey->expiration(), 1000);
    assert_false(pubkey->valid());
    assert_true(pubkey->expired());
    assert_int_equal(subpub->expiration(), 0);
    assert_false(subpub->valid());
    assert_false(subpub->expired());

    /* add primary userid with smaller expiration date */
    rnp_selfsig_cert_info_t selfsig1 = {};
    const char *            boris = "Boris <boris@rnp>";
    selfsig1.userid = boris;
    selfsig1.key_expiration = 100;
    selfsig1.primary = true;
    key->add_uid_cert(selfsig1, PGP_HASH_SHA256, global_ctx);
    key->revalidate(*secring);
    /* key becomes invalid even it is secret */
    assert_int_equal(key->expiration(), 100);
    assert_false(key->valid());
    assert_true(key->expired());

    delete secring;
    delete pubring;

    secring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "alice-sub-sec.pgp", global_ctx);
    assert_true(rnp_key_store_load_from_path(secring, NULL));
    assert_non_null(key = rnp_tests_key_search(secring, "Alice <alice@rnp>"));
    /* create direct-key signature */
    sig = {};
    sig.version = PGP_V4;
    sig.halg = PGP_HASH_SHA256;
    sig.palg = key->alg();
    sig.set_type(PGP_SIG_DIRECT);
    sig.set_creation(key->creation());
    sig.set_key_expiration(6);
    sig.set_keyfp(key->fp());
    sig.set_keyid(key->keyid());

    key->unlock(pprov);
    key->sign_direct(key->pkt(), sig, global_ctx);
    key->add_sig(sig, PGP_UID_NONE);
    key->revalidate(*secring);
    assert_int_equal(key->expiration(), 6);
    /* add primary userid with 0 expiration */
    selfsig1 = {};
    selfsig1.userid = boris;
    selfsig1.key_expiration = 0;
    selfsig1.primary = true;
    key->add_uid_cert(selfsig1, PGP_HASH_SHA256, global_ctx);
    key->revalidate(*secring);
    assert_int_equal(key->expiration(), 6);

    pubring =
      new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "alice-sub-pub.pgp", global_ctx);
    assert_true(rnp_key_store_load_from_path(pubring, NULL));
    assert_non_null(pubkey = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
    assert_non_null(subpub = rnp_tests_get_key_by_id(pubring, "dd23ceb7febeff17"));
    assert_int_equal(subpub->expiration(), 0);
    assert_true(subpub->valid());
    assert_false(subpub->expired());

    pubkey->add_sig(sig, PGP_UID_NONE);
    pubkey->revalidate(*pubring);
    assert_int_equal(pubkey->expiration(), 6);
    assert_false(pubkey->valid());
    assert_true(pubkey->expired());

    pgp_transferable_userid_t truid = {};
    truid.uid = key->get_uid(1).pkt;
    truid.signatures.push_back(key->get_sig(key->get_uid(1).get_sig(0)).sig);
    pubkey->add_uid(truid);
    pubkey->revalidate(*pubring);

    assert_int_equal(pubkey->expiration(), 6);
    assert_false(pubkey->valid());
    assert_true(pubkey->expired());

    /* code below may be used to print out generated key to save it somewhere */
    /*
    pgp_dest_t out = {};
    pgp_dest_t armored = {};
    assert_rnp_success(init_stdout_dest(&out));
    assert_rnp_success(init_armored_dst(&armored, &out, PGP_ARMORED_PUBLIC_KEY));
    pubkey->write_xfer(armored, pubring);
    dst_close(&armored, false);
    dst_close(&out, false);
    */

    delete secring;
    delete pubring;
}
