Revision control

Copy as Markdown

Other Tools

/*
* 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 <rekey/rnp_key_store.h>
#include <rnp/rnpcfg.h>
#include <rnpkeys/rnpkeys.h>
#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 <fstream>
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<rnp_key_handle_t> 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;
}