/* * Copyright (c) 2017-2020 [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 "rnp.h" #include #include #include #include "rnp_tests.h" #include "support.h" #include "crypto/common.h" #include "crypto.h" #include "pgp-key.h" #include "librepgp/stream-ctx.h" #include "librepgp/stream-sig.h" #include "librepgp/stream-key.h" #include "defaults.h" #include static bool generate_test_key(const char *keystore, const char *userid, const char *hash, const char *home) { cli_rnp_t rnp; int pipefd[2] = {-1, -1}; bool res = false; size_t keycount = 0; /* Initialize the cli rnp structure and generate key */ if (!setup_cli_rnp_common(&rnp, keystore, home, pipefd)) { return false; } std::vector keys; /* Generate the key */ cli_set_default_rsa_key_desc(rnp.cfg(), hash); if (!cli_rnp_generate_key(&rnp, userid)) { goto done; } if (!rnp.load_keyrings(true)) { goto done; } if (rnp_get_public_key_count(rnp.ffi, &keycount) || (keycount != 2)) { goto done; } if (rnp_get_secret_key_count(rnp.ffi, &keycount) || (keycount != 2)) { goto done; } if (!cli_rnp_keys_matching_string( &rnp, keys, userid ? userid : "", CLI_SEARCH_SUBKEYS_AFTER)) { goto done; } if (keys.size() != 2) { goto done; } res = true; done: if (pipefd[0] != -1) { close(pipefd[0]); } clear_key_handles(keys); rnp.end(); return res; } static bool hash_supported(const std::string &hash) { if (!sm2_enabled() && lowercase(hash) == "sm3") { return false; } return true; } static bool hash_secure(rnp_ffi_t ffi, const std::string &hash, uint32_t action, uint64_t time) { uint32_t flags = action; uint32_t level = 0; rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, hash.c_str(), time, &flags, NULL, &level); return level == RNP_SECURITY_DEFAULT; } TEST_F(rnp_tests, rnpkeys_generatekey_testSignature) { /* Set the UserId = custom value. * Execute the Generate-key command to generate a new pair of private/public * key * Sign a message, then verify it */ const char *hashAlg[] = {"SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "SM3", "sha1", "sha224", "sha256", "sha384", "sha512", "sm3", NULL}; int pipefd[2] = {-1, -1}; char memToSign[] = "A simple test message"; cli_rnp_t rnp; std::ofstream out("dummyfile.dat"); out << memToSign; out.close(); for (int i = 0; hashAlg[i] != NULL; i++) { std::string userId = std::string("sigtest_") + hashAlg[i]; /* Generate key for test */ assert_true( generate_test_key(RNP_KEYSTORE_GPG, userId.c_str(), DEFAULT_HASH_ALG, NULL)); for (unsigned int cleartext = 0; cleartext <= 1; ++cleartext) { for (unsigned int armored = 0; armored <= 1; ++armored) { if (cleartext && !armored) { // This combination doesn't make sense continue; } /* Setup password input and rnp structure */ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, NULL, pipefd)); /* Load keyring */ assert_true(rnp.load_keyrings(true)); size_t seccount = 0; assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &seccount)); assert_true(seccount > 0); /* Setup signing context */ rnp_cfg &cfg = rnp.cfg(); cfg.load_defaults(); cfg.set_bool(CFG_ARMOR, armored); cfg.set_bool(CFG_SIGN_NEEDED, true); cfg.set_str(CFG_HASH, hashAlg[i]); 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_CLEARTEXT, cleartext); cfg.add_str(CFG_SIGNERS, userId); /* Sign the file */ if (!hash_supported(hashAlg[i])) { assert_false(cli_rnp_protect_file(&rnp)); rnp.end(); assert_int_equal(rnp_unlink("dummyfile.dat.pgp"), -1); continue; } assert_true(cli_rnp_protect_file(&rnp)); if (pipefd[0] != -1) { close(pipefd[0]); pipefd[0] = -1; } /* Verify the file */ cfg.clear(); cfg.load_defaults(); cfg.set_bool(CFG_OVERWRITE, true); cfg.set_str(CFG_INFILE, "dummyfile.dat.pgp"); cfg.set_str(CFG_OUTFILE, "dummyfile.verify"); if (!hash_secure( rnp.ffi, hashAlg[i], RNP_SECURITY_VERIFY_DATA, global_ctx.time())) { assert_false(cli_rnp_process_file(&rnp)); rnp.end(); assert_int_equal(rnp_unlink("dummyfile.dat.pgp"), 0); continue; } assert_true(cli_rnp_process_file(&rnp)); /* Ensure signature verification passed */ std::string verify = file_to_str("dummyfile.verify"); if (cleartext) { verify = strip_eol(verify); } assert_true(verify == memToSign); /* Corrupt the signature if not armored/cleartext */ if (!cleartext && !armored) { std::fstream verf("dummyfile.dat.pgp", std::ios_base::binary | std::ios_base::out | std::ios_base::in); off_t versize = file_size("dummyfile.dat.pgp"); verf.seekg(versize - 10, std::ios::beg); char sigch = 0; verf.read(&sigch, 1); sigch = sigch ^ 0xff; verf.seekg(versize - 10, std::ios::beg); verf.write(&sigch, 1); verf.close(); assert_false(cli_rnp_process_file(&rnp)); } rnp.end(); assert_int_equal(rnp_unlink("dummyfile.dat.pgp"), 0); rnp_unlink("dummyfile.verify"); } } } assert_int_equal(rnp_unlink("dummyfile.dat"), 0); } static bool cipher_supported(const std::string &cipher) { if (!sm2_enabled() && lowercase(cipher) == "sm4") { return false; } if (!twofish_enabled() && lowercase(cipher) == "twofish") { return false; } if (!blowfish_enabled() && lowercase(cipher) == "blowfish") { return false; } if (!cast5_enabled() && lowercase(cipher) == "cast5") { return false; } return true; } TEST_F(rnp_tests, rnpkeys_generatekey_testEncryption) { const char *cipherAlg[] = { "BLOWFISH", "TWOFISH", "CAST5", "TRIPLEDES", "AES128", "AES192", "AES256", "CAMELLIA128", "CAMELLIA192", "CAMELLIA256", "SM4", "blowfish", "twofish", "cast5", "tripledes", "aes128", "aes192", "aes256", "camellia128", "camellia192", "camellia256", "sm4", NULL}; cli_rnp_t rnp = {}; char memToEncrypt[] = "A simple test message"; int pipefd[2] = {-1, -1}; const char *userid = "ciphertest"; std::ofstream out("dummyfile.dat"); out << memToEncrypt; out.close(); assert_true(generate_test_key(RNP_KEYSTORE_GPG, userid, "SHA256", NULL)); for (int i = 0; cipherAlg[i] != NULL; i++) { for (unsigned int armored = 0; armored <= 1; ++armored) { /* Set up rnp and encrypt the dataa */ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, NULL, NULL)); /* Load keyring */ assert_true(rnp.load_keyrings(false)); size_t seccount = 0; assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &seccount)); assert_true(seccount == 0); /* Set the cipher and armored flags */ rnp_cfg &cfg = rnp.cfg(); cfg.load_defaults(); cfg.set_bool(CFG_ARMOR, armored); 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_str(CFG_CIPHER, cipherAlg[i]); cfg.add_str(CFG_RECIPIENTS, userid); /* Encrypt the file */ bool supported = cipher_supported(cipherAlg[i]); assert_true(cli_rnp_protect_file(&rnp) == supported); rnp.end(); if (!supported) { continue; } /* Set up rnp again and decrypt the file */ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, NULL, pipefd)); /* Load the keyrings */ assert_true(rnp.load_keyrings(true)); assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &seccount)); assert_true(seccount > 0); /* Setup the decryption context and decrypt */ rnp_cfg &newcfg = rnp.cfg(); newcfg.load_defaults(); newcfg.set_bool(CFG_OVERWRITE, true); newcfg.set_str(CFG_INFILE, "dummyfile.dat.pgp"); newcfg.set_str(CFG_OUTFILE, "dummyfile.decrypt"); assert_true(cli_rnp_process_file(&rnp)); rnp.end(); if (pipefd[0] != -1) { close(pipefd[0]); } /* Ensure plaintext recovered */ std::string decrypt = file_to_str("dummyfile.decrypt"); assert_true(decrypt == memToEncrypt); assert_int_equal(rnp_unlink("dummyfile.dat.pgp"), 0); assert_int_equal(rnp_unlink("dummyfile.decrypt"), 0); } } assert_int_equal(rnp_unlink("dummyfile.dat"), 0); } TEST_F(rnp_tests, rnpkeys_generatekey_verifySupportedHashAlg) { /* Generate key for each of the hash algorithms. Check whether key was generated * successfully */ const char *hashAlg[] = {"MD5", "SHA1", "SHA256", "SHA384", "SHA512", "SHA224", "SM3", "md5", "sha1", "sha256", "sha384", "sha512", "sha224", "sm3"}; const char *keystores[] = {RNP_KEYSTORE_GPG, RNP_KEYSTORE_GPG21, RNP_KEYSTORE_KBX}; cli_rnp_t rnp = {}; for (size_t i = 0; i < ARRAY_SIZE(hashAlg); i++) { const char *keystore = keystores[i % ARRAY_SIZE(keystores)]; /* Setting up rnp again and decrypting memory */ printf("keystore: %s, hashalg %s\n", keystore, hashAlg[i]); /* Generate key with specified hash algorithm */ bool supported = hash_supported(hashAlg[i]); assert_true(generate_test_key(keystore, hashAlg[i], hashAlg[i], NULL) == supported); if (!supported) { continue; } /* Load and check key */ assert_true(setup_cli_rnp_common(&rnp, keystore, NULL, NULL)); /* Loading the keyrings */ assert_true(rnp.load_keyrings(true)); /* Some minor checks */ size_t keycount = 0; assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount)); assert_true(keycount > 0); keycount = 0; assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount)); assert_true(keycount > 0); rnp_key_handle_t handle = NULL; assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", hashAlg[i], &handle)); if (hash_secure(rnp.ffi, hashAlg[i], RNP_SECURITY_VERIFY_KEY, global_ctx.time())) { assert_non_null(handle); bool valid = false; rnp_key_is_valid(handle, &valid); assert_true(valid); } else { assert_null(handle); } rnp_key_handle_destroy(handle); rnp.end(); delete_recursively(".rnp"); } } TEST_F(rnp_tests, rnpkeys_generatekey_verifyUserIdOption) { /* Set the UserId = custom value. * Execute the Generate-key command to generate a new keypair * Verify the key was generated with the correct UserId. */ const char *userIds[] = {"rnpkeys_generatekey_verifyUserIdOption_MD5", "rnpkeys_generatekey_verifyUserIdOption_SHA-1", "rnpkeys_generatekey_verifyUserIdOption_RIPEMD160", "rnpkeys_generatekey_verifyUserIdOption_SHA256", "rnpkeys_generatekey_verifyUserIdOption_SHA384", "rnpkeys_generatekey_verifyUserIdOption_SHA512", "rnpkeys_generatekey_verifyUserIdOption_SHA224"}; const char *keystores[] = {RNP_KEYSTORE_GPG, RNP_KEYSTORE_GPG21, RNP_KEYSTORE_KBX}; cli_rnp_t rnp = {}; for (size_t i = 0; i < ARRAY_SIZE(userIds); i++) { const char *keystore = keystores[i % ARRAY_SIZE(keystores)]; /* Generate key with specified hash algorithm */ assert_true(generate_test_key(keystore, userIds[i], "SHA256", NULL)); /* Initialize the basic RNP structure. */ assert_true(setup_cli_rnp_common(&rnp, keystore, NULL, NULL)); /* Load the newly generated rnp key*/ assert_true(rnp.load_keyrings(true)); size_t keycount = 0; assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount)); assert_true(keycount > 0); keycount = 0; assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount)); assert_true(keycount > 0); rnp_key_handle_t handle = NULL; assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", userIds[i], &handle)); assert_non_null(handle); rnp_key_handle_destroy(handle); rnp.end(); delete_recursively(".rnp"); } } TEST_F(rnp_tests, rnpkeys_generatekey_verifykeyHomeDirOption) { /* Try to generate keypair in different home directories */ cli_rnp_t rnp = {}; /* Initialize the rnp structure. */ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, NULL, NULL)); /* Pubring and secring should not exist yet */ assert_false(path_rnp_file_exists(".rnp/pubring.gpg", NULL)); assert_false(path_rnp_file_exists(".rnp/secring.gpg", NULL)); /* Ensure the key was generated. */ assert_true(generate_test_key(RNP_KEYSTORE_GPG, NULL, "SHA256", NULL)); /* Pubring and secring should now exist */ assert_true(path_rnp_file_exists(".rnp/pubring.gpg", NULL)); assert_true(path_rnp_file_exists(".rnp/secring.gpg", NULL)); /* Loading keyrings and checking whether they have correct key */ assert_true(rnp.load_keyrings(true)); size_t keycount = 0; assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount)); assert_int_equal(keycount, 2); keycount = 0; assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount)); assert_int_equal(keycount, 2); std::string userid = fmt("RSA (Encrypt or Sign) 1024-bit key <%s@localhost>", getenv_logname()); rnp_key_handle_t handle = NULL; assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", userid.c_str(), &handle)); assert_non_null(handle); rnp_key_handle_destroy(handle); rnp.end(); /* Now we start over with a new home. When home is specified explicitly then it should * include .rnp as well */ std::string newhome = "newhome/.rnp"; path_mkdir(0700, "newhome", NULL); path_mkdir(0700, newhome.c_str(), NULL); /* Initialize the rnp structure. */ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, newhome.c_str(), NULL)); /* Pubring and secring should not exist yet */ assert_false(path_rnp_file_exists(newhome.c_str(), "pubring.gpg", NULL)); assert_false(path_rnp_file_exists(newhome.c_str(), "secring.gpg", NULL)); /* Ensure the key was generated. */ assert_true(generate_test_key(RNP_KEYSTORE_GPG, "newhomekey", "SHA256", newhome.c_str())); /* Pubring and secring should now exist */ assert_true(path_rnp_file_exists(newhome.c_str(), "pubring.gpg", NULL)); assert_true(path_rnp_file_exists(newhome.c_str(), "secring.gpg", NULL)); /* Loading keyrings and checking whether they have correct key */ assert_true(rnp.load_keyrings(true)); keycount = 0; assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount)); assert_int_equal(keycount, 2); keycount = 0; assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount)); assert_int_equal(keycount, 2); /* We should not find this key */ assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", userid.c_str(), &handle)); assert_null(handle); assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", "newhomekey", &handle)); assert_non_null(handle); rnp_key_handle_destroy(handle); rnp.end(); // Free memory and other allocated resources. } TEST_F(rnp_tests, rnpkeys_generatekey_verifykeyKBXHomeDirOption) { /* Try to generate keypair in different home directories for KBX keystorage */ const char *newhome = "newhome"; cli_rnp_t rnp = {}; /* Initialize the rnp structure. */ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_KBX, NULL, NULL)); /* Pubring and secring should not exist yet */ assert_false(path_rnp_file_exists(".rnp/pubring.kbx", NULL)); assert_false(path_rnp_file_exists(".rnp/secring.kbx", NULL)); assert_false(path_rnp_file_exists(".rnp/pubring.gpg", NULL)); assert_false(path_rnp_file_exists(".rnp/secring.gpg", NULL)); /* Ensure the key was generated. */ assert_true(generate_test_key(RNP_KEYSTORE_KBX, NULL, "SHA256", NULL)); /* Pubring and secring should now exist, but only for the KBX */ assert_true(path_rnp_file_exists(".rnp/pubring.kbx", NULL)); assert_true(path_rnp_file_exists(".rnp/secring.kbx", NULL)); assert_false(path_rnp_file_exists(".rnp/pubring.gpg", NULL)); assert_false(path_rnp_file_exists(".rnp/secring.gpg", NULL)); /* Loading keyrings and checking whether they have correct key */ assert_true(rnp.load_keyrings(true)); size_t keycount = 0; assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount)); assert_int_equal(keycount, 2); keycount = 0; assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount)); assert_int_equal(keycount, 2); std::string userid = fmt("RSA (Encrypt or Sign) 1024-bit key <%s@localhost>", getenv_logname()); rnp_key_handle_t handle = NULL; assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", userid.c_str(), &handle)); assert_non_null(handle); rnp_key_handle_destroy(handle); rnp.end(); /* Now we start over with a new home. */ path_mkdir(0700, newhome, NULL); /* Initialize the rnp structure. */ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_KBX, newhome, NULL)); /* Pubring and secring should not exist yet */ assert_false(path_rnp_file_exists(newhome, "pubring.kbx", NULL)); assert_false(path_rnp_file_exists(newhome, "secring.kbx", NULL)); assert_false(path_rnp_file_exists(newhome, "pubring.gpg", NULL)); assert_false(path_rnp_file_exists(newhome, "secring.gpg", NULL)); /* Ensure the key was generated. */ assert_true(generate_test_key(RNP_KEYSTORE_KBX, "newhomekey", "SHA256", newhome)); /* Pubring and secring should now exist, but only for the KBX */ assert_true(path_rnp_file_exists(newhome, "pubring.kbx", NULL)); assert_true(path_rnp_file_exists(newhome, "secring.kbx", NULL)); assert_false(path_rnp_file_exists(newhome, "pubring.gpg", NULL)); assert_false(path_rnp_file_exists(newhome, "secring.gpg", NULL)); /* Loading keyrings and checking whether they have correct key */ assert_true(rnp.load_keyrings(true)); keycount = 0; assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount)); assert_int_equal(keycount, 2); keycount = 0; assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount)); assert_int_equal(keycount, 2); /* We should not find this key */ assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", userid.c_str(), &handle)); assert_null(handle); assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", "newhomekey", &handle)); assert_non_null(handle); rnp_key_handle_destroy(handle); rnp.end(); } TEST_F(rnp_tests, rnpkeys_generatekey_verifykeyHomeDirNoPermission) { const char *nopermsdir = "noperms"; path_mkdir(0000, nopermsdir, NULL); /* Try to generate key in the directory and make sure generation fails */ #ifndef _WIN32 assert_false(generate_test_key(RNP_KEYSTORE_GPG, NULL, "SHA256", nopermsdir)); #else /* There are no permissions for mkdir() under the Windows */ assert_true(generate_test_key(RNP_KEYSTORE_GPG, NULL, "SHA256", nopermsdir)); #endif } static bool ask_expert_details(cli_rnp_t *ctx, rnp_cfg &ops, const char *rsp) { /* Run tests*/ bool ret = false; int pipefd[2] = {-1, -1}; int user_input_pipefd[2] = {-1, -1}; size_t rsp_len; if (pipe(pipefd) == -1) { return false; } ops.set_int(CFG_PASSFD, pipefd[0]); write_pass_to_pipe(pipefd[1], 2); close(pipefd[1]); if (!rnpkeys_init(*ctx, ops)) { close(pipefd[0]); // otherwise will be closed via passfp goto end; } /* Write response to fd */ if (pipe(user_input_pipefd) == -1) { goto end; } rsp_len = strlen(rsp); for (size_t i = 0; i < rsp_len;) { i += write(user_input_pipefd[1], rsp + i, rsp_len - i); } close(user_input_pipefd[1]); /* Mock user-input*/ ctx->cfg().set_int(CFG_USERINPUTFD, user_input_pipefd[0]); if (!rnp_cmd(ctx, CMD_GENERATE_KEY, NULL)) { ret = false; goto end; } ops.copy(ctx->cfg()); ret = true; end: /* Close & clean fd*/ if (user_input_pipefd[0]) { close(user_input_pipefd[0]); } return ret; } static bool check_key_props(cli_rnp_t * rnp, const char *uid, const char *primary_alg, const char *sub_alg, const char *primary_curve, const char *sub_curve, int bits, int sub_bits, const char *hash) { rnp_key_handle_t key = NULL; rnp_key_handle_t subkey = NULL; rnp_signature_handle_t sig = NULL; uint32_t kbits = 0; char * str = NULL; bool res = false; /* check primary key properties */ if (rnp_locate_key(rnp->ffi, "userid", uid, &key) || !key) { return false; } if (rnp_key_get_alg(key, &str) || strcmp(str, primary_alg)) { goto done; } rnp_buffer_destroy(str); str = NULL; if (primary_curve && (rnp_key_get_curve(key, &str) || strcmp(str, primary_curve))) { goto done; } rnp_buffer_destroy(str); str = NULL; if (bits && (rnp_key_get_bits(key, &kbits) || (bits != (int) kbits))) { goto done; } /* check subkey properties */ if (!sub_alg) { res = true; goto done; } if (rnp_key_get_subkey_at(key, 0, &subkey)) { goto done; } if (rnp_key_get_alg(subkey, &str) || strcmp(str, sub_alg)) { goto done; } rnp_buffer_destroy(str); str = NULL; if (sub_curve && (rnp_key_get_curve(subkey, &str) || strcmp(str, sub_curve))) { goto done; } rnp_buffer_destroy(str); str = NULL; if (sub_bits && (rnp_key_get_bits(subkey, &kbits) || (sub_bits != (int) kbits))) { goto done; } if (rnp_key_get_signature_at(subkey, 0, &sig) || !sig) { goto done; } if (rnp_signature_get_hash_alg(sig, &str) || strcmp(str, hash)) { goto done; } res = true; done: rnp_signature_handle_destroy(sig); rnp_key_handle_destroy(key); rnp_key_handle_destroy(subkey); rnp_buffer_destroy(str); return res; } static bool check_cfg_props(const rnp_cfg &cfg, const char * primary_alg, const char * sub_alg, const char * primary_curve, const char * sub_curve, int bits, int sub_bits) { if (cfg.get_str(CFG_KG_PRIMARY_ALG) != primary_alg) { return false; } if (cfg.get_str(CFG_KG_SUBKEY_ALG) != sub_alg) { return false; } if (primary_curve && (cfg.get_str(CFG_KG_PRIMARY_CURVE) != primary_curve)) { return false; } if (sub_curve && (cfg.get_str(CFG_KG_SUBKEY_CURVE) != sub_curve)) { return false; } if (bits && (cfg.get_int(CFG_KG_PRIMARY_BITS) != bits)) { return false; } if (sub_bits && (cfg.get_int(CFG_KG_SUBKEY_BITS) != sub_bits)) { return false; } return true; } TEST_F(rnp_tests, rnpkeys_generatekey_testExpertMode) { cli_rnp_t rnp; rnp_cfg ops; ops.set_bool(CFG_EXPERT, true); ops.set_int(CFG_S2K_ITER, 1); /* ecdsa/ecdh p256 keypair */ ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_ecdsa_p256"); assert_true(ask_expert_details(&rnp, ops, "19\n1\n")); assert_false(check_cfg_props(ops, "ECDH", "ECDH", "NIST P-256", "NIST P-256", 0, 0)); assert_false(check_cfg_props(ops, "ECDSA", "ECDSA", "NIST P-256", "NIST P-256", 0, 0)); assert_false(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-384", "NIST P-256", 0, 0)); assert_false(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-256", "NIST P-384", 0, 0)); assert_false(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-256", "NIST P-256", 1024, 0)); assert_false(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-256", "NIST P-256", 0, 1024)); assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-256", "NIST P-256", 0, 0)); assert_true(check_key_props( &rnp, "expert_ecdsa_p256", "ECDSA", "ECDH", "NIST P-256", "NIST P-256", 0, 0, "SHA256")); rnp.end(); /* ecdsa/ecdh p384 keypair */ ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_ecdsa_p384"); assert_true(ask_expert_details(&rnp, ops, "19\n2\n")); assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0)); assert_false(check_key_props( &rnp, "expert_ecdsa_p256", "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0, "SHA384")); assert_true(check_key_props( &rnp, "expert_ecdsa_p384", "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0, "SHA384")); rnp.end(); /* ecdsa/ecdh p521 keypair */ ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_ecdsa_p521"); assert_true(ask_expert_details(&rnp, ops, "19\n3\n")); assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-521", "NIST P-521", 0, 0)); assert_true(check_key_props( &rnp, "expert_ecdsa_p521", "ECDSA", "ECDH", "NIST P-521", "NIST P-521", 0, 0, "SHA512")); rnp.end(); /* ecdsa/ecdh brainpool256 keypair */ ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_ecdsa_bp256"); assert_true(ask_expert_details(&rnp, ops, "19\n4\n")); if (brainpool_enabled()) { assert_true( check_cfg_props(ops, "ECDSA", "ECDH", "brainpoolP256r1", "brainpoolP256r1", 0, 0)); assert_true(check_key_props(&rnp, "expert_ecdsa_bp256", "ECDSA", "ECDH", "brainpoolP256r1", "brainpoolP256r1", 0, 0, "SHA256")); } else { /* secp256k1 will be selected instead */ assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "secp256k1", "secp256k1", 0, 0)); assert_true(check_key_props(&rnp, "expert_ecdsa_bp256", "ECDSA", "ECDH", "secp256k1", "secp256k1", 0, 0, "SHA256")); } rnp.end(); /* ecdsa/ecdh brainpool384 keypair */ ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_ecdsa_bp384"); if (brainpool_enabled()) { assert_true(ask_expert_details(&rnp, ops, "19\n5\n")); assert_true( check_cfg_props(ops, "ECDSA", "ECDH", "brainpoolP384r1", "brainpoolP384r1", 0, 0)); assert_true(check_key_props(&rnp, "expert_ecdsa_bp384", "ECDSA", "ECDH", "brainpoolP384r1", "brainpoolP384r1", 0, 0, "SHA384")); } else { assert_false(ask_expert_details(&rnp, ops, "19\n5\n")); } rnp.end(); /* ecdsa/ecdh brainpool512 keypair */ ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_ecdsa_bp512"); if (brainpool_enabled()) { assert_true(ask_expert_details(&rnp, ops, "19\n6\n")); assert_true( check_cfg_props(ops, "ECDSA", "ECDH", "brainpoolP512r1", "brainpoolP512r1", 0, 0)); assert_true(check_key_props(&rnp, "expert_ecdsa_bp512", "ECDSA", "ECDH", "brainpoolP512r1", "brainpoolP512r1", 0, 0, "SHA512")); } else { assert_false(ask_expert_details(&rnp, ops, "19\n6\n")); } rnp.end(); /* ecdsa/ecdh secp256k1 keypair */ ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_ecdsa_p256k1"); if (brainpool_enabled()) { assert_true(ask_expert_details(&rnp, ops, "19\n7\n")); assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "secp256k1", "secp256k1", 0, 0)); assert_true(check_key_props(&rnp, "expert_ecdsa_p256k1", "ECDSA", "ECDH", "secp256k1", "secp256k1", 0, 0, "SHA256")); } else { assert_false(ask_expert_details(&rnp, ops, "19\n7\n")); } rnp.end(); /* eddsa/x25519 keypair */ ops.clear(); ops.set_bool(CFG_EXPERT, true); ops.set_int(CFG_S2K_ITER, 1); ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_eddsa_ecdh"); assert_true(ask_expert_details(&rnp, ops, "22\n")); assert_true(check_cfg_props(ops, "EDDSA", "ECDH", NULL, "Curve25519", 0, 0)); assert_true(check_key_props( &rnp, "expert_eddsa_ecdh", "EDDSA", "ECDH", "Ed25519", "Curve25519", 0, 0, "SHA256")); rnp.end(); /* rsa/rsa 1024 key */ ops.clear(); ops.set_bool(CFG_EXPERT, true); ops.set_int(CFG_S2K_ITER, 1); ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_rsa_1024"); assert_true(ask_expert_details(&rnp, ops, "1\n1024\n")); assert_true(check_cfg_props(ops, "RSA", "RSA", NULL, NULL, 1024, 1024)); assert_true(check_key_props( &rnp, "expert_rsa_1024", "RSA", "RSA", NULL, NULL, 1024, 1024, "SHA256")); rnp.end(); /* rsa 4096 key, asked twice */ ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_rsa_4096"); assert_true(ask_expert_details(&rnp, ops, "1\n1023\n4096\n")); assert_true(check_cfg_props(ops, "RSA", "RSA", NULL, NULL, 4096, 4096)); assert_true(check_key_props( &rnp, "expert_rsa_4096", "RSA", "RSA", NULL, NULL, 4096, 4096, "SHA256")); rnp.end(); /* sm2 key */ ops.clear(); ops.set_bool(CFG_EXPERT, true); ops.set_int(CFG_S2K_ITER, 1); ops.unset(CFG_USERID); ops.add_str(CFG_USERID, "expert_sm2"); if (!sm2_enabled()) { assert_false(ask_expert_details(&rnp, ops, "99\n")); } else { assert_true(ask_expert_details(&rnp, ops, "99\n")); assert_true(check_cfg_props(ops, "SM2", "SM2", NULL, NULL, 0, 0)); assert_true(check_key_props( &rnp, "expert_sm2", "SM2", "SM2", "SM2 P-256", "SM2 P-256", 0, 0, "SM3")); } rnp.end(); } TEST_F(rnp_tests, generatekeyECDSA_explicitlySetSmallOutputDigest_DigestAlgAdjusted) { cli_rnp_t rnp; rnp_cfg ops; ops.set_bool(CFG_EXPERT, true); ops.set_str(CFG_HASH, "SHA1"); ops.set_bool(CFG_WEAK_HASH, true); ops.set_int(CFG_S2K_ITER, 1); ops.add_str(CFG_USERID, "expert_small_digest"); assert_true(ask_expert_details(&rnp, ops, "19\n2\n")); assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0)); assert_true(check_key_props(&rnp, "expert_small_digest", "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0, "SHA384")); rnp.end(); } TEST_F(rnp_tests, generatekey_multipleUserIds_ShouldFail) { cli_rnp_t rnp; rnp_cfg ops; ops.set_bool(CFG_EXPERT, true); ops.set_int(CFG_S2K_ITER, 1); ops.add_str(CFG_USERID, "multi_userid_1"); ops.add_str(CFG_USERID, "multi_userid_2"); assert_false(ask_expert_details(&rnp, ops, "1\n1024\n")); rnp.end(); } TEST_F(rnp_tests, generatekeyECDSA_explicitlySetBiggerThanNeededDigest_ShouldSuceed) { cli_rnp_t rnp; rnp_cfg ops; ops.set_bool(CFG_EXPERT, true); ops.set_str(CFG_HASH, "SHA512"); ops.set_int(CFG_S2K_ITER, 1); ops.add_str(CFG_USERID, "expert_large_digest"); assert_true(ask_expert_details(&rnp, ops, "19\n2\n")); assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0)); assert_true(check_key_props(&rnp, "expert_large_digest", "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0, "SHA512")); rnp.end(); } TEST_F(rnp_tests, generatekeyECDSA_explicitlySetUnknownDigest_ShouldFail) { cli_rnp_t rnp; rnp_cfg ops; ops.set_bool(CFG_EXPERT, true); ops.set_str(CFG_HASH, "WRONG_DIGEST_ALGORITHM"); ops.set_int(CFG_S2K_ITER, 1); // Finds out that hash doesn't exist and returns an error assert_false(ask_expert_details(&rnp, ops, "19\n2\n")); rnp.end(); } /* This tests some of the mid-level key generation functions and their * generated sigs in the keyring. */ TEST_F(rnp_tests, test_generated_key_sigs) { rnp_key_store_t *pubring = new rnp_key_store_t(global_ctx); rnp_key_store_t *secring = new rnp_key_store_t(global_ctx); pgp_key_t * primary_pub = NULL, *primary_sec = NULL; pgp_key_t * sub_pub = NULL, *sub_sec = NULL; // primary { pgp_key_t pub; pgp_key_t sec; rnp_keygen_primary_desc_t desc; pgp_sig_subpkt_t * subpkt = NULL; pgp_signature_t * psig = NULL; pgp_signature_t * ssig = NULL; pgp_signature_info_t psiginfo = {}; pgp_signature_info_t ssiginfo = {}; desc.crypto.key_alg = PGP_PKA_RSA; desc.crypto.rsa.modulus_bit_len = 1024; desc.crypto.ctx = &global_ctx; desc.cert.userid = "test"; // generate assert_true(pgp_generate_primary_key(desc, true, sec, pub, PGP_KEY_STORE_GPG)); // add to our rings assert_true(rnp_key_store_add_key(pubring, &pub)); assert_true(rnp_key_store_add_key(secring, &sec)); // retrieve back from our rings (for later) primary_pub = rnp_tests_get_key_by_grip(pubring, pub.grip()); primary_sec = rnp_tests_get_key_by_grip(secring, pub.grip()); assert_non_null(primary_pub); assert_non_null(primary_sec); assert_true(primary_pub->valid()); assert_true(primary_pub->validated()); assert_false(primary_pub->expired()); assert_true(primary_sec->valid()); assert_true(primary_sec->validated()); assert_false(primary_sec->expired()); // check packet and subsig counts assert_int_equal(3, pub.rawpkt_count()); assert_int_equal(3, sec.rawpkt_count()); assert_int_equal(1, pub.sig_count()); assert_int_equal(1, sec.sig_count()); psig = &pub.get_sig(0).sig; ssig = &sec.get_sig(0).sig; // make sure our sig MPI is not NULL assert_int_not_equal(psig->material_len, 0); assert_int_not_equal(ssig->material_len, 0); // make sure we're targeting the right packet assert_int_equal(PGP_PKT_SIGNATURE, pub.get_sig(0).rawpkt.tag); assert_int_equal(PGP_PKT_SIGNATURE, sec.get_sig(0).rawpkt.tag); // validate the userid self-sig psiginfo.sig = psig; pub.validate_cert(psiginfo, pub.pkt(), pub.get_uid(0).pkt, global_ctx); assert_true(psiginfo.valid); assert_true(psig->keyfp() == pub.fp()); // check subpackets and their contents subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR); assert_non_null(subpkt); assert_true(subpkt->hashed); subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false); assert_non_null(subpkt); assert_false(subpkt->hashed); assert_int_equal(0, memcmp(subpkt->fields.issuer, pub.keyid().data(), PGP_KEY_ID_SIZE)); subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_CREATION_TIME); assert_non_null(subpkt); assert_true(subpkt->hashed); assert_true(subpkt->fields.create <= time(NULL)); ssiginfo.sig = ssig; sec.validate_cert(ssiginfo, sec.pkt(), sec.get_uid(0).pkt, global_ctx); assert_true(ssiginfo.valid); assert_true(ssig->keyfp() == sec.fp()); // modify a hashed portion of the sig packets psig->hashed_data[32] ^= 0xff; ssig->hashed_data[32] ^= 0xff; // ensure validation fails pub.validate_cert(psiginfo, pub.pkt(), pub.get_uid(0).pkt, global_ctx); assert_false(psiginfo.valid); sec.validate_cert(ssiginfo, sec.pkt(), sec.get_uid(0).pkt, global_ctx); assert_false(ssiginfo.valid); // restore the original data psig->hashed_data[32] ^= 0xff; ssig->hashed_data[32] ^= 0xff; // ensure validation fails with incorrect uid pgp_userid_pkt_t uid; uid.tag = PGP_PKT_USER_ID; uid.uid = (uint8_t *) malloc(4); assert_non_null(uid.uid); uid.uid_len = 4; memcpy(uid.uid, "fake", 4); pub.validate_cert(psiginfo, pub.pkt(), uid, global_ctx); assert_false(psiginfo.valid); sec.validate_cert(ssiginfo, sec.pkt(), uid, global_ctx); assert_false(ssiginfo.valid); // validate via an alternative method // primary_pub + pubring primary_pub->validate(*pubring); assert_true(primary_pub->valid()); assert_true(primary_pub->validated()); assert_false(primary_pub->expired()); // primary_sec + pubring primary_sec->validate(*pubring); assert_true(primary_sec->valid()); assert_true(primary_sec->validated()); assert_false(primary_sec->expired()); // primary_pub + secring primary_pub->validate(*secring); assert_true(primary_pub->valid()); assert_true(primary_pub->validated()); assert_false(primary_pub->expired()); // primary_sec + secring primary_sec->validate(*secring); assert_true(primary_sec->valid()); assert_true(primary_sec->validated()); assert_false(primary_sec->expired()); // modify a hashed portion of the sig packet, offset may change in future pgp_subsig_t &sig = primary_pub->get_sig(0); sig.sig.hashed_data[10] ^= 0xff; sig.validity.validated = false; // ensure validation fails primary_pub->validate(*pubring); assert_false(primary_pub->valid()); assert_true(primary_pub->validated()); assert_false(primary_pub->expired()); // restore the original data sig.sig.hashed_data[10] ^= 0xff; sig.validity.validated = false; primary_pub->validate(*pubring); assert_true(primary_pub->valid()); assert_true(primary_pub->validated()); assert_false(primary_pub->expired()); } // sub { pgp_key_t pub; pgp_key_t sec; rnp_keygen_subkey_desc_t desc; pgp_sig_subpkt_t * subpkt = NULL; pgp_signature_t * psig = NULL; pgp_signature_t * ssig = NULL; pgp_signature_info_t psiginfo = {}; pgp_signature_info_t ssiginfo = {}; memset(&desc, 0, sizeof(desc)); desc.crypto.key_alg = PGP_PKA_RSA; desc.crypto.rsa.modulus_bit_len = 1024; desc.crypto.ctx = &global_ctx; // generate pgp_password_provider_t prov = {}; assert_true(pgp_generate_subkey( desc, true, *primary_sec, *primary_pub, sec, pub, prov, PGP_KEY_STORE_GPG)); assert_true(pub.valid()); assert_true(pub.validated()); assert_false(pub.expired()); assert_true(sec.valid()); assert_true(sec.validated()); assert_false(sec.expired()); // check packet and subsig counts assert_int_equal(2, pub.rawpkt_count()); assert_int_equal(2, sec.rawpkt_count()); assert_int_equal(1, pub.sig_count()); assert_int_equal(1, sec.sig_count()); psig = &pub.get_sig(0).sig; ssig = &sec.get_sig(0).sig; // make sure our sig MPI is not NULL assert_int_not_equal(psig->material_len, 0); assert_int_not_equal(ssig->material_len, 0); // make sure we're targeting the right packet assert_int_equal(PGP_PKT_SIGNATURE, pub.get_sig(0).rawpkt.tag); assert_int_equal(PGP_PKT_SIGNATURE, sec.get_sig(0).rawpkt.tag); // validate the binding sig psiginfo.sig = psig; primary_pub->validate_binding(psiginfo, pub, global_ctx); assert_true(psiginfo.valid); assert_true(psig->keyfp() == primary_pub->fp()); // check subpackets and their contents subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR); assert_non_null(subpkt); assert_true(subpkt->hashed); subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false); assert_non_null(subpkt); assert_false(subpkt->hashed); assert_int_equal( 0, memcmp(subpkt->fields.issuer, primary_pub->keyid().data(), PGP_KEY_ID_SIZE)); subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_CREATION_TIME); assert_non_null(subpkt); assert_true(subpkt->hashed); assert_true(subpkt->fields.create <= time(NULL)); ssiginfo.sig = ssig; primary_pub->validate_binding(ssiginfo, sec, global_ctx); assert_true(ssiginfo.valid); assert_true(ssig->keyfp() == primary_sec->fp()); // modify a hashed portion of the sig packets psig->hashed_data[10] ^= 0xff; ssig->hashed_data[10] ^= 0xff; // ensure validation fails primary_pub->validate_binding(psiginfo, pub, global_ctx); assert_false(psiginfo.valid); primary_pub->validate_binding(ssiginfo, sec, global_ctx); assert_false(ssiginfo.valid); // restore the original data psig->hashed_data[10] ^= 0xff; ssig->hashed_data[10] ^= 0xff; // add to our rings assert_true(rnp_key_store_add_key(pubring, &pub)); assert_true(rnp_key_store_add_key(secring, &sec)); // retrieve back from our rings sub_pub = rnp_tests_get_key_by_grip(pubring, pub.grip()); sub_sec = rnp_tests_get_key_by_grip(secring, pub.grip()); assert_non_null(sub_pub); assert_non_null(sub_sec); assert_true(sub_pub->valid()); assert_true(sub_pub->validated()); assert_false(sub_pub->expired()); assert_true(sub_sec->valid()); assert_true(sub_sec->validated()); assert_false(sub_sec->expired()); // validate via an alternative method sub_pub->validate(*pubring); assert_true(sub_pub->valid()); assert_true(sub_pub->validated()); assert_false(sub_pub->expired()); sub_sec->validate(*pubring); assert_true(sub_sec->valid()); assert_true(sub_sec->validated()); assert_false(sub_sec->expired()); } delete pubring; delete secring; }