Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/lib/rnp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3982,6 +3982,9 @@ try {
} else {
res = primary->write_autocrypt(output->dst, *sub, uididx);
}
if (res) {
output->keep = true;
}
return res ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS;
}
FFI_GUARD
Expand Down
68 changes: 68 additions & 0 deletions src/rnp/fficli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2295,6 +2295,74 @@ cli_rnp_export_keys(cli_rnp_t *rnp, const char *filter)
return result;
}

bool
cli_rnp_export_autocrypt_key(cli_rnp_t *rnp, const char *filter)
{
std::vector<rnp_key_handle_t> keys;
if (!rnp->keys_matching(keys, filter ? filter : std::string(), 0)) {
ERR_MSG("Key(s) matching '%s' not found.", filter);
return false;
}

/* Explicit --userid takes priority for UID selection. */
std::string explicit_uid;
if (rnp->cfg().get_count(CFG_USERID) > 0) {
explicit_uid = rnp->cfg().get_str(CFG_USERID, 0);
}

rnp_output_t output = NULL;
bool result = false;

output = cli_rnp_output_to_specifier(*rnp, rnp->cfg().get_str(CFG_OUTFILE));
if (!output) {
goto done;
}

for (auto key : keys) {
bool primary = false;
if (rnp_key_is_primary(key, &primary)) {
goto done;
}
if (!primary) {
continue;
}

/* Determine which UID to embed. If --userid was given, pass it directly.
* Otherwise search the key's UIDs for one containing the filter string;
* fall back to the first UID if none match. */
const char *uid = NULL;
std::string auto_uid;
if (!explicit_uid.empty()) {
uid = explicit_uid.c_str();
} else {
size_t uid_count = 0;
rnp_key_get_uid_count(key, &uid_count);
for (size_t i = 0; i < uid_count && auto_uid.empty(); i++) {
char *uid_str = NULL;
if (rnp_key_get_uid_at(key, i, &uid_str)) {
continue;
}
if (!filter || strstr(uid_str, filter)) {
auto_uid = uid_str;
}
rnp_buffer_destroy(uid_str);
}
if (!auto_uid.empty()) {
uid = auto_uid.c_str();
}
}

if (rnp_key_export_autocrypt(key, NULL, uid, output, 0)) {
goto done;
}
}
result = true;
done:
rnp_output_destroy(output);
clear_key_handles(keys);
return result;
}

