Revision control

Copy as Markdown

Other Tools

/*
* Copyright (c) 2020-2023 [Ribose Inc](https://www.ribose.com).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <fstream>
#include <vector>
#include <string>
#include <algorithm>
#include <rnp/rnp.h>
#include "rnp_tests.h"
#include "support.h"
#include "librepgp/stream-common.h"
#include "librepgp/stream-packet.h"
#include "librepgp/stream-sig.h"
#include <nlohmann/json.hpp>
#include <vector>
#include <string>
#include "file-utils.h"
#include <librepgp/stream-ctx.h>
#include "key.hpp"
#include "ffi-priv-types.h"
static bool
getpasscb_once(rnp_ffi_t ffi,
void * app_ctx,
rnp_key_handle_t key,
const char * pgp_context,
char * buf,
size_t buf_len)
{
const char **pass = (const char **) app_ctx;
if (!*pass) {
return false;
}
size_t pass_len = strlen(*pass);
if (pass_len >= buf_len) {
return false;
}
memcpy(buf, *pass, pass_len);
*pass = NULL;
return true;
}
static bool
getpasscb_inc(rnp_ffi_t ffi,
void * app_ctx,
rnp_key_handle_t key,
const char * pgp_context,
char * buf,
size_t buf_len)
{
int * num = (int *) app_ctx;
std::string pass = "pass" + std::to_string(*num);
(*num)++;
strncpy(buf, pass.c_str(), buf_len - 1);
return true;
}
#define TBL_MAX_USERIDS 4
typedef struct key_tbl_t {
const uint8_t *key_data;
size_t key_data_size;
bool secret;
const char * keyid;
const char * grip;
const char * userids[TBL_MAX_USERIDS + 1];
} key_tbl_t;
static void
tbl_getkeycb(rnp_ffi_t ffi,
void * app_ctx,
const char *identifier_type,
const char *identifier,
bool secret)
{
key_tbl_t *found = NULL;
for (key_tbl_t *tbl = (key_tbl_t *) app_ctx; tbl && tbl->key_data && !found; tbl++) {
if (tbl->secret != secret) {
continue;
}
if (!strcmp(identifier_type, "keyid") && !strcmp(identifier, tbl->keyid)) {
found = tbl;
break;
} else if (!strcmp(identifier_type, "grip") && !strcmp(identifier, tbl->grip)) {
found = tbl;
break;
} else if (!strcmp(identifier_type, "userid")) {
for (size_t i = 0; i < TBL_MAX_USERIDS; i++) {
if (!strcmp(identifier, tbl->userids[i])) {
found = tbl;
break;
}
}
}
}
if (found) {
char *format = NULL;
assert_rnp_success(
rnp_detect_key_format(found->key_data, found->key_data_size, &format));
assert_non_null(format);
uint32_t flags = secret ? RNP_LOAD_SAVE_SECRET_KEYS : RNP_LOAD_SAVE_PUBLIC_KEYS;
rnp_input_t input = NULL;
assert_rnp_success(
rnp_input_from_memory(&input, found->key_data, found->key_data_size, true));
assert_non_null(input);
assert_rnp_success(rnp_load_keys(ffi, format, input, flags));
free(format);
assert_rnp_success(rnp_input_destroy(input));
input = NULL;
}
}
// Portable byte search (memmem is POSIX-only, not available on MSVC).
static const void *
portable_memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen)
{
if (needlelen == 0 || haystacklen < needlelen) {
return NULL;
}
auto h = static_cast<const uint8_t *>(haystack);
auto n = static_cast<const uint8_t *>(needle);
auto end = h + haystacklen - needlelen + 1;
for (auto p = h; p < end; p++) {
if (memcmp(p, n, needlelen) == 0) {
return p;
}
}
return NULL;
}
TEST_F(rnp_tests, test_ffi_encrypt_pass)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t op = NULL;
const char * plaintext = "data1";
// setup FFI
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
// load our keyrings
assert_true(
load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));
// write out some data
str_to_file("plaintext", plaintext);
// create input+output w/ bad paths (should fail)
input = NULL;
assert_rnp_failure(rnp_input_from_stdin(NULL));
assert_rnp_failure(rnp_input_from_path(NULL, "noexist"));
assert_rnp_failure(rnp_input_from_path(&input, NULL));
assert_rnp_failure(rnp_input_from_path(&input, "noexist"));
assert_null(input);
assert_rnp_failure(rnp_output_to_path(&output, ""));
assert_null(output);
assert_rnp_failure(rnp_output_to_path(NULL, "path"));
assert_rnp_failure(rnp_output_to_path(&output, NULL));
// create input+output
assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
assert_non_null(output);
// create encrypt operation
assert_rnp_failure(rnp_op_encrypt_create(NULL, ffi, input, output));
assert_rnp_failure(rnp_op_encrypt_create(&op, NULL, input, output));
assert_rnp_failure(rnp_op_encrypt_create(&op, ffi, NULL, output));
assert_rnp_failure(rnp_op_encrypt_create(&op, ffi, input, NULL));
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
// add password (using all defaults)
assert_rnp_failure(rnp_op_encrypt_add_password(NULL, "pass1", NULL, 0, NULL));
assert_rnp_failure(rnp_op_encrypt_add_password(op, "", NULL, 0, NULL));
assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass1", "WRONG", 0, NULL));
assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass1", NULL, 0, "WRONG"));
assert_rnp_success(rnp_op_encrypt_add_password(op, "pass1", NULL, 0, NULL));
// Allow insecure ciphers
if (blowfish_enabled()) {
assert_rnp_success(rnp_remove_security_rule(
ffi, RNP_FEATURE_SYMM_ALG, "BLOWFISH", 0, RNP_SECURITY_REMOVE_ALL, 0, nullptr));
}
if (cast5_enabled()) {
assert_rnp_success(rnp_remove_security_rule(
ffi, RNP_FEATURE_SYMM_ALG, "CAST5", 0, RNP_SECURITY_REMOVE_ALL, 0, nullptr));
}
// add password
if (!sm2_enabled() && !twofish_enabled()) {
assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
assert_rnp_failure(
rnp_op_encrypt_add_password(op, "pass2", "SHA256", 12345, "TWOFISH"));
const char *alg = blowfish_enabled() ? "BLOWFISH" : "AES256";
assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SHA256", 12345, alg));
} else if (!sm2_enabled() && twofish_enabled()) {
assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
assert_rnp_success(
rnp_op_encrypt_add_password(op, "pass2", "SHA256", 12345, "TWOFISH"));
} else if (sm2_enabled() && !twofish_enabled()) {
assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
const char *alg = blowfish_enabled() ? "BLOWFISH" : "AES256";
assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, alg));
} else {
assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
}
// set the data encryption cipher
assert_rnp_failure(rnp_op_encrypt_set_cipher(NULL, "CAST5"));
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, NULL));
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "WRONG"));
if (cast5_enabled()) {
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
} else {
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
}
// execute the operation
assert_rnp_failure(rnp_op_encrypt_execute(NULL));
assert_rnp_success(rnp_op_encrypt_execute(op));
// make sure the output file was created
assert_true(rnp_file_exists("encrypted"));
// cleanup
assert_rnp_success(rnp_input_destroy(input));
input = NULL;
assert_rnp_success(rnp_output_destroy(output));
output = NULL;
assert_rnp_success(rnp_op_encrypt_destroy(op));
op = NULL;
/* decrypt */
// decrypt (no pass provider, should fail)
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_rnp_failure(rnp_ffi_set_pass_provider(NULL, NULL, NULL));
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
assert_rnp_failure(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// decrypt (wrong pass, should fail)
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
const char *pass = "wrong1";
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_once, &pass));
assert_rnp_failure(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// decrypt (pass1)
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass1"));
assert_rnp_failure(rnp_decrypt(NULL, input, output));
assert_rnp_failure(rnp_decrypt(ffi, NULL, output));
assert_rnp_failure(rnp_decrypt(ffi, input, NULL));
assert_rnp_success(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// compare the decrypted file
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
unlink("decrypted");
// decrypt (pass2)
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass2"));
assert_rnp_success(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// compare the decrypted file
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
// final cleanup
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_encrypt_pass_provider)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t op = NULL;
const char *plaintext = "Data encrypted with password provided via password provider.";
// setup FFI
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
// write out some data
str_to_file("plaintext", plaintext);
// create input + output
assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
// create encrypt operation
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
// add password with NULL password provider set - should fail
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, NULL, 0, NULL));
// add password with password provider set.
int pswdnum = 1;
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_inc, &pswdnum));
assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, NULL, 0, NULL));
// add another password with different encryption parameters
if (!sm2_enabled() && !twofish_enabled()) {
assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SHA256", 12345, "TWOFISH"));
assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, NULL, 12345, NULL));
} else if (!sm2_enabled() && twofish_enabled()) {
assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, "SHA256", 12345, "TWOFISH"));
} else if (sm2_enabled() && !twofish_enabled()) {
assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, NULL));
} else {
assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
}
// set the data encryption cipher
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAMELLIA256"));
// execute the operation
assert_rnp_success(rnp_op_encrypt_execute(op));
// make sure the output file was created
assert_true(rnp_file_exists("encrypted"));
// cleanup
assert_rnp_success(rnp_input_destroy(input));
input = NULL;
assert_rnp_success(rnp_output_destroy(output));
output = NULL;
assert_rnp_success(rnp_op_encrypt_destroy(op));
op = NULL;
/* decrypt with pass1 */
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass1"));
assert_rnp_success(rnp_decrypt(ffi, input, output));
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
unlink("decrypted");
/* decrypt with pass2 via provider */
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
pswdnum = 2;
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_inc, &pswdnum));
assert_rnp_success(rnp_decrypt(ffi, input, output));
rnp_input_destroy(input);
rnp_output_destroy(output);
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
unlink("decrypted");
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_encrypt_set_cipher)
{
/* setup FFI */
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
/* create input + output */
rnp_input_t input = NULL;
const char *plaintext = "Data encrypted with password using different CEK/KEK.";
assert_rnp_failure(
rnp_input_from_memory(NULL, (const uint8_t *) plaintext, strlen(plaintext), false));
assert_rnp_failure(rnp_input_from_memory(&input, NULL, strlen(plaintext), false));
assert_rnp_success(rnp_input_from_memory(&input, (const uint8_t *) plaintext, 0, false));
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(
rnp_input_from_memory(&input, (const uint8_t *) plaintext, strlen(plaintext), false));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
/* create encrypt operation */
rnp_op_encrypt_t op = NULL;
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
/* use different sym algos */
assert_rnp_success(rnp_op_encrypt_add_password(op, "password1", NULL, 0, "AES192"));
assert_rnp_success(rnp_op_encrypt_add_password(op, "password2", NULL, 0, "AES128"));
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
/* execute the operation */
assert_rnp_success(rnp_op_encrypt_execute(op));
assert_true(rnp_file_exists("encrypted"));
/* cleanup */
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
assert_rnp_success(rnp_op_encrypt_destroy(op));
/* decrypt with password1 */
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password1"));
rnp_op_verify_t verify;
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_failure(rnp_op_verify_set_flags(NULL, RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT));
assert_rnp_failure(rnp_op_verify_set_flags(verify, 0x77));
assert_rnp_success(rnp_op_verify_set_flags(verify, RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT));
assert_rnp_success(rnp_op_verify_set_flags(verify, 0));
assert_rnp_success(rnp_op_verify_execute(verify));
rnp_input_destroy(input);
rnp_output_destroy(output);
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
/* Check protection info */
char *mode = NULL;
char *cipher = NULL;
bool valid = false;
assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
assert_string_equal(mode, "cfb-mdc");
assert_string_equal(cipher, "AES256");
assert_true(valid);
rnp_buffer_destroy(mode);
rnp_buffer_destroy(cipher);
/* Check SESKs */
size_t count = 0;
assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
assert_int_equal(count, 2);
/* First SESK: AES192 */
rnp_symenc_handle_t symenc = NULL;
assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
char *aalg = NULL;
assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
assert_string_equal(aalg, "None");
assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
assert_string_equal(cipher, "AES192");
rnp_buffer_destroy(aalg);
rnp_buffer_destroy(cipher);
/* Second SESK: AES128 */
assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
assert_string_equal(aalg, "None");
assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
assert_string_equal(cipher, "AES128");
rnp_buffer_destroy(aalg);
rnp_buffer_destroy(cipher);
unlink("decrypted");
unlink("encrypted");
rnp_op_verify_destroy(verify);
/* Now use AEAD */
assert_rnp_success(
rnp_input_from_memory(&input, (const uint8_t *) plaintext, strlen(plaintext), false));
assert_rnp_success(rnp_output_to_path(&output, "encrypted-aead"));
/* create encrypt operation */
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
/* use different sym algos */
assert_rnp_success(rnp_op_encrypt_add_password(op, "password1", NULL, 0, "AES256"));
assert_rnp_success(rnp_op_encrypt_add_password(op, "password2", NULL, 0, "AES192"));
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES128"));
assert_rnp_success(rnp_op_encrypt_set_aead(op, "OCB"));
/* execute the operation */
assert_rnp_success(rnp_op_encrypt_execute(op));
assert_true(rnp_file_exists("encrypted-aead"));
/* cleanup */
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
assert_rnp_success(rnp_op_encrypt_destroy(op));
/* decrypt with password2 */
assert_rnp_success(rnp_input_from_path(&input, "encrypted-aead"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password2"));
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
rnp_input_destroy(input);
rnp_output_destroy(output);
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
/* Check protection info */
assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
assert_string_equal(mode, "aead-ocb");
assert_string_equal(cipher, "AES128");
assert_true(valid);
rnp_buffer_destroy(mode);
rnp_buffer_destroy(cipher);
/* Check SESKs */
assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
assert_int_equal(count, 2);
/* First SESK: AES192 */
assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
assert_string_equal(aalg, "OCB");
assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
assert_string_equal(cipher, "AES256");
rnp_buffer_destroy(aalg);
rnp_buffer_destroy(cipher);
/* Second SESK: AES128 */
assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
assert_string_equal(aalg, "OCB");
assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
assert_string_equal(cipher, "AES192");
rnp_buffer_destroy(aalg);
rnp_buffer_destroy(cipher);
unlink("decrypted");
unlink("encrypted-aead");
rnp_op_verify_destroy(verify);
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_encrypt_empty_buffer)
{
/* setup FFI */
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
/* create input + output */
rnp_input_t input = NULL;
const char *plaintext = "";
assert_rnp_success(
rnp_input_from_memory(&input, (const uint8_t *) plaintext, strlen(plaintext), false));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
/* create encrypt operation */
rnp_op_encrypt_t op = NULL;
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
assert_rnp_success(rnp_op_encrypt_add_password(op, "password1", NULL, 0, "AES192"));
/* execute the operation */
assert_rnp_success(rnp_op_encrypt_execute(op));
assert_true(rnp_file_exists("encrypted"));
/* cleanup */
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
assert_rnp_success(rnp_op_encrypt_destroy(op));
/* decrypt with password1 */
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password1"));
rnp_input_destroy(input);
rnp_output_destroy(output);
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
unlink("decrypted");
unlink("encrypted");
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_encrypt_null_buffer)
{
/* setup FFI */
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
/* create input + output */
rnp_input_t input = NULL;
assert_rnp_failure(rnp_input_from_memory(&input, NULL, 10 /*not zero value*/, false));
assert_rnp_success(rnp_input_from_memory(&input, NULL, 0, false));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
/* create encrypt operation */
rnp_op_encrypt_t op = NULL;
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
assert_rnp_success(rnp_op_encrypt_add_password(op, "password1", NULL, 0, "AES192"));
/* execute the operation */
assert_rnp_success(rnp_op_encrypt_execute(op));
assert_true(rnp_file_exists("encrypted"));
/* cleanup */
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
assert_rnp_success(rnp_op_encrypt_destroy(op));
/* decrypt with password1 */
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password1"));
rnp_input_destroy(input);
rnp_output_destroy(output);
assert_string_equal(file_to_str("decrypted").c_str(), "");
unlink("decrypted");
unlink("encrypted");
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_encrypt_pk)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t op = NULL;
const char * plaintext = "data1";
// setup FFI
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
// load our keyrings
assert_true(
load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));
// write out some data
str_to_file("plaintext", plaintext);
// create input+output
assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
assert_non_null(output);
// create encrypt operation
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
// add recipients
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
assert_rnp_failure(rnp_op_encrypt_add_recipient(NULL, key));
assert_rnp_failure(rnp_op_encrypt_add_recipient(op, NULL));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
rnp_key_handle_destroy(key);
key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
rnp_key_handle_destroy(key);
key = NULL;
// set the data encryption cipher
if (cast5_enabled()) {
assert_rnp_success(rnp_remove_security_rule(
ffi, RNP_FEATURE_SYMM_ALG, "CAST5", 0, RNP_SECURITY_REMOVE_ALL, 0, nullptr));
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
} else {
assert_rnp_failure(rnp_remove_security_rule(
ffi, RNP_FEATURE_SYMM_ALG, "CAST5", 0, RNP_SECURITY_REMOVE_ALL, 0, nullptr));
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
}
// execute the operation
assert_rnp_success(rnp_op_encrypt_execute(op));
// make sure the output file was created
assert_true(rnp_file_exists("encrypted"));
// cleanup
assert_rnp_success(rnp_input_destroy(input));
input = NULL;
assert_rnp_success(rnp_output_destroy(output));
output = NULL;
assert_rnp_success(rnp_op_encrypt_destroy(op));
op = NULL;
/* decrypt */
// decrypt (no pass provider, should fail)
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
assert_rnp_failure(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// decrypt (wrong pass, should fail)
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
const char *pass = "wrong1";
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_once, &pass));
assert_rnp_failure(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// decrypt
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
assert_rnp_success(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// read in the decrypted file
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
// final cleanup
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_select_deprecated_ciphers)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t op = NULL;
const char * plaintext = "data1";
// setup FFI
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_rnp_success(
rnp_input_from_memory(&input, (const uint8_t *) plaintext, strlen(plaintext), false));
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
// create encrypt operation
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
/* check predefined rules */
uint32_t flags = 0;
uint64_t from = 0;
uint32_t level = 0;
if (cast5_enabled()) {
assert_rnp_success(rnp_get_security_rule(ffi,
RNP_FEATURE_SYMM_ALG,
"CAST5",
CAST5_3DES_IDEA_BLOWFISH_FROM + 1,
&flags,
&from,
&level));
assert_int_equal(from, CAST5_3DES_IDEA_BLOWFISH_FROM);
assert_int_equal(level, RNP_SECURITY_INSECURE);
}
assert_rnp_success(rnp_get_security_rule(ffi,
RNP_FEATURE_SYMM_ALG,
"TRIPLEDES",
CAST5_3DES_IDEA_BLOWFISH_FROM + 1,
&flags,
&from,
&level));
assert_int_equal(from, CAST5_3DES_IDEA_BLOWFISH_FROM);
assert_int_equal(level, RNP_SECURITY_INSECURE);
if (idea_enabled()) {
assert_rnp_success(rnp_get_security_rule(ffi,
RNP_FEATURE_SYMM_ALG,
"IDEA",
CAST5_3DES_IDEA_BLOWFISH_FROM + 1,
&flags,
&from,
&level));
assert_int_equal(from, CAST5_3DES_IDEA_BLOWFISH_FROM);
assert_int_equal(level, RNP_SECURITY_INSECURE);
}
if (blowfish_enabled()) {
assert_rnp_success(rnp_get_security_rule(ffi,
RNP_FEATURE_SYMM_ALG,
"BLOWFISH",
CAST5_3DES_IDEA_BLOWFISH_FROM + 1,
&flags,
&from,
&level));
assert_int_equal(from, CAST5_3DES_IDEA_BLOWFISH_FROM);
assert_int_equal(level, RNP_SECURITY_INSECURE);
}
ffi->context.set_time(CAST5_3DES_IDEA_BLOWFISH_FROM + 1);
// set the data encryption cipher
if (cast5_enabled()) {
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
}
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "TRIPLEDES"));
if (idea_enabled()) {
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "IDEA"));
}
if (blowfish_enabled()) {
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "BLOWFISH"));
}
ffi->context.set_time(CAST5_3DES_IDEA_BLOWFISH_FROM - 1);
if (cast5_enabled()) {
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
}
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "TRIPLEDES"));
if (idea_enabled()) {
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "IDEA"));
}
if (blowfish_enabled()) {
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "BLOWFISH"));
}
// cleanup
assert_rnp_success(rnp_op_encrypt_destroy(op));
op = NULL;
rnp_ffi_destroy(ffi);
// check again but with removing rules now
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
// create encrypt operation
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
ffi->context.set_time(CAST5_3DES_IDEA_BLOWFISH_FROM + 1);
// set the data encryption cipher
if (cast5_enabled()) {
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
}
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "TRIPLEDES"));
if (idea_enabled()) {
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "IDEA"));
}
if (blowfish_enabled()) {
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "BLOWFISH"));
}
size_t removed = 0;
if (cast5_enabled()) {
removed = 0;
assert_rnp_success(rnp_remove_security_rule(
ffi, RNP_FEATURE_SYMM_ALG, "CAST5", 0, RNP_SECURITY_REMOVE_ALL, 0, &removed));
assert_int_equal(removed, 1);
}
removed = 0;
assert_rnp_success(rnp_remove_security_rule(
ffi, RNP_FEATURE_SYMM_ALG, "TRIPLEDES", 0, RNP_SECURITY_REMOVE_ALL, 0, &removed));
assert_int_equal(removed, 1);
if (idea_enabled()) {
removed = 0;
assert_rnp_success(rnp_remove_security_rule(
ffi, RNP_FEATURE_SYMM_ALG, "IDEA", 0, RNP_SECURITY_REMOVE_ALL, 0, &removed));
assert_int_equal(removed, 1);
}
if (blowfish_enabled()) {
removed = 0;
assert_rnp_success(rnp_remove_security_rule(
ffi, RNP_FEATURE_SYMM_ALG, "BLOWFISH", 0, RNP_SECURITY_REMOVE_ALL, 0, &removed));
assert_int_equal(removed, 1);
}
if (cast5_enabled()) {
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
}
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "TRIPLEDES"));
if (idea_enabled()) {
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "IDEA"));
}
if (blowfish_enabled()) {
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "BLOWFISH"));
}
// cleanup
assert_rnp_success(rnp_input_destroy(input));
input = NULL;
assert_rnp_success(rnp_output_destroy(output));
output = NULL;
assert_rnp_success(rnp_op_encrypt_destroy(op));
op = NULL;
rnp_ffi_destroy(ffi);
}
static bool
first_key_password_provider(rnp_ffi_t ffi,
void * app_ctx,
rnp_key_handle_t key,
const char * pgp_context,
char * buf,
size_t buf_len)
{
if (!key) {
throw std::invalid_argument("key");
}
char *keyid = NULL;
rnp_key_get_keyid(key, &keyid);
if (strcmp(keyid, "8A05B89FAD5ADED1")) {
throw std::invalid_argument("keyid");
}
rnp_buffer_destroy(keyid);
return false;
}
TEST_F(rnp_tests, test_ffi_decrypt_pk_unlocked)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t op = NULL;
const char * plaintext = "data1";
// setup FFI
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
// load our keyrings
assert_true(
load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));
// write out some data
str_to_file("plaintext", plaintext);
// create input+output
assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
// create encrypt operation
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
// add recipients
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
rnp_key_handle_destroy(key);
assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
rnp_key_handle_destroy(key);
// execute the operation
assert_rnp_success(rnp_op_encrypt_execute(op));
// make sure the output file was created
assert_true(rnp_file_exists("encrypted"));
// cleanup
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
assert_rnp_success(rnp_op_encrypt_destroy(op));
/* decrypt (unlocked first key, no pass provider) */
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
rnp_key_handle_t defkey = NULL;
assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
assert_non_null(defkey);
assert_rnp_success(rnp_key_unlock(defkey, "password"));
assert_rnp_success(rnp_decrypt(ffi, input, output));
assert_rnp_success(rnp_key_lock(defkey));
rnp_key_handle_destroy(key);
rnp_key_handle_destroy(defkey);
// cleanup
rnp_input_destroy(input);
rnp_output_destroy(output);
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
assert_int_equal(unlink("decrypted"), 0);
/* decrypt (unlocked second key, no pass provider) */
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
assert_non_null(defkey);
assert_rnp_success(rnp_key_unlock(defkey, "password"));
assert_rnp_success(rnp_decrypt(ffi, input, output));
assert_rnp_success(rnp_key_lock(defkey));
rnp_key_handle_destroy(key);
rnp_key_handle_destroy(defkey);
// cleanup
rnp_input_destroy(input);
rnp_output_destroy(output);
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
assert_int_equal(unlink("decrypted"), 0);
/* decrypt (unlocked first key, pass provider should not be called) */
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, ffi_asserting_password_provider, NULL));
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
assert_non_null(defkey);
assert_rnp_success(rnp_key_unlock(defkey, "password"));
assert_rnp_success(rnp_decrypt(ffi, input, output));
assert_rnp_success(rnp_key_lock(defkey));
rnp_key_handle_destroy(key);
rnp_key_handle_destroy(defkey);
// cleanup
rnp_input_destroy(input);
rnp_output_destroy(output);
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
assert_int_equal(unlink("decrypted"), 0);
/* decrypt (unlocked second key, pass provider should not be called) */
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, first_key_password_provider, NULL));
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
assert_non_null(defkey);
assert_rnp_success(rnp_key_unlock(defkey, "password"));
assert_rnp_success(rnp_decrypt(ffi, input, output));
assert_rnp_success(rnp_key_lock(defkey));
rnp_key_handle_destroy(key);
rnp_key_handle_destroy(defkey);
// cleanup
rnp_input_destroy(input);
rnp_output_destroy(output);
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
assert_int_equal(unlink("decrypted"), 0);
// final cleanup
rnp_ffi_destroy(ffi);
}
#if defined(ENABLE_PQC) && defined(ENABLE_CRYPTO_REFRESH)
/* generate keys and make sure they are usable */
TEST_F(rnp_tests, test_ffi_pqc_gen_enc_sign)
{
std::vector<std::pair<std::string, std::string>> primary_sub = {
{"ML-DSA-65+ED25519", "ML-KEM-768+X25519"},
{"ML-DSA-87+ED448", "ML-KEM-1024+X448"},
{"ML-DSA-65+ECDSA-P384", "ML-KEM-768+ECDH-P384"},
{"ML-DSA-87+ECDSA-P521", "ML-KEM-1024+ECDH-P521"},
{"ML-DSA-65+ECDSA-BP384", "ML-KEM-768+ECDH-BP384"},
{"ML-DSA-87+ECDSA-BP512", "ML-KEM-1024+ECDH-BP512"},
{"SLH-DSA-SHAKE-128f", "ML-KEM-768+X25519"},
{"SLH-DSA-SHAKE-128s", "ML-KEM-768+X25519"},
{"SLH-DSA-SHAKE-256s", "ML-KEM-1024+X448"}};
for (auto pk_algs : primary_sub) {
rnp_ffi_t ffi = NULL;
rnp_key_handle_t key = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t op = NULL;
const char * plaintext = "data1";
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_rnp_success(rnp_generate_key_ex(ffi,
pk_algs.first.c_str(),
pk_algs.second.c_str(),
0,
0,
NULL,
NULL,
"Test UID",
NULL,
&key));
// encrypt+sign
str_to_file("plaintext", plaintext);
assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
assert_non_null(output);
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
assert_rnp_success(rnp_op_encrypt_add_signature(op, key, NULL));
assert_rnp_success(rnp_op_encrypt_execute(op));
assert_true(rnp_file_exists("encrypted"));
assert_rnp_success(rnp_input_destroy(input));
input = NULL;
assert_rnp_success(rnp_output_destroy(output));
output = NULL;
/* decrypt */
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_rnp_success(rnp_decrypt(ffi, input, output));
// cleanup
assert_rnp_success(rnp_op_encrypt_destroy(op));
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
assert_rnp_success(rnp_key_handle_destroy(key));
assert_rnp_success(rnp_ffi_destroy(ffi));
}
}
#endif
#if defined(ENABLE_CRYPTO_REFRESH)
TEST_F(rnp_tests, test_ffi_decrypt_v6_skesk_test_vectors)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_verify_t verify;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
if (aead_eax_enabled()) {
assert_rnp_success(rnp_input_from_path(&input, "data/RFC9580/A.9.5.v6_skesk_eax.asc"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
assert_string_equal(file_to_str("decrypted").c_str(), "Hello, world!");
assert_int_equal(unlink("decrypted"), 0);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_op_verify_destroy(verify);
}
assert_rnp_success(rnp_input_from_path(&input, "data/RFC9580/A.10.5.v6_skesk_ocb.asc"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
assert_string_equal(file_to_str("decrypted").c_str(), "Hello, world!");
assert_int_equal(unlink("decrypted"), 0);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_op_verify_destroy(verify);
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_v6_skesk_enc_dec)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t enc = NULL;
rnp_op_verify_t verify;
const char plaintext[] = "Test Plaintext String";
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
std::vector<std::string> ciphers = {"AES128", "AES192", "AES256"};
std::vector<std::string> aead_modes = {"OCB"};
if (aead_eax_enabled()) {
aead_modes.push_back("EAX");
}
for (auto aead : aead_modes)
for (auto cipher : ciphers) {
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
assert_rnp_success(rnp_input_from_memory(
&input, (const uint8_t *) plaintext, strlen(plaintext), false));
assert_rnp_success(rnp_op_encrypt_create(&enc, ffi, input, output));
assert_rnp_success(
rnp_op_encrypt_add_password(enc, "password", NULL, 0, "AES256"));
assert_rnp_success(rnp_op_encrypt_set_cipher(enc, cipher.c_str()));
assert_rnp_success(rnp_op_encrypt_set_aead(enc, aead.c_str()));
assert_rnp_success(rnp_op_encrypt_enable_skesk_v6(enc));
assert_rnp_success(rnp_op_encrypt_execute(enc));
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_op_encrypt_destroy(enc);
assert_rnp_success(rnp_ffi_set_pass_provider(
ffi, ffi_string_password_provider, (void *) "password"));
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_op_verify_destroy(verify);
assert_int_equal(unlink("decrypted"), 0);
assert_int_equal(unlink("encrypted"), 0);
}
rnp_ffi_destroy(ffi);
}
#endif
#if defined(ENABLE_PQC) && defined(ENABLE_CRYPTO_REFRESH)
TEST_F(rnp_tests, test_ffi_decrypt_v6_pkesk_test_vector)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(import_all_keys(ffi, "data/RFC9580/A.4.transferable-seckey-v6.asc"));
assert_rnp_success(rnp_input_from_path(&input, "data/RFC9580/A.8.5-v6pkesk-v2seipd"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_null(&output));
assert_rnp_success(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_ffi_destroy(ffi);
}
#endif
#if defined(ENABLE_CRYPTO_REFRESH)
TEST_F(rnp_tests, test_ffi_argon2_locked_seckey)
{
/* The sample uses Argon2 with m = 2^21 (2 GiB), which does not fit into a
* 32-bit address space. */
if (sizeof(size_t) == 4) {
GTEST_SKIP();
}
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(
import_all_keys(ffi, "data/RFC9580/A.5.sample-locked-v6-seckey-argon2-aead.asc"));
// test key unlock
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "keyid", "CB186C4F0609A697", &key));
assert_rnp_success(rnp_key_unlock(key, "correct horse battery staple"));
// protect with a different passphrase
assert_rnp_success(rnp_key_protect(key, "some other password", NULL, NULL, NULL, 0));
assert_rnp_success(rnp_key_lock(key));
assert_rnp_failure(rnp_key_unlock(key, "some incorrect password"));
assert_rnp_success(rnp_key_unlock(key, "some other password"));
rnp_key_handle_destroy(key);
// test decrypting pkesk
assert_rnp_success(rnp_ffi_set_pass_provider(
ffi, ffi_string_password_provider, (void *) "correct horse battery staple"));
assert_rnp_success(rnp_input_from_path(&input, "data/RFC9580/A.8.5-v6pkesk-v2seipd"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(rnp_decrypt(ffi, input, output));
assert_string_equal(file_to_str("decrypted").c_str(), "Hello, world!");
assert_int_equal(unlink("decrypted"), 0);
// cleanup
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_decrypt_argon2_skesk)
{
/* The vectors use Argon2 with m = 2^21 (2 GiB), which does not fit into a
* 32-bit address space. */
if (sizeof(size_t) == 4) {
GTEST_SKIP();
}
const char password[] = "password";
const char expect_plaintext[] = "Hello, world!";
std::vector<std::string> ciphers = {"AES128", "AES192", "AES256"};
std::vector<std::string> testdata = {
"data/RFC9580/A.12.1.v4-skesk-argon2-aes128.asc",
"data/RFC9580/A.12.2.v4-skesk-argon2-aes192.asc",
"data/RFC9580/A.12.3.v4-skesk-argon2-aes256.asc",
};
for (size_t i = 0; i < size(testdata); i++) {
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_verify_t verify;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
/* decrypt */
assert_rnp_success(rnp_input_from_path(&input, testdata.at(i).c_str()));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) password));
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
assert_string_equal(file_to_str("decrypted").c_str(), expect_plaintext);
assert_int_equal(unlink("decrypted"), 0);
/* Check protection info */
char *mode = NULL;
char *cipher = NULL;
bool valid = false;
assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
assert_string_equal(mode, "cfb-mdc");
assert_string_equal(cipher, ciphers.at(i).c_str());
assert_true(valid);
rnp_buffer_destroy(mode);
rnp_buffer_destroy(cipher);
/* Check SKESK count */
size_t count = 0;
assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
assert_int_equal(count, 1);
// cleanup
rnp_op_verify_destroy(verify);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_ffi_destroy(ffi);
}
}
#endif
#if defined(ENABLE_PQC) && defined(ENABLE_CRYPTO_REFRESH)
TEST_F(rnp_tests, test_ffi_verify_v2_seipd_test_vector)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_verify_t verify = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(import_all_keys(ffi, "data/RFC9580/A.4.transferable-seckey-v6.asc"));
assert_rnp_success(
rnp_input_from_path(&input, "data/RFC9580/A.7.-sample-inline-signed-message.asc"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
/* dash-escaped */
assert_string_equal(
file_to_str("decrypted").c_str(),
"What we need from the grocery store:\n\n- tofu\n- vegetables\n- noodles\n");
assert_int_equal(unlink("decrypted"), 0);
// cleanup
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_op_verify_destroy(verify);
rnp_ffi_destroy(ffi);
}
/* RNP currently skips v6 signatures on cleartext signatures.
This test expects the failure. Eventually, if v6 signatures are supported, the test should be
adapted */
TEST_F(rnp_tests, test_ffi_verify_v2_seipd_cleartext_test_vector)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_verify_t verify = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(import_all_keys(ffi, "data/RFC9580/A.4.transferable-seckey-v6.asc"));
assert_rnp_success(
rnp_input_from_path(&input, "data/RFC9580/A.6.-sample-cleartext-signed-message.asc"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_int_equal(RNP_ERROR_SIGNATURE_INVALID, rnp_op_verify_execute(verify));
// /* dash-escaped */
// assert_string_equal(
// file_to_str("decrypted").c_str(),
// "What we need from the grocery store:\n\n- tofu\n- vegetables\n- noodles\n");
// assert_int_equal(unlink("decrypted"), 0);
// cleanup
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_op_verify_destroy(verify);
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_decrypt_pqc_pkesk_test_vector)
{
std::vector<std::pair<std::string, std::string>> key_msg_pairs = {
{"data/draft-ietf-openpgp-pqc/v6-eddsa-sample-sk.asc",
"data/draft-ietf-openpgp-pqc/v6-eddsa-sample-message.asc"},
{"data/draft-ietf-openpgp-pqc/v4-eddsa-sample-sk.asc",
"data/draft-ietf-openpgp-pqc/v4-eddsa-sample-message-v1.asc"},
{"data/draft-ietf-openpgp-pqc/v4-eddsa-sample-sk.asc",
"data/draft-ietf-openpgp-pqc/v4-eddsa-sample-message-v2.asc"},
{"data/draft-ietf-openpgp-pqc/v6-mldsa-65-sample-sk.asc",
"data/draft-ietf-openpgp-pqc/v6-mldsa-65-sample-message.asc"},
{"data/draft-ietf-openpgp-pqc/v6-mldsa-87-sample-sk.asc",
"data/draft-ietf-openpgp-pqc/v6-mldsa-87-sample-message.asc"},
{"data/draft-ietf-openpgp-pqc/v6-slhdsa-128s-sample-sk.asc",
"data/draft-ietf-openpgp-pqc/v6-slhdsa-128s-sample-message.asc"}};
for (auto key_msg_pair : key_msg_pairs) {
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(import_all_keys(ffi, key_msg_pair.first.c_str()));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(rnp_input_from_path(&input, key_msg_pair.second.c_str()));
assert_non_null(input);
assert_rnp_success(rnp_decrypt(ffi, input, output));
assert_string_equal(file_to_str("decrypted").c_str(), "Testing\n");
assert_int_equal(unlink("decrypted"), 0);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_ffi_destroy(ffi);
}
}
TEST_F(rnp_tests, test_ffi_pqc_default_enc_subkey)
{
rnp_ffi_t ffi = NULL;
rnp_key_handle_t key1 = NULL;
rnp_key_handle_t key2 = NULL;
rnp_key_handle_t defkey1 = NULL;
rnp_key_handle_t defkey2 = NULL;
rnp_op_generate_t op = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
/* generate key 1 */
assert_rnp_success(rnp_op_generate_create(&op, ffi, "ML-DSA-65+ED25519"));
assert_rnp_success(rnp_op_generate_set_hash(op, "SHA3-256"));
assert_rnp_success(rnp_op_generate_execute(op));
assert_rnp_success(rnp_op_generate_get_key(op, &key1));
assert_non_null(key1);
assert_rnp_success(rnp_op_generate_destroy(op));
op = NULL;
assert_rnp_success(rnp_op_generate_subkey_create(&op, ffi, key1, "ML-KEM-768+X25519"));
assert_rnp_success(rnp_op_generate_execute(op));
rnp_op_generate_destroy(op);
op = NULL;
assert_rnp_success(rnp_op_generate_subkey_create(&op, ffi, key1, "ECDH"));
assert_rnp_success(rnp_op_generate_set_curve(op, "NIST P-256"));
assert_rnp_success(rnp_op_generate_execute(op));
rnp_op_generate_destroy(op);
op = NULL;
/* generate key 2 */
assert_rnp_success(rnp_op_generate_create(&op, ffi, "ML-DSA-65+ED25519"));
assert_rnp_success(rnp_op_generate_set_hash(op, "SHA3-256"));
assert_rnp_success(rnp_op_generate_execute(op));
assert_rnp_success(rnp_op_generate_get_key(op, &key2));
assert_non_null(key2);
assert_rnp_success(rnp_op_generate_destroy(op));
op = NULL;
assert_rnp_success(rnp_op_generate_subkey_create(&op, ffi, key2, "ECDH"));
assert_rnp_success(rnp_op_generate_set_curve(op, "NIST P-256"));
assert_rnp_success(rnp_op_generate_execute(op));
rnp_op_generate_destroy(op);
op = NULL;
assert_rnp_success(rnp_op_generate_subkey_create(&op, ffi, key2, "ML-KEM-768+X25519"));
assert_rnp_success(rnp_op_generate_execute(op));
rnp_op_generate_destroy(op);
op = NULL;
/* check default key */
assert_rnp_success(
rnp_key_get_default_key(key1, "encrypt", RNP_KEY_PREFER_PQC_ENC_SUBKEY, &defkey1));
// PQC key is older but preferred
assert(defkey1->pub->alg() == PGP_PKA_KYBER768_X25519);
assert_rnp_success(
rnp_key_get_default_key(key2, "encrypt", RNP_KEY_PREFER_PQC_ENC_SUBKEY, &defkey2));
// PQC key is newer and preferred
assert(defkey2->pub->alg() == PGP_PKA_KYBER768_X25519);
/* cleanup */
rnp_key_handle_destroy(key1);
rnp_key_handle_destroy(key2);
rnp_key_handle_destroy(defkey1);
rnp_key_handle_destroy(defkey2);
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_encrypt_pk_with_v6_key)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t op = NULL;
const char * plaintext = "data1";
// setup FFI
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(import_all_keys(ffi, "data/RFC9580/A.4.transferable-seckey-v6.asc"));
// No other cipher can be used here: the only 128-bit block ciphers in OpenPGP aside from
// AES are Camellia and TwoFish.
std::vector<std::string> ciphers = {"AES128", "AES192", "AES256"};
#ifdef ENABLE_TWOFISH
ciphers.push_back("TwoFish");
#endif
#if (defined BOTAN_HAS_CAMELLIA || !defined CRYPTO_BACKEND_BOTAN3)
// ENABLE_CAMELLIA does not exist, yet BOTAN might not support this algorithm.
std::vector<std::string> camellia({"Camellia128", "Camellia192", "Camellia256"});
ciphers.insert(ciphers.end(), camellia.begin(), camellia.end());
#endif
std::vector<std::string> aead_modes = {"None", "OCB"};
if (aead_eax_enabled()) {
aead_modes.push_back("EAX");
}
std::vector<bool> enable_pkeskv6_modes = {true, false};
for (auto enable_pkeskv6 : enable_pkeskv6_modes)
for (auto aead : aead_modes)
for (auto cipher : ciphers) {
bool expect_success = true;
if (!enable_pkeskv6 && (cipher == "TwoFish" || cipher.find("Camellia") == 0)) {
// This combination is not supported, since the algorithm ID is fixed to
// AES for v3 PKESK for the public key algorithm featured by the key used
// here.
expect_success = false;
}
#if defined(CRYPTO_BACKEND_OPENSSL)
// OpenSSL backend only supports AES with OCB/EAX; non-AES AEAD combinations
// fail. When PKESKv6 is enabled, AEAD=None is rejected and OCB is used, so
// non-AES fails too.
if (cipher.find("AES") != 0 && (aead != "None" || enable_pkeskv6)) {
expect_success = false;
}
#endif
// write out some data
FILE *fp = fopen("plaintext", "wb");
assert_non_null(fp);
assert_int_equal(1, fwrite(plaintext, strlen(plaintext), 1, fp));
assert_int_equal(0, fclose(fp));
// create input+output
assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
assert_non_null(output);
// create encrypt operation
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
// add recipients
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "keyid", "12c83f1e706f6308", &key));
assert_non_null(key);
assert_rnp_failure(rnp_op_encrypt_add_recipient(op, NULL)); // what for ?
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
if (enable_pkeskv6) {
assert_rnp_success(rnp_op_encrypt_enable_pkesk_v6(op));
}
rnp_key_handle_destroy(key);
key = NULL;
// set the data encryption cipher
if ((aead == "None") && enable_pkeskv6) {
// already enabled v6 pkesk, does not make any sense to set AEAD to None
// explicitly.
assert_rnp_failure(rnp_op_encrypt_set_aead(op, aead.c_str()));
} else {
assert_rnp_success(rnp_op_encrypt_set_aead(op, aead.c_str()));
}
assert_rnp_success(rnp_op_encrypt_set_cipher(op, cipher.c_str()));
// execute the operation
if (expect_success) {
assert_rnp_success(rnp_op_encrypt_execute(op));
} else {
assert_rnp_failure(rnp_op_encrypt_execute(op));
// cleanup
rnp_op_encrypt_destroy(op);
op = NULL;
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
continue;
}
// make sure the output file was created
assert_true(rnp_file_exists("encrypted"));
// cleanup
assert_rnp_success(rnp_input_destroy(input));
input = NULL;
assert_rnp_success(rnp_output_destroy(output));
output = NULL;
assert_rnp_success(rnp_op_encrypt_destroy(op));
op = NULL;
// decrypt
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
assert_rnp_success(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
}
rnp_ffi_destroy(ffi);
}
#endif
TEST_F(rnp_tests, test_ffi_encrypt_pk_key_provider)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t op = NULL;
const char * plaintext = "data1";
uint8_t * primary_sec_key_data = NULL;
size_t primary_sec_size = 0;
uint8_t * sub_sec_key_data = NULL;
size_t sub_sec_size = 0;
/* first, let's generate some encrypted data */
// setup FFI
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_non_null(ffi);
// load our keyrings
assert_true(
load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));
// write out some data
str_to_file("plaintext", plaintext);
// create input+output
assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
assert_non_null(output);
// create encrypt operation
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
// add recipient 1
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
assert_non_null(key);
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
// cleanup
assert_rnp_success(rnp_key_handle_destroy(key));
key = NULL;
// add recipient 2
assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
assert_non_null(key);
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
// save the primary key data for later
assert_rnp_success(rnp_get_secret_key_data(key, &primary_sec_key_data, &primary_sec_size));
assert_non_null(primary_sec_key_data);
assert_rnp_success(rnp_key_handle_destroy(key));
key = NULL;
// save the appropriate encrypting subkey for the key provider to use during decryption
// later
assert_rnp_success(rnp_locate_key(ffi, "keyid", "8A05B89FAD5ADED1", &key));
assert_non_null(key);
assert_rnp_success(rnp_get_secret_key_data(key, &sub_sec_key_data, &sub_sec_size));
assert_non_null(sub_sec_key_data);
// cleanup
assert_rnp_success(rnp_key_handle_destroy(key));
key = NULL;
// set the data encryption cipher
if (cast5_enabled()) {
if (cast5_enabled()) {
assert_rnp_success(rnp_remove_security_rule(
ffi, RNP_FEATURE_SYMM_ALG, "CAST5", 0, RNP_SECURITY_REMOVE_ALL, 0, NULL));
}
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
} else {
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
}
// execute the operation
assert_rnp_success(rnp_op_encrypt_execute(op));
// make sure the output file was created
assert_true(rnp_file_exists("encrypted"));
// cleanup
assert_rnp_success(rnp_input_destroy(input));
input = NULL;
assert_rnp_success(rnp_output_destroy(output));
output = NULL;
assert_rnp_success(rnp_op_encrypt_destroy(op));
op = NULL;
assert_rnp_success(rnp_ffi_destroy(ffi));
ffi = NULL;
/* decrypt */
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
// load the primary
input = NULL;
assert_rnp_success(
rnp_input_from_memory(&input, primary_sec_key_data, primary_sec_size, true));
assert_non_null(input);
assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS));
rnp_input_destroy(input);
input = NULL;
// decrypt (no key to decrypt, should fail)
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_int_equal(RNP_ERROR_NO_SUITABLE_KEY, rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// key_data key_data_size secret keyid grip userids
const key_tbl_t keydb[] = {
{sub_sec_key_data, sub_sec_size, true, "8A05B89FAD5ADED1", NULL, {NULL}}, {0}};
// decrypt
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_rnp_success(rnp_ffi_set_key_provider(ffi, tbl_getkeycb, (void *) keydb));
assert_rnp_success(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// compare the decrypted file
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
// final cleanup
rnp_ffi_destroy(ffi);
free(sub_sec_key_data);
free(primary_sec_key_data);
}
TEST_F(rnp_tests, test_ffi_encrypt_and_sign)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t op = NULL;
rnp_op_sign_signature_t signsig = NULL;
const char * plaintext = "data1";
rnp_key_handle_t key = NULL;
const uint32_t issued = 1516211899; // Unix epoch, nowish
const uint32_t expires = 1000000000; // expires later
const uint32_t issued2 = 1516211900; // Unix epoch, nowish
const uint32_t expires2 = 2000000000; // expires later
// setup FFI
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
// load our keyrings
assert_true(
load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));
// write out some data
str_to_file("plaintext", plaintext);
// create input+output
assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
assert_non_null(output);
// create encrypt operation
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
// add recipients
assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
rnp_key_handle_destroy(key);
key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
rnp_key_handle_destroy(key);
key = NULL;
// set the data encryption cipher
if (cast5_enabled()) {
if (cast5_enabled()) {
assert_rnp_success(rnp_remove_security_rule(
ffi, RNP_FEATURE_SYMM_ALG, "CAST5", 0, RNP_SECURITY_REMOVE_ALL, 0, NULL));
}
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
} else {
assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
}
// enable armoring
assert_rnp_failure(rnp_op_encrypt_set_armor(NULL, true));
assert_rnp_success(rnp_op_encrypt_set_armor(op, true));
// add signature
assert_rnp_failure(rnp_op_encrypt_set_hash(NULL, "SHA256"));
assert_rnp_failure(rnp_op_encrypt_set_hash(op, NULL));
assert_rnp_failure(rnp_op_encrypt_set_hash(op, "WRONG"));
assert_rnp_success(rnp_op_encrypt_set_hash(op, "SHA1"));
assert_rnp_failure(rnp_op_encrypt_set_creation_time(NULL, 0));
assert_rnp_success(rnp_op_encrypt_set_creation_time(op, 0));
assert_rnp_failure(rnp_op_encrypt_set_expiration_time(NULL, 0));
assert_rnp_success(rnp_op_encrypt_set_expiration_time(op, 0));
assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
assert_rnp_failure(rnp_op_encrypt_add_signature(NULL, key, NULL));
assert_rnp_failure(rnp_op_encrypt_add_signature(op, NULL, NULL));
assert_rnp_success(rnp_op_encrypt_add_signature(op, key, NULL));
rnp_key_handle_destroy(key);
key = NULL;
// attempt to add signature from the public key
assert_true(import_pub_keys(ffi, "data/test_stream_key_load/ecc-p256-pub.asc"));
assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &key));
assert_rnp_failure(rnp_op_encrypt_add_signature(op, key, &signsig));
rnp_key_handle_destroy(key);
key = NULL;
// attempt to add signature by the offline secret key
assert_true(
import_pub_keys(ffi, "data/test_key_edge_cases/alice-s2k-101-no-sign-sub.pgp"));
assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
assert_rnp_failure(rnp_op_encrypt_add_signature(op, key, &signsig));
rnp_key_handle_destroy(key);
key = NULL;
// add second signature with different hash/issued/expiration
assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid2", &key));
assert_rnp_success(rnp_op_encrypt_add_signature(op, key, &signsig));
assert_rnp_failure(rnp_op_sign_signature_set_creation_time(NULL, issued2));
assert_rnp_success(rnp_op_sign_signature_set_creation_time(signsig, issued2));
assert_rnp_failure(rnp_op_sign_signature_set_expiration_time(NULL, expires2));
assert_rnp_success(rnp_op_sign_signature_set_expiration_time(signsig, expires2));
assert_rnp_failure(rnp_op_sign_signature_set_hash(signsig, NULL));
assert_rnp_failure(rnp_op_sign_signature_set_hash(NULL, "SHA512"));
assert_rnp_failure(rnp_op_sign_signature_set_hash(signsig, "UNKNOWN"));
assert_rnp_success(rnp_op_sign_signature_set_hash(signsig, "SHA512"));
rnp_key_handle_destroy(key);
key = NULL;
// set default sig parameters after the signature is added - those should be picked up
assert_rnp_success(rnp_op_encrypt_set_hash(op, "SHA256"));
assert_rnp_success(rnp_op_encrypt_set_creation_time(op, issued));
assert_rnp_success(rnp_op_encrypt_set_expiration_time(op, expires));
// execute the operation
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
assert_rnp_success(rnp_op_encrypt_execute(op));
// make sure the output file was created
assert_true(rnp_file_exists("encrypted"));
// check whether keys are locked
rnp_identifier_iterator_t it = NULL;
assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "fingerprint"));
const char *fprint = NULL;
while (!rnp_identifier_iterator_next(it, &fprint)) {
if (!fprint) {
break;
}
SCOPED_TRACE(fprint);
rnp_key_handle_t skey = NULL;
assert_rnp_success(rnp_locate_key(ffi, "fingerprint", fprint, &skey));
bool secret = true;
assert_rnp_success(rnp_key_have_secret(skey, &secret));
if (secret) {
bool locked = false;
assert_rnp_success(rnp_key_is_locked(skey, &locked));
assert_true(locked);
}
rnp_key_handle_destroy(skey);
}
rnp_identifier_iterator_destroy(it);
// cleanup
assert_rnp_success(rnp_input_destroy(input));
input = NULL;
assert_rnp_success(rnp_output_destroy(output));
output = NULL;
assert_rnp_success(rnp_op_encrypt_destroy(op));
op = NULL;
/* decrypt */
// decrypt (no pass provider, should fail)
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
assert_rnp_failure(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// decrypt (wrong pass, should fail)
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
const char *pass = "wrong1";
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_once, &pass));
assert_rnp_failure(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// decrypt
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_non_null(output);
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
assert_rnp_success(rnp_decrypt(ffi, input, output));
// cleanup
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// compare the decrypted file
assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
// verify and check signatures
rnp_op_verify_t verify;
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_non_null(input);
assert_rnp_success(rnp_output_to_path(&output, "verified"));
assert_non_null(output);
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
// check signatures
rnp_op_verify_signature_t sig;
size_t sig_count;
uint32_t sig_create;
uint32_t sig_expires;
char * hname = NULL;
assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
assert_int_equal(sig_count, 2);
assert_rnp_failure(rnp_op_verify_get_signature_at(NULL, 0, &sig));
assert_rnp_failure(rnp_op_verify_get_signature_at(verify, 0, NULL));
assert_rnp_failure(rnp_op_verify_get_signature_at(verify, 10, &sig));
// signature 1
assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
assert_rnp_failure(rnp_op_verify_signature_get_status(NULL));
assert_rnp_success(rnp_op_verify_signature_get_status(sig));
assert_rnp_success(rnp_op_verify_signature_get_times(sig, &sig_create, &sig_expires));
assert_int_equal(sig_create, issued);
assert_int_equal(sig_expires, expires);
assert_rnp_failure(rnp_op_verify_signature_get_hash(NULL, &hname));
assert_rnp_failure(rnp_op_verify_signature_get_hash(sig, NULL));
assert_rnp_success(rnp_op_verify_signature_get_hash(sig, &hname));
assert_string_equal(hname, "SHA256");
rnp_buffer_destroy(hname);
hname = NULL;
key = NULL;
assert_rnp_failure(rnp_op_verify_signature_get_key(NULL, &key));
assert_rnp_failure(rnp_op_verify_signature_get_key(sig, NULL));
assert_rnp_success(rnp_op_verify_signature_get_key(sig, &key));
assert_non_null(key);
rnp_key_handle_destroy(key);
// signature 2
assert_rnp_success(rnp_op_verify_get_signature_at(verify, 1, &sig));
assert_rnp_success(rnp_op_verify_signature_get_status(sig));
assert_rnp_success(rnp_op_verify_signature_get_times(sig, &sig_create, &sig_expires));
assert_int_equal(sig_create, issued2);
assert_int_equal(sig_expires, expires2);
assert_rnp_success(rnp_op_verify_signature_get_hash(sig, &hname));
assert_string_equal(hname, "SHA512");
rnp_buffer_destroy(hname);
hname = NULL;
// make sure keys are locked
assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "fingerprint"));
while (!rnp_identifier_iterator_next(it, &fprint)) {
if (!fprint) {
break;
}
SCOPED_TRACE(fprint);
rnp_key_handle_t skey = NULL;
assert_rnp_success(rnp_locate_key(ffi, "fingerprint", fprint, &skey));
bool secret = true;
assert_rnp_success(rnp_key_have_secret(skey, &secret));
if (secret) {
bool locked = false;
assert_rnp_success(rnp_key_is_locked(skey, &locked));
assert_true(locked);
}
rnp_key_handle_destroy(skey);
}
rnp_identifier_iterator_destroy(it);
// cleanup
rnp_op_verify_destroy(verify);
rnp_input_destroy(input);
input = NULL;
rnp_output_destroy(output);
output = NULL;
// compare the decrypted file
assert_string_equal(file_to_str("verified").c_str(), plaintext);
// final cleanup
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_encrypt_pk_subkey_selection)
{
rnp_ffi_t ffi = NULL;
rnp_input_t input = NULL;
rnp_output_t output = NULL;
rnp_op_encrypt_t op = NULL;
const char * plaintext = "data1";
/* check whether a latest subkey is selected for encryption */
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
/* case 1: three encryption subkeys, second expired, third has later creation time */
assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/key0-sub02.pgp"));
assert_rnp_success(
rnp_input_from_memory(&input, (uint8_t *) plaintext, strlen(plaintext), false));
assert_rnp_failure(rnp_output_to_memory(NULL, 0));
assert_rnp_success(rnp_output_to_memory(&output, 0));
/* create encrypt operation, add recipient and execute */
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid0", &key));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
rnp_key_handle_destroy(key);
assert_rnp_success(rnp_op_encrypt_execute(op));
/* get output */
uint8_t *buf = NULL;
size_t len = 0;
assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, true));
assert_true(buf && len);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_op_encrypt_destroy(op);
/* decrypt */
assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
assert_rnp_success(rnp_input_from_memory(&input, buf, len, true));
rnp_buffer_destroy(buf);
assert_rnp_success(rnp_output_to_memory(&output, 0));
rnp_op_verify_t verify = NULL;
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
/* check whether we used correct subkey */
size_t count = 0;
assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
assert_int_equal(count, 1);
rnp_recipient_handle_t recipient = NULL;
assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
assert_non_null(recipient);
char *keyid = NULL;
assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
assert_non_null(keyid);
assert_string_equal(keyid, "8A05B89FAD5ADED1");
rnp_buffer_destroy(keyid);
rnp_op_verify_destroy(verify);
rnp_input_destroy(input);
rnp_output_destroy(output);
/* case 2: only subkeys 1-2, make sure that latest but expired subkey is not selected */
assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/key0-sub01.pgp"));
assert_rnp_success(
rnp_input_from_memory(&input, (uint8_t *) plaintext, strlen(plaintext), false));
assert_rnp_success(rnp_output_to_memory(&output, 0));
/* create encrypt operation, add recipient and execute */
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
rnp_key_handle_destroy(key);
assert_rnp_success(rnp_op_encrypt_execute(op));
/* get output */
buf = NULL;
len = 0;
assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, true));
assert_true(buf && len);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_op_encrypt_destroy(op);
/* decrypt */
assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
assert_rnp_success(rnp_input_from_memory(&input, buf, len, true));
rnp_buffer_destroy(buf);
assert_rnp_success(rnp_output_to_memory(&output, 0));
verify = NULL;
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
/* check whether we used correct subkey */
count = 0;
assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
assert_int_equal(count, 1);
recipient = NULL;
assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
assert_non_null(recipient);
keyid = NULL;
assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
assert_non_null(keyid);
assert_string_equal(keyid, "1ED63EE56FADC34D");
rnp_buffer_destroy(keyid);
rnp_op_verify_destroy(verify);
rnp_input_destroy(input);
rnp_output_destroy(output);
/* case 3: only expired subkey, make sure encryption operation fails */
assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/key0-sub1.pgp"));
assert_rnp_success(
rnp_input_from_memory(&input, (uint8_t *) plaintext, strlen(plaintext), false));
assert_rnp_success(rnp_output_to_memory(&output, 0));
/* create encrypt operation, add recipient and execute */
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
assert_int_equal(rnp_op_encrypt_add_recipient(op, key), RNP_ERROR_NO_SUITABLE_KEY);
rnp_key_handle_destroy(key);
assert_rnp_failure(rnp_op_encrypt_execute(op));
rnp_op_encrypt_destroy(op);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_decrypt_small_rsa)
{
rnp_ffi_t ffi = NULL;
const char *plaintext = "data1";
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(import_all_keys(ffi, "data/test_key_validity/rsa_key_small_sig-sec.asc"));
rnp_input_t input = NULL;
assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/data.enc.small-rsa"));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_memory(&output, 0));
assert_rnp_success(rnp_decrypt(ffi, input, output));
size_t len = 0;
uint8_t *buf = NULL;
assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
assert_int_equal(len, 5);
assert_int_equal(memcmp(plaintext, buf, 5), 0);
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_decrypt_small_eg)
{
/* make sure unlock and decrypt fails with invalid key */
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(
import_all_keys(ffi, "data/test_key_edge_cases/key-eg-small-subgroup-sec.pgp"));
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "keyid", "3b8dda452b9f69b4", &key));
assert_non_null(key);
/* key is not encrypted */
assert_rnp_success(rnp_key_unlock(key, NULL));
rnp_key_handle_destroy(key);
rnp_input_t input = NULL;
assert_rnp_success(
rnp_input_from_path(&input, "data/test_messages/message.txt.enc-eg-bad"));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_null(&output));
assert_rnp_failure(rnp_decrypt(ffi, input, output));
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
rnp_ffi_destroy(ffi);
/* make sure unlock and decrypt fails with invalid encrypted key */
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
assert_true(
import_all_keys(ffi, "data/test_key_edge_cases/key-eg-small-subgroup-sec-enc.pgp"));
assert_rnp_success(rnp_locate_key(ffi, "keyid", "3b072c3bb2d1a8b2", &key));
assert_non_null(key);
assert_rnp_success(rnp_key_unlock(key, "password"));
assert_rnp_success(rnp_key_lock(key));
rnp_key_handle_destroy(key);
assert_rnp_success(
rnp_input_from_path(&input, "data/test_messages/message.txt.enc-eg-bad2"));
assert_rnp_success(rnp_output_to_null(&output));
assert_rnp_failure(rnp_decrypt(ffi, input, output));
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
rnp_ffi_destroy(ffi);
}
/* Regression test for #1275 / PR #2391: rnp_op_verify_get_used_recipient must
* return the actual key that was used for decryption, even when the recipient
* was hidden in the PKESK packet (keyid all zeroes). The fix patches the
* used_recipient's keyid from the key that successfully decrypted. Without
* the fix, used_recipient is NULL because no embedded keyid matches a key. */
TEST_F(rnp_tests, test_ffi_decrypt_hidden_recipient_used_recipient)
{
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(import_sec_keys(ffi, "data/keyrings/1/secring.gpg"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
/* message.txt.enc-hidden-1 has a hidden first recipient. The seckey that
* decrypts it (per src/tests/cli_tests.py::test_hidden_recipient) is
* 0x326EF111425D14A5. */
rnp_input_t input = NULL;
assert_rnp_success(
rnp_input_from_path(&input, "data/test_messages/message.txt.enc-hidden-1"));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_null(&output));
rnp_op_verify_t verify = NULL;
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_set_flags(verify, RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT));
assert_rnp_success(rnp_op_verify_execute(verify));
/* Without the PR's patch, used_recipient->keyid would be all-zero
* (the hidden-recipient sentinel from RFC 4880 §5.1) because no real
* keyid is embedded in the PKESK packet. With the patch, used_recipient
* ->keyid is the actual key that succeeded. */
rnp_recipient_handle_t recipient = NULL;
assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
assert_non_null(recipient);
char *keyid = NULL;
assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
assert_non_null(keyid);
/* Must not be the all-zero hidden-recipient sentinel. */
EXPECT_STRNE(keyid, "0000000000000000");
rnp_buffer_destroy(keyid);
rnp_op_verify_destroy(verify);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_encrypt_no_wrap)
{
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(
load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));
rnp_input_t input = NULL;
assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed"));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
rnp_op_encrypt_t op = NULL;
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
rnp_key_handle_destroy(key);
/* set nowrap flag */
assert_rnp_failure(rnp_op_encrypt_set_flags(NULL, RNP_ENCRYPT_NOWRAP));
assert_rnp_failure(rnp_op_encrypt_set_flags(op, 17));
assert_rnp_success(rnp_op_encrypt_set_flags(op, RNP_ENCRYPT_NOWRAP));
assert_rnp_success(rnp_op_encrypt_execute(op));
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
assert_rnp_success(rnp_op_encrypt_destroy(op));
/* decrypt via rnp_decrypt() */
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
assert_rnp_success(rnp_decrypt(ffi, input, output));
rnp_input_destroy(input);
rnp_output_destroy(output);
assert_string_equal(file_to_str("decrypted").c_str(),
file_to_str("data/test_messages/message.txt").c_str());
unlink("decrypted");
/* decrypt and verify signatures */
rnp_op_verify_t verify;
assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
assert_rnp_success(rnp_output_to_path(&output, "verified"));
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
/* check signature */
rnp_op_verify_signature_t sig;
size_t sig_count;
assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
assert_int_equal(sig_count, 1);
assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
assert_rnp_success(rnp_op_verify_signature_get_status(sig));
assert_rnp_success(rnp_input_destroy(input));
assert_rnp_success(rnp_output_destroy(output));
rnp_op_verify_destroy(verify);
assert_string_equal(file_to_str("verified").c_str(),
file_to_str("data/test_messages/message.txt").c_str());
unlink("verified");
// final cleanup
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_v5_signatures)
{
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
/* import v5 keys */
assert_true(import_pub_keys(ffi, "data/test_stream_key_load/v5-rsa-pub.asc"));
/* verify v5 attached signature */
rnp_input_t input = NULL;
assert_rnp_success(
rnp_input_from_path(&input, "data/test_messages/message.txt.signed.v5-rsa"));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_null(&output));
rnp_op_verify_t verify = NULL;
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
rnp_op_verify_signature_t sig = NULL;
assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
assert_rnp_success(rnp_op_verify_signature_get_status(sig));
rnp_op_verify_destroy(verify);
rnp_input_destroy(input);
rnp_output_destroy(output);
/* verify v5 detached signature */
assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt"));
rnp_input_t sigin = NULL;
assert_rnp_success(
rnp_input_from_path(&sigin, "data/test_messages/message.txt.signed.v5-detached-rsa"));
assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, sigin));
assert_rnp_success(rnp_op_verify_execute(verify));
assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
assert_rnp_success(rnp_op_verify_signature_get_status(sig));
rnp_op_verify_destroy(verify);
rnp_input_destroy(input);
rnp_input_destroy(sigin);
/* verify cleartext signature */
assert_rnp_success(
rnp_input_from_path(&input, "data/test_messages/message.txt.signed.v5-clear-rsa"));
assert_rnp_success(rnp_output_to_null(&output));
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
assert_rnp_success(rnp_op_verify_signature_get_status(sig));
rnp_op_verify_destroy(verify);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_ffi_destroy(ffi);
}
TEST_F(rnp_tests, test_ffi_mimemode_signature)
{
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(import_pub_keys(ffi, "data/test_stream_key_load/ecc-25519-pub.asc"));
rnp_input_t input = NULL;
assert_rnp_success(
rnp_input_from_path(&input, "data/test_messages/message.txt.signed-mimemode"));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_null(&output));
rnp_op_verify_t verify = NULL;
assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
assert_rnp_success(rnp_op_verify_execute(verify));
size_t sigcount = 255;
assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
assert_int_equal(sigcount, 1);
rnp_op_verify_signature_t sig = NULL;
assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
assert_rnp_success(rnp_op_verify_signature_get_status(sig));
char format = 0;
assert_rnp_success(rnp_op_verify_get_format(verify, &format));
assert_int_equal(format, 'm');
rnp_op_verify_destroy(verify);
rnp_input_destroy(input);
rnp_output_destroy(output);
rnp_ffi_destroy(ffi);
}
/* Password provider that returns a sequence of passwords, then a final one.
* Used to test the password-retry logic in init_encrypted_src. */
typedef struct password_sequence {
std::vector<std::string> passwords; /* wrong passwords to return in order */
std::string final_password; /* returned after the wrong ones */
size_t idx = 0;
} password_sequence;
static bool
password_sequence_cb(rnp_ffi_t ffi,
void * app_ctx,
rnp_key_handle_t key,
const char * pgp_context,
char * buf,
size_t buf_len)
{
auto * seq = static_cast<password_sequence *>(app_ctx);
const std::string &pw =
(seq->idx < seq->passwords.size()) ? seq->passwords[seq->idx++] : seq->final_password;
if (pw.size() >= buf_len) {
return false;
}
memcpy(buf, pw.data(), pw.size() + 1);
return true;
}
typedef struct password_cancel_state {
bool cancelled = false;
} password_cancel_state;
static bool
password_cancel_cb(rnp_ffi_t ffi,
void * app_ctx,
rnp_key_handle_t key,
const char * pgp_context,
char * buf,
size_t buf_len)
{
auto *state = static_cast<password_cancel_state *>(app_ctx);
state->cancelled = true;
return false;
}
TEST_F(rnp_tests, test_ffi_decrypt_password_retry)
{
/* Verify the password retry in init_encrypted_src for the symmetric path:
* - up to RNP_PASSWORD_MAX_ATTEMPTS tries per symmetric password
* - provider cancellation exits immediately (no retry)
* - wrong-then-right succeeds
* - all-wrong fails
* Uses a symmetric (password-only) encrypted message so the retry surface is
* exactly the symmetric path. */
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
/* Encrypt a message with a known password. */
rnp_op_encrypt_t op = NULL;
rnp_input_t input = NULL;
assert_rnp_success(rnp_input_from_memory(&input, (const uint8_t *) "payload", 7, false));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_memory(&output, 0));
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
assert_rnp_success(rnp_op_encrypt_add_password(op, "correct", NULL, 0, NULL));
assert_rnp_success(rnp_op_encrypt_execute(op));
rnp_op_encrypt_destroy(op);
rnp_input_destroy(input);
size_t buf_len = 0;
uint8_t *buf = NULL;
assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
/* Helper macros to keep the three cases readable. */
#define ATTEMPT_DECRYPT(cb, cb_ctx) \
do { \
rnp_input_t _in = NULL; \
rnp_output_t _out = NULL; \
rnp_op_verify_t _verify = NULL; \
assert_rnp_success(rnp_input_from_memory(&_in, buf, buf_len, false)); \
assert_rnp_success(rnp_output_to_null(&_out)); \
assert_rnp_success(rnp_op_verify_create(&_verify, ffi, _in, _out)); \
assert_rnp_success(rnp_ffi_set_pass_provider(ffi, (cb), (cb_ctx))); \
ret = rnp_op_verify_execute(_verify); \
rnp_op_verify_destroy(_verify); \
rnp_input_destroy(_in); \
rnp_output_destroy(_out); \
} while (0)
rnp_result_t ret = RNP_SUCCESS;
/* Case 1: wrong, wrong, correct → success (retry worked). */
{
password_sequence seq{{"wrong1", "wrong2"}, "correct"};
ATTEMPT_DECRYPT(password_sequence_cb, &seq);
assert_int_equal(ret, RNP_SUCCESS);
/* The sequence should have been consumed past idx 2 (i.e. we tried the
* final password). */
assert_true(seq.idx >= 2);
}
/* Case 2: three wrong passwords → BAD_PASSWORD (retry exhausted). */
{
password_sequence seq{{"wrong1", "wrong2", "wrong3"}, ""};
ATTEMPT_DECRYPT(password_sequence_cb, &seq);
assert_int_equal(ret, RNP_ERROR_BAD_PASSWORD);
}
/* Case 3: provider cancels immediately → BAD_PASSWORD, callback called once. */
{
password_cancel_state state;
ATTEMPT_DECRYPT(password_cancel_cb, &state);
assert_int_equal(ret, RNP_ERROR_BAD_PASSWORD);
assert_true(state.cancelled);
}
#undef ATTEMPT_DECRYPT
rnp_output_destroy(output);
rnp_ffi_destroy(ffi);
}
/* Assert that encrypt and key-export operations do not leak plaintext or
* passphrase material in their output. Per docs/develop/testing-crypto-paths.adoc.
* A regression in any of these would indicate the encryption pipeline is
* broken in a way that silently exposes user secrets. */
TEST_F(rnp_tests, test_no_plaintext_leakage)
{
const char *MARKER = "SECRET_PLAINTEXT_MARKER_a1b2c3d4e5";
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(
load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));
assert_rnp_success(
rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
/* --- Case 1: symmetric (password) encryption must not contain plaintext --- */
{
rnp_input_t input = NULL;
assert_rnp_success(
rnp_input_from_memory(&input, (const uint8_t *) MARKER, strlen(MARKER), false));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_memory(&output, 0));
rnp_op_encrypt_t op = NULL;
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
assert_rnp_success(rnp_op_encrypt_add_password(op, "testpass", NULL, 0, NULL));
assert_rnp_success(rnp_op_encrypt_execute(op));
rnp_op_encrypt_destroy(op);
rnp_input_destroy(input);
size_t len = 0;
uint8_t *buf = NULL;
assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
assert_non_null(buf);
assert_true(len > 0);
/* The marker MUST NOT appear in the encrypted output. */
assert_null(portable_memmem(buf, len, MARKER, strlen(MARKER)));
rnp_output_destroy(output);
}
/* --- Case 2: public-key encryption must not contain plaintext --- */
{
rnp_input_t input = NULL;
assert_rnp_success(
rnp_input_from_memory(&input, (const uint8_t *) MARKER, strlen(MARKER), false));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_memory(&output, 0));
rnp_op_encrypt_t op = NULL;
assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
rnp_key_handle_destroy(key);
assert_rnp_success(rnp_op_encrypt_execute(op));
rnp_op_encrypt_destroy(op);
rnp_input_destroy(input);
size_t len = 0;
uint8_t *buf = NULL;
assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
assert_non_null(buf);
assert_true(len > 0);
assert_null(portable_memmem(buf, len, MARKER, strlen(MARKER)));
rnp_output_destroy(output);
}
/* --- Case 3: secret-key export must not contain the passphrase --- */
{
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
assert_non_null(key);
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_memory(&output, 0));
assert_rnp_success(rnp_key_export(key, output, RNP_KEY_EXPORT_SECRET));
rnp_key_handle_destroy(key);
size_t len = 0;
uint8_t *buf = NULL;
assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
assert_non_null(buf);
assert_true(len > 0);
/* The passphrase MUST NOT appear in the exported key material. */
assert_null(portable_memmem(buf, len, "password", 8));
rnp_output_destroy(output);
}
/* --- Case 4: detached signature must not contain the plaintext --- */
{
rnp_input_t input = NULL;
assert_rnp_success(
rnp_input_from_memory(&input, (const uint8_t *) MARKER, strlen(MARKER), false));
rnp_output_t output = NULL;
assert_rnp_success(rnp_output_to_memory(&output, 0));
rnp_op_sign_t op = NULL;
assert_rnp_success(rnp_op_sign_detached_create(&op, ffi, input, output));
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
assert_rnp_success(rnp_op_sign_add_signature(op, key, NULL));
rnp_key_handle_destroy(key);
assert_rnp_success(rnp_op_sign_execute(op));
rnp_op_sign_destroy(op);
rnp_input_destroy(input);
size_t len = 0;
uint8_t *buf = NULL;
assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
assert_non_null(buf);
assert_true(len > 0);
/* Detached signature MUST NOT contain the plaintext. */
assert_null(portable_memmem(buf, len, MARKER, strlen(MARKER)));
rnp_output_destroy(output);
}
rnp_ffi_destroy(ffi);
}