bool
cli_rnp_export_revocation(cli_rnp_t *rnp, const char *key)
{
Expand Down
1 change: 1 addition & 0 deletions src/rnp/fficli.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ void cli_rnp_print_key_info(
bool cli_rnp_set_generate_params(rnp_cfg &cfg, bool subkey = false);
bool cli_rnp_generate_key(cli_rnp_t *rnp, const char *username);
bool cli_rnp_export_keys(cli_rnp_t *rnp, const char *filter);
bool cli_rnp_export_autocrypt_key(cli_rnp_t *rnp, const char *filter);
bool cli_rnp_export_revocation(cli_rnp_t *rnp, const char *key);
bool cli_rnp_revoke_key(cli_rnp_t *rnp, const char *key);
bool cli_rnp_remove_key(cli_rnp_t *rnp, const char *key);
Expand Down
11 changes: 11 additions & 0 deletions src/rnpkeys/rnpkeys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ const char *usage =
" --permissive Skip erroring keys/sigs instead of failing.\n"
" --export-key Export a key.\n"
" --secret Export a secret key instead of a public.\n"
" --export-autocrypt-key Export a key in Autocrypt format.\n"
" --userid Override which UID to embed in the Autocrypt export.\n"
" --export-rev Export a key's revocation.\n"
" --rev-type Set revocation type.\n"
" --rev-reason Human-readable reason for revocation.\n"
Expand Down Expand Up @@ -96,6 +98,7 @@ struct option options[] = {
{"list-keys", no_argument, NULL, CMD_LIST_KEYS},
{"export", no_argument, NULL, CMD_EXPORT_KEY},
{"export-key", optional_argument, NULL, CMD_EXPORT_KEY},
{"export-autocrypt-key", optional_argument, NULL, CMD_EXPORT_AUTOCRYPT_KEY},
{"import", no_argument, NULL, CMD_IMPORT},
{"import-key", no_argument, NULL, CMD_IMPORT_KEYS},
{"import-keys", no_argument, NULL, CMD_IMPORT_KEYS},
Expand Down Expand Up @@ -413,6 +416,13 @@ rnp_cmd(cli_rnp_t *rnp, optdefs_t cmd, const char *f)
}
return cli_rnp_export_keys(rnp, f);
}
case CMD_EXPORT_AUTOCRYPT_KEY: {
if (!f && rnp->cfg().get_count(CFG_USERID)) {
fs = rnp->cfg().get_str(CFG_USERID, 0);
f = fs.c_str();
}
return cli_rnp_export_autocrypt_key(rnp, f);
}
case CMD_IMPORT:
case CMD_IMPORT_KEYS:
case CMD_IMPORT_SIGS:
Expand Down Expand Up @@ -488,6 +498,7 @@ setoption(rnp_cfg &cfg, optdefs_t *cmd, int val, const char *arg)
return true;
case CMD_LIST_KEYS:
case CMD_EXPORT_KEY:
case CMD_EXPORT_AUTOCRYPT_KEY:
case CMD_EXPORT_REV:
case CMD_REVOKE_KEY:
case CMD_REMOVE_KEY:
Expand Down
1 change: 1 addition & 0 deletions src/rnpkeys/rnpkeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ typedef enum {
CMD_NONE = 0,
CMD_LIST_KEYS = 260,
CMD_EXPORT_KEY,
CMD_EXPORT_AUTOCRYPT_KEY,
CMD_IMPORT,
CMD_IMPORT_KEYS,
CMD_IMPORT_SIGS,
Expand Down
52 changes: 52 additions & 0 deletions src/tests/exportkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <rekey/rnp_key_store.h>
#include "rnp_tests.h"
#include "support.h"
#include "../rnp/fficli.h"

TEST_F(rnp_tests, rnpkeys_exportkey_verifyUserId)
{
Expand Down Expand Up @@ -66,3 +67,54 @@ TEST_F(rnp_tests, rnpkeys_exportkey_verifyUserId)
}
rnp.end(); // Free memory and other allocated resources.
}

TEST_F(rnp_tests, rnpkeys_exportkey_autocrypt)
{
/* Set up CLI rnp pointing at keyring 1 (has key 7bc6709b15c23a4a with multiple UIDs
* and an encryption subkey 8a05b89fad5aded1). */
cli_rnp_t rnp = {};
int pipefd[2] = {-1, -1};
assert_true(
setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, "data/keyrings/1", pipefd));
assert_true(rnp.load_keyrings(false));

/* Export with explicit keyid filter and userid, verify output is valid. */
rnp.cfg().set_str(CFG_OUTFILE, "ac-out.pgp");
rnp.cfg().add_str(CFG_USERID, "key0-uid0");
assert_true(cli_rnp_export_autocrypt_key(&rnp, "7bc6709b15c23a4a"));

/* Re-import the exported data into a fresh FFI and verify structure. */
rnp_ffi_t ffi = NULL;
assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
assert_true(import_all_keys(ffi, "ac-out.pgp"));
size_t count = 0;
assert_rnp_success(rnp_get_public_key_count(ffi, &count));
assert_int_equal(count, 2);
assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
assert_int_equal(count, 0);
rnp_key_handle_t key = NULL;
assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
assert_non_null(key);
rnp_key_handle_t sub = NULL;
assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &sub));
assert_non_null(sub);
/* Only one UID should be present in the autocrypt export. */
assert_rnp_success(rnp_key_get_uid_count(key, &count));
assert_int_equal(count, 1);
char *uid_str = NULL;
assert_rnp_success(rnp_key_get_uid_at(key, 0, &uid_str));
assert_string_equal(uid_str, "key0-uid0");
rnp_buffer_destroy(uid_str);
rnp_key_handle_destroy(sub);
rnp_key_handle_destroy(key);
rnp_ffi_destroy(ffi);

/* A non-matching filter should return false. */
rnp.cfg().set_str(CFG_OUTFILE, "ac-out2.pgp");
assert_false(cli_rnp_export_autocrypt_key(&rnp, "nosuchkey"));

if (pipefd[0] != -1) {
close(pipefd[0]);
}
rnp.end();
}
Loading