summaryrefslogtreecommitdiffstats
path: root/src/rnpkeys
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/rnpkeys/CMakeLists.txt101
-rw-r--r--src/rnpkeys/main.cpp136
-rw-r--r--src/rnpkeys/rnpkeys.1.adoc453
-rw-r--r--src/rnpkeys/rnpkeys.cpp648
-rw-r--r--src/rnpkeys/rnpkeys.h80
-rw-r--r--src/rnpkeys/tui.cpp413
6 files changed, 1831 insertions, 0 deletions
diff --git a/src/rnpkeys/CMakeLists.txt b/src/rnpkeys/CMakeLists.txt
new file mode 100644
index 0000000..6302903
--- /dev/null
+++ b/src/rnpkeys/CMakeLists.txt
@@ -0,0 +1,101 @@
+# Copyright (c) 2018-2020 Ribose Inc.
+# 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 HOLDERS 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.
+
+if(MSVC)
+ # remove extra ${Configuration} subfolder
+ set(ArchiveOutputDir ${CMAKE_BINARY_DIR}\\src\\rnpkeys)
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${ArchiveOutputDir})
+
+ set(RuntimeOutputDir ${CMAKE_BINARY_DIR}\\src\\rnpkeys)
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${RuntimeOutputDir})
+
+ find_path(GETOPT_INCLUDE_DIR
+ NAMES getopt.h
+ )
+ find_library(GETOPT_LIBRARY
+ NAMES getopt
+ )
+ find_path(DIRENT_INCLUDE_DIR
+ NAMES dirent.h
+ )
+endif()
+
+# for the headers
+find_package(JSON-C 0.11 REQUIRED)
+
+add_executable(rnpkeys
+ rnpkeys.cpp
+ tui.cpp
+ main.cpp
+ ../rnp/rnpcfg.cpp
+ ../rnp/fficli.cpp
+)
+
+if(BUILD_SHARED_LIBS)
+ target_sources(rnpkeys PRIVATE ../lib/logging.cpp $<TARGET_OBJECTS:rnp-common>)
+endif(BUILD_SHARED_LIBS)
+
+target_include_directories(rnpkeys
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+ "${JSON-C_INCLUDE_DIRS}"
+)
+if(MSVC)
+ target_include_directories(rnpkeys
+ PRIVATE
+ "${GETOPT_INCLUDE_DIR}"
+ "${DIRENT_INCLUDE_DIR}"
+ )
+endif()
+
+target_link_libraries(rnpkeys
+ PRIVATE
+ librnp
+ JSON-C::JSON-C
+)
+if(MSVC)
+ target_link_libraries(rnpkeys
+ PRIVATE
+ "${GETOPT_LIBRARY}"
+ )
+endif()
+
+include(GNUInstallDirs)
+install(TARGETS rnpkeys
+ RUNTIME
+ DESTINATION "${CMAKE_INSTALL_BINDIR}"
+ COMPONENT cli
+)
+
+# Build and install man page
+if (ENABLE_DOC)
+ add_adoc_man("${CMAKE_CURRENT_SOURCE_DIR}/rnpkeys.1.adoc" ${RNP_VERSION})
+endif()
diff --git a/src/rnpkeys/main.cpp b/src/rnpkeys/main.cpp
new file mode 100644
index 0000000..8bcb7e1
--- /dev/null
+++ b/src/rnpkeys/main.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * 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 HOLDERS 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.
+ */
+/* Command line program to perform rnp operations */
+#ifdef _MSC_VER
+#include "uniwin.h"
+#else
+#include <getopt.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include "rnpkeys.h"
+
+extern struct option options[];
+extern const char * usage;
+
+optdefs_t
+get_short_cmd(int ch)
+{
+ switch (ch) {
+ case 'V':
+ return CMD_VERSION;
+ case 'g':
+ return CMD_GENERATE_KEY;
+ case 'l':
+ return CMD_LIST_KEYS;
+ case 'h':
+ [[fallthrough]];
+ default:
+ return CMD_HELP;
+ }
+}
+
+#ifndef RNP_RUN_TESTS
+int
+main(int argc, char **argv)
+#else
+int rnpkeys_main(int argc, char **argv);
+int
+rnpkeys_main(int argc, char **argv)
+#endif
+{
+ if (argc < 2) {
+ print_usage(usage);
+ return EXIT_FAILURE;
+ }
+
+ cli_rnp_t rnp = {};
+#if !defined(RNP_RUN_TESTS) && defined(_WIN32)
+ try {
+ rnp.substitute_args(&argc, &argv);
+ } catch (std::exception &ex) {
+ RNP_LOG("Error converting arguments ('%s')", ex.what());
+ return EXIT_FAILURE;
+ }
+#endif
+
+ rnp_cfg cfg;
+ optdefs_t cmd = CMD_NONE;
+ int optindex = 0;
+ int ch;
+
+ while ((ch = getopt_long(argc, argv, "Vglh", options, &optindex)) != -1) {
+ /* Check for unsupported command/option */
+ if (ch == '?') {
+ print_usage(usage);
+ return EXIT_FAILURE;
+ }
+
+ optdefs_t newcmd = cmd;
+ if (ch >= CMD_LIST_KEYS) {
+ if (!setoption(cfg, &newcmd, options[optindex].val, optarg)) {
+ ERR_MSG("Failed to process argument --%s", options[optindex].name);
+ return EXIT_FAILURE;
+ }
+ } else {
+ newcmd = get_short_cmd(ch);
+ }
+
+ if (cmd && newcmd != cmd) {
+ ERR_MSG("Conflicting commands!");
+ return EXIT_FAILURE;
+ }
+ cmd = newcmd;
+ }
+
+ /* No initialization required for these two commands. */
+ if (cmd == CMD_HELP || cmd == CMD_VERSION) {
+ return cli_rnp_t::ret_code(rnp_cmd(&rnp, cmd, NULL));
+ }
+
+ if (!rnpkeys_init(rnp, cfg)) {
+ return EXIT_FAILURE;
+ }
+
+ if (!cli_rnp_setup(&rnp)) {
+ return EXIT_FAILURE;
+ }
+
+ /* now do the required action for each of the command line args */
+ if (optind == argc) {
+ return cli_rnp_t::ret_code(rnp_cmd(&rnp, cmd, NULL));
+ }
+ bool success = true;
+ for (int i = optind; i < argc; i++) {
+ success = success && rnp_cmd(&rnp, cmd, argv[i]);
+ }
+ return cli_rnp_t::ret_code(success);
+}
diff --git a/src/rnpkeys/rnpkeys.1.adoc b/src/rnpkeys/rnpkeys.1.adoc
new file mode 100644
index 0000000..2b09d17
--- /dev/null
+++ b/src/rnpkeys/rnpkeys.1.adoc
@@ -0,0 +1,453 @@
+= rnpkeys(1)
+RNP
+:doctype: manpage
+:release-version: {component-version}
+:man manual: RNP Manual
+:man source: RNP {release-version}
+
+== NAME
+
+RNPKEYS - OpenPGP key management utility.
+
+== SYNOPSIS
+
+*rnpkeys* [_--homedir_ _dir_] [_OPTIONS_] _COMMAND_
+
+== DESCRIPTION
+
+The _rnpkeys_ command-line utility is part of the _RNP_ suite and
+provides OpenPGP key management functionality, including:
+
+* key listing;
+* key generation;
+* key import/export; and
+* key editing.
+
+
+=== BASICS
+
+By default, *rnp* will apply a _COMMAND_, additionally configured with _OPTIONS_,
+to all _INPUT_FILE_(s) or _stdin_ if no _INPUT_FILE_ is given.
+There are some special cases for _INPUT_FILE_ :
+
+* _-_ (dash) substitutes to _stdin_
+* env:VARIABLE_NAME substitutes to the contents of environment variable VARIABLE_NAME
+
+Depending on the input, output may be written:
+
+* to the specified file with a removed or added file extension (_.pgp_, _.asc_, _.sig_); or
+* to _stdout_.
+
+Without the *--armor* option, output will be in binary.
+
+If _COMMAND_ requires public or private keys, *rnp* will look for the keyrings in *~/.rnp*. The options *--homedir* and *--keyfile* override this (see below).
+
+If _COMMAND_ needs a password, *rnp* will ask for it via *stdin* or *tty*,
+unless the *--password* or *--pass-fd* option was specified.
+
+
+By default, *rnpkeys* will use keyrings stored in the _~/.rnp_ directory.
+
+This behavior may be overridden with the _--homedir_ option.
+
+If _COMMAND_ needs a password, the command will prompt the caller
+via _stdin_ or _tty_, unless the *--password* or *--pass-fd*
+options were also used.
+
+=== SPECIFYING KEYS
+
+Most *rnpkeys* commands require a key locator or a filter,
+representing one or more keys.
+
+It may be specified in one of the following ways:
+
+*userid*::
+Or just part of the *userid*.
+For *"Alice <alice@rnpgp.com>"*, the following methods are considered identical:
+
+** _alice_
+** _alice@rnpgp_
+** _rnpgp.com_
+
+*keyid*::
+Or its right-most 8 characters. With or without _0x_ at the beginning and spaces/tabs inside. Such as:
+
+** _0x725F6F2D6D5F6120_
+** _"725F6F2D 6D5F6120"_
+** _0x6D5F6120_
+
+*key fingerprint*: The 40-character key fingerprint, such as:
+
+** _"0x416E746F 6E537669 72696465 6E6B6F20"_
+
+
+
+== COMMANDS
+
+=== INFORMATIONAL
+
+*-h*, *--help*::
+Displays a short help message. No options are expected.
+
+*-V*, *--version*::
+Displays version information. No options are expected.
+
+*-l*, *--list-keys*::
+List out keys and some brief information about each. +
++
+Additional options:
+
+*--with-sigs*:::
+Additionally display signatures of listed keys.
+
+
+=== KEY GENERATION
+
+*-g*, *--generate-key*::
+Generate a new keypair. +
++
+Without additional options, an RSA primary key pair with an RSA sub-key pair will be generated, and prompting for the encryption password afterwards.
++
+Additional options:
+
+*--numbits*:::
+Overrides the default RSA key size of *2048* bits.
+
+*--expiration* _TIME_:::
+Set key and subkey expiration time, counting from the creation time. +
++
+By default generated keys do not expire. +
++
+Expiration time can be specified as:
+
+* expiration date in the ISO 8601:2019 date format (_yyyy-mm-dd_); or
+* hours/days/months/years since creation time with the syntax of _20h_/_30d_/_1m_/_1y_;
+* number of seconds.
+
+*--expert*:::
+Select key algorithms interactively and override default settings.
+
+*--userid*:::
+Specifies the _userid_ to be used in generation.
+
+*--hash*:::
+Specify the hash algorithm used in generation.
+
+*--cipher*:::
+Specify the encryption algorithm used in generation.
+
+*--s2k-iterations*:::
+Specify the number of iterations for the S2K (string-to-key) process. +
++
+This is used during the derivation of the symmetric key, which
+encrypts a secret key from the password. +
+
+*--s2k-msec*:::
+Specify that *rnpkeys* should automatically pick a
+*--s2k-iterations* value such that the single key derivation operation
+would take _NUMBER_ of milliseconds on the current system. +
++
+For example, setting it to _2000_ would mean that each secret key
+decryption operation would take around 2 seconds (on the current machine).
+
+
+=== KEY/SIGNATURE IMPORT
+
+*--import*, *--import-keys*, *--import-sigs*::
+Import keys or signatures. +
++
+While *rnpkeys* automatically detects the input data format,
+one may still wish to specify whether the input provides keys or signatures. +
++
+By default, the import process will stop on the first discovered
+erroneous key or signature. +
++
+Additional options:
+
+*--permissive*:::
+Skip errored or unsupported packets during the import process.
+
+=== KEY/SIGNATURE EXPORT
+
+*--export-key* [*--userid*=_FILTER_] [_FILTER_]::
+Export key(s). Only export keys that match _FILTER_ if _FILTER_ is given. +
++
+If filter matches a primary key, the subkeys of the primary key are also exported.
++
+By default, key data is written to _stdout_ in ASCII-armored format.
++
+Additional options:
+
+*--output* _PATH_:::
+Specifies output to be written to a file name instead of _stdout_.
+
+*--secret*:::
+Without this option specified, the command will only export public key(s).
+This option must be provided to export secret key(s).
+
+*--export-rev* _KEY_::
+Export the revocation signature for a specified secret key. +
++
+The revocation signature can be used later in a case of key loss or compromise.
++
+Additional options:
+
+*--rev-type*:::
+Specifies type of key revocation.
+
+*--rev-reason*:::
+Specifies reason for key revocation.
+
+
+=== KEY MANIPULATION
+
+*--revoke-key* _KEY_::
+Issue revocation signature for the secret key, and save it in the keyring. +
++
+Revoked keys cannot be used further. +
++
+Additional options:
+
+*--rev-type*:::
+Specifies type of key revocation, see *options* section for the available values.
+
+*--rev-reason*:::
+Specifies reason for key revocation.
+
+
+*--remove-key* _KEY_::
+Remove the specified key. +
++
+If a primary key is specified, then all of its subkeys are also removed. +
++
+If the specified key is a secret key, then it will not be deleted without
+confirmation.
++
+Additional options:
+
+*--force*:::
+Forces removal of a secret key without prompting the user.
+
+*--edit-key* _KEY_::
+Edit or update information, associated with a key. Should be accompanied with editing option. +
++
+Currently the following options are available: +
++
+*--add-subkey*:::
+Generate and add a new subkey to the existing primary key. All additional options for the
+*--generate-key* command apply for subkey generation as well, except *--userid*.
+
+*--check-cv25519-bits*:::
+Check whether least significant/most significant bits of Curve25519 ECDH subkey are correctly set.
+RNP internally sets those bits to required values (3 least significant bits and most significant bit must be zero) during decryption,
+however other implementations (GnuPG) may require those bits to be set in key material.
+_KEY_ must specify the exact subkey via keyid or fingerprint.
+
+*--fix-cv25519-bits*:::
+Set least significant/most significant bits of Curve25519 ECDH subkey to the correct values, and save a key.
+So later export of the key would ensure compatibility with other implementations (like GnuPG).
+This operation would require the password for your secret key.
+Since version 0.16.0 of RNP generated secret key is stored with bits set to a needed value,
+however, this may be needed to fix older keys or keys generated by other implementations.
+_KEY_ must specify the exact subkey via keyid or fingerprint.
+
+*--set-expire* _TIME_:::
+Set key expiration time. See the description of the *--expiration* option for possible time formats.
+Setting argument to 0 removes key expiration, the key would never expire. It is not recommended
+due to security reasons.
+
+=== OPTIONS
+
+*--homedir* _DIR_::
+Change homedir (where RNP looks for keyrings) to the specified value. +
++
+The default homedir is _~/.rnp_ .
+
+*--output* _PATH_::
+Write data processing related output to the file specified. +
++
+Combine it with *--overwrite* to overwrite file if it already exists.
+
+*--overwrite*::
+Overwrite output file if it already exists. +
++
+
+*--userid* _USERID_::
+Use the specified _userid_ during key generation and in some
+key-searching operations.
+
+*--numbits* _BITS_::
+Specify size in bits for the generated key and subkey. +
++
+_bits_ may be in range *1024*-*16384*, as long as the public key algorithm
+does not place additional limits.
+
+*--cipher* _ALGORITHM_::
+Set the key encryption algorithm. This is only used in key generation. +
++
+The default value is _AES256_.
+
+*--hash* _ALGORITHM_::
+Use the specified hash algorithm for signatures and derivation of the encrypting key from password for secret key encryption. +
++
+The default value is _SHA256_.
+
+*--expert*::
+Use the *expert key generation* mode, allowing the selection of
+key/subkey algorithms. +
++
+The following types of keys can be generated in this mode: +
++
+--
+** *DSA* key with *ElGamal* encryption subkey
+** *DSA* key with *RSA* subkey
+** *ECDSA* key with *ECDH* subkey
+** *EdDSA* key with *x25519* subkey
+** *SM2* key with subkey
+--
++
+Specifically, for *ECDSA* and *ECDH* the underlying curve can also be specified: +
++
+--
+** _NIST P-256_, _NIST P-384_, _NIST P-521_
+** _brainpoolP256r1_, _brainpoolP384r1_, _brainpoolP512r1_
+** _secp256k1_
+--
+
+*--pass-fd* _FD_::
+Specify a file descriptor to read passwords from instead of from _stdin_/_tty_. +
++
+Useful for automated or non-interactive sessions.
+
+*--password* _PASSWORD_::
+Use the specified password when it is needed. +
++
+WARNING: Not recommended for production use due to potential security issues.
+Use *--pass-fd* for batch operations instead.
+
+*--with-sigs*::
+Print signature information when listing keys via the *-l* command.
+
+*--force*::
+Force actions to happen without prompting the user. +
++
+This applies to cases such as secret key removal, revoking an already revoked key and so on.
+
+*--permissive*::
+Skip malformed or unknown keys/signatures during key import. +
++
+By default, *rnpkeys* will stop on the first erroring packet
+and exit with an error.
+
+*--rev-type* _TYPE_::
+Use the specified type during revocation signature generation instead of the default _0_. +
++
+The following values are supported: +
++
+--
+** 0, or "no": no revocation type specified.
+** 1, or "superseded": key was superseded with another key.
+** 2, or "compromised": key was compromised and no longer valid.
+** 3, or "retired": key is retired.
+--
++
+Please refer to *IETF RFC 4880* for details.
+
+*--rev-reason* _REASON_::
+Add the specified human-readable revocation _REASON_ to the
+signature instead of an empty string.
+
+*--s2k-iterations* _NUMBER_::
+Specify the number of iterations for the S2K (string-to-key) process. +
++
+This is used during the derivation of the symmetric key, which
+encrypts a secret key from the password. +
++
+Please refer to IETF RFC 4880 for further details.
+
+*--s2k-msec* _NUMBER_::
+Specify that *rnpkeys* should automatically pick a
+*--s2k-iterations* value such that the single key derivation operation
+would take _NUMBER_ of milliseconds on the current system. +
++
+For example, setting it to _2000_ would mean that each secret key
+decryption operation would take around 2 seconds (on the current machine).
+
+*--notty*::
+Disable use of tty. +
++
+By default RNP would detect whether TTY is attached and use it for user prompts. +
++
+This option overrides default behaviour so user input may be passed in batch mode.
+
+*--current-time* _TIME_::
+Override system's time with a specified value. +
++
+By default RNP uses system's time in all signature/key checks, however in some scenarios it could be needed to override this. +
++
+*TIME* could be specified in the ISO 8601-1:2019 date format (_yyyy-mm-dd_), or in the UNIX timestamp format.
+
+== EXIT STATUS
+
+_0_::
+ Success.
+
+_Non-zero_::
+ Failure.
+
+== EXAMPLES
+
+The following examples demonstrate method of usage of the _rnpkeys_ command.
+
+=== EXAMPLE 1: IMPORT EXISTING KEYS FROM THE GNUPG
+
+Following oneliner may be used to import all public keys from the GnuPG:
+
+*gpg* *-a* *--export* | *rnpkeys* *--import* _-_
+
+To import all secret keys the following command should be used (please note, that you'll be asked for secret key password(s)):
+
+*gpg* *-a* *--export-secret-keys* | *rnpkeys* *--import* _-_
+
+=== EXAMPLE 2: GENERATE A NEW KEY
+
+This example generates a new key with specified userid and expiration.
+Also it enables "expert" mode, allowing the selection of key/subkey algorithms.
+
+*rnpkeys* *--generate* *--userid* *"john@doe.com"* *--expert* *--expiration* *1y*
+
+== BUGS
+
+Please report _issues_ via the RNP public issue tracker at:
+https://github.com/rnpgp/rnp/issues.
+
+_Security reports_ or _security-sensitive feedback_ should be reported
+according to the instructions at:
+https://www.rnpgp.org/feedback.
+
+
+== AUTHORS
+
+*RNP* is an open source project led by Ribose and has
+received contributions from numerous individuals and
+organizations.
+
+
+== RESOURCES
+
+*Web site*: https://www.rnpgp.org
+
+*Source repository*: https://github.com/rnpgp/rnp
+
+
+== COPYING
+
+Copyright \(C) 2017-2021 Ribose.
+The RNP software suite is _freely licensed_:
+please refer to the *LICENSE* file for details.
+
+
+
+== SEE ALSO
+
+*rnp(1)*, *librnp(3)*
diff --git a/src/rnpkeys/rnpkeys.cpp b/src/rnpkeys/rnpkeys.cpp
new file mode 100644
index 0000000..1a6997c
--- /dev/null
+++ b/src/rnpkeys/rnpkeys.cpp
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * 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 HOLDERS 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.
+ */
+/* Command line program to perform rnp operations */
+
+#ifdef _MSC_VER
+#include "uniwin.h"
+#else
+#include <getopt.h>
+#endif
+#include <string.h>
+#include <stdarg.h>
+#include "rnpkeys.h"
+#include "str-utils.h"
+#include <set>
+
+const char *usage =
+ "Manipulate OpenPGP keys and keyrings.\n"
+ "Usage: rnpkeys --command [options] [files]\n"
+ "Commands:\n"
+ " -h, --help This help message.\n"
+ " -V, --version Print RNP version information.\n"
+ " -g, --generate-key Generate a new keypair (default is RSA).\n"
+ " --userid Specify key's userid.\n"
+ " --expert Select key type, size, and additional parameters.\n"
+ " --numbits Override default key size (2048).\n"
+ " --expiration Set key and subkey expiration time.\n"
+ " --cipher Set cipher used to encrypt a secret key.\n"
+ " --hash Set hash which is used for key derivation.\n"
+ " --allow-weak-hash Allow usage of a weak hash algorithm.\n"
+ " -l, --list-keys List keys in the keyrings.\n"
+ " --secret List secret keys instead of public ones.\n"
+ " --with-sigs List signatures as well.\n"
+ " --import Import keys or signatures.\n"
+ " --import-keys Import keys.\n"
+ " --import-sigs Import signatures.\n"
+ " --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-rev Export a key's revocation.\n"
+ " --rev-type Set revocation type.\n"
+ " --rev-reason Human-readable reason for revocation.\n"
+ " --revoke-key Revoke a key specified.\n"
+ " --remove-key Remove a key specified.\n"
+ " --edit-key Edit key properties.\n"
+ " --add-subkey Add new subkey.\n"
+ " --check-cv25519-bits Check whether Cv25519 subkey bits are correct.\n"
+ " --fix-cv25519-bits Fix Cv25519 subkey bits.\n"
+ " --set-expire Set key expiration time.\n"
+ "\n"
+ "Other options:\n"
+ " --homedir Override home directory (default is ~/.rnp/).\n"
+ " --password Password, which should be used during operation.\n"
+ " --pass-fd Read password(s) from the file descriptor.\n"
+ " --force Force operation (like secret key removal).\n"
+ " --output [file, -] Write data to the specified file or stdout.\n"
+ " --overwrite Overwrite output file without a prompt.\n"
+ " --notty Do not write anything to the TTY.\n"
+ " --current-time Override system's time.\n"
+ "\n"
+ "See man page for a detailed listing and explanation.\n"
+ "\n";
+
+struct option options[] = {
+ /* key-management commands */
+ {"list-keys", no_argument, NULL, CMD_LIST_KEYS},
+ {"export", no_argument, NULL, CMD_EXPORT_KEY},
+ {"export-key", optional_argument, NULL, CMD_EXPORT_KEY},
+ {"import", no_argument, NULL, CMD_IMPORT},
+ {"import-key", no_argument, NULL, CMD_IMPORT_KEYS},
+ {"import-keys", no_argument, NULL, CMD_IMPORT_KEYS},
+ {"import-sigs", no_argument, NULL, CMD_IMPORT_SIGS},
+ {"gen", optional_argument, NULL, CMD_GENERATE_KEY},
+ {"gen-key", optional_argument, NULL, CMD_GENERATE_KEY},
+ {"generate", optional_argument, NULL, CMD_GENERATE_KEY},
+ {"generate-key", optional_argument, NULL, CMD_GENERATE_KEY},
+ {"export-rev", no_argument, NULL, CMD_EXPORT_REV},
+ {"export-revocation", no_argument, NULL, CMD_EXPORT_REV},
+ {"revoke-key", no_argument, NULL, CMD_REVOKE_KEY},
+ {"remove-key", no_argument, NULL, CMD_REMOVE_KEY},
+ {"edit-key", no_argument, NULL, CMD_EDIT_KEY},
+ /* debugging commands */
+ {"help", no_argument, NULL, CMD_HELP},
+ {"version", no_argument, NULL, CMD_VERSION},
+ {"debug", required_argument, NULL, OPT_DEBUG},
+ /* options */
+ {"coredumps", no_argument, NULL, OPT_COREDUMPS},
+ {"keystore-format", required_argument, NULL, OPT_KEY_STORE_FORMAT},
+ {"userid", required_argument, NULL, OPT_USERID},
+ {"with-sigs", no_argument, NULL, OPT_WITH_SIGS},
+ {"hash", required_argument, NULL, OPT_HASH_ALG},
+ {"home", required_argument, NULL, OPT_HOMEDIR},
+ {"homedir", required_argument, NULL, OPT_HOMEDIR},
+ {"numbits", required_argument, NULL, OPT_NUMBITS},
+ {"s2k-iterations", required_argument, NULL, OPT_S2K_ITER},
+ {"s2k-msec", required_argument, NULL, OPT_S2K_MSEC},
+ {"expiration", required_argument, NULL, OPT_EXPIRATION},
+ {"pass-fd", required_argument, NULL, OPT_PASSWDFD},
+ {"password", required_argument, NULL, OPT_PASSWD},
+ {"results", required_argument, NULL, OPT_RESULTS},
+ {"cipher", required_argument, NULL, OPT_CIPHER},
+ {"expert", no_argument, NULL, OPT_EXPERT},
+ {"output", required_argument, NULL, OPT_OUTPUT},
+ {"overwrite", no_argument, NULL, OPT_OVERWRITE},
+ {"force", no_argument, NULL, OPT_FORCE},
+ {"secret", no_argument, NULL, OPT_SECRET},
+ {"rev-type", required_argument, NULL, OPT_REV_TYPE},
+ {"rev-reason", required_argument, NULL, OPT_REV_REASON},
+ {"permissive", no_argument, NULL, OPT_PERMISSIVE},
+ {"notty", no_argument, NULL, OPT_NOTTY},
+ {"fix-cv25519-bits", no_argument, NULL, OPT_FIX_25519_BITS},
+ {"check-cv25519-bits", no_argument, NULL, OPT_CHK_25519_BITS},
+ {"add-subkey", no_argument, NULL, OPT_ADD_SUBKEY},
+ {"set-expire", required_argument, NULL, OPT_SET_EXPIRE},
+ {"current-time", required_argument, NULL, OPT_CURTIME},
+ {"allow-weak-hash", no_argument, NULL, OPT_ALLOW_WEAK_HASH},
+ {NULL, 0, NULL, 0},
+};
+
+/* list keys */
+static bool
+print_keys_info(cli_rnp_t *rnp, FILE *fp, const char *filter)
+{
+ bool psecret = rnp->cfg().get_bool(CFG_SECRET);
+ bool psigs = rnp->cfg().get_bool(CFG_WITH_SIGS);
+ int flags = CLI_SEARCH_SUBKEYS_AFTER | (psecret ? CLI_SEARCH_SECRET : 0);
+ std::vector<rnp_key_handle_t> keys;
+
+ if (!cli_rnp_keys_matching_string(rnp, keys, filter ? filter : "", flags)) {
+ fprintf(fp, "Key(s) not found.\n");
+ return false;
+ }
+ fprintf(fp, "%d key%s found\n", (int) keys.size(), (keys.size() == 1) ? "" : "s");
+ for (auto key : keys) {
+ cli_rnp_print_key_info(fp, rnp->ffi, key, psecret, psigs);
+ }
+
+ fprintf(fp, "\n");
+ /* clean up */
+ clear_key_handles(keys);
+ return true;
+}
+
+static bool
+import_keys(cli_rnp_t *rnp, rnp_input_t input, const std::string &inname)
+{
+ std::set<std::string> new_pub_keys;
+ std::set<std::string> new_sec_keys;
+ std::set<std::string> updated_keys;
+ bool res = false;
+ bool updated = false;
+ size_t unchanged_keys = 0;
+ size_t processed_keys = 0;
+
+ uint32_t flags = RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS |
+ RNP_LOAD_SAVE_SINGLE | RNP_LOAD_SAVE_BASE64;
+
+ bool permissive = rnp->cfg().get_bool(CFG_PERMISSIVE);
+ if (permissive) {
+ flags |= RNP_LOAD_SAVE_PERMISSIVE;
+ }
+
+ do {
+ /* load keys one-by-one */
+ char * results = NULL;
+ rnp_result_t ret = rnp_import_keys(rnp->ffi, input, flags, &results);
+ if (ret == RNP_ERROR_EOF) {
+ res = true;
+ break;
+ }
+ if (ret && updated) {
+ /* some keys were imported, but then error occurred */
+ ERR_MSG("warning: not all data was processed.");
+ res = true;
+ break;
+ }
+ if (ret) {
+ ERR_MSG("failed to import key(s) from %s, stopping.", inname.c_str());
+ break;
+ }
+
+ // print information about imported key(s)
+ json_object *jso = json_tokener_parse(results);
+ rnp_buffer_destroy(results);
+ if (!jso) {
+ ERR_MSG("invalid key import resulting JSON");
+ break;
+ }
+ json_object *keys = NULL;
+ if (!json_object_object_get_ex(jso, "keys", &keys)) {
+ ERR_MSG("invalid key import JSON contents");
+ json_object_put(jso);
+ break;
+ }
+ processed_keys += json_object_array_length(keys);
+ for (size_t idx = 0; idx < (size_t) json_object_array_length(keys); idx++) {
+ json_object * keyinfo = json_object_array_get_idx(keys, idx);
+ rnp_key_handle_t key = NULL;
+ if (!keyinfo) {
+ continue;
+ }
+ std::string pub_status = json_obj_get_str(keyinfo, "public");
+ std::string sec_status = json_obj_get_str(keyinfo, "secret");
+ const char *fphex = json_obj_get_str(keyinfo, "fingerprint");
+
+ if (pub_status == "new") {
+ new_pub_keys.insert(fphex);
+ updated = true;
+ }
+ if (sec_status == "new") {
+ new_sec_keys.insert(fphex);
+ updated = true;
+ }
+ if (pub_status == "updated" || sec_status == "updated") {
+ updated_keys.insert(fphex);
+ updated = true;
+ }
+ if (pub_status == "unchanged" || sec_status == "unchanged") {
+ if (!new_pub_keys.count(fphex) && !new_sec_keys.count(fphex) &&
+ !updated_keys.count(fphex)) {
+ unchanged_keys++;
+ continue;
+ }
+ }
+ if (rnp_locate_key(rnp->ffi, "fingerprint", fphex, &key) || !key) {
+ ERR_MSG("failed to locate key with fingerprint %s", fphex);
+ continue;
+ }
+ cli_rnp_print_key_info(stdout, rnp->ffi, key, true, false);
+ rnp_key_handle_destroy(key);
+ }
+ json_object_put(jso);
+ } while (1);
+
+ // print statistics
+ ERR_MSG("Import finished: %lu key%s processed, %lu new public keys, %lu new secret keys, "
+ "%lu updated, %lu unchanged.",
+ processed_keys,
+ (processed_keys != 1) ? "s" : "",
+ new_pub_keys.size(),
+ new_sec_keys.size(),
+ updated_keys.size(),
+ unchanged_keys);
+
+ if (updated) {
+ // set default key if we didn't have one
+ if (rnp->defkey().empty()) {
+ rnp->set_defkey();
+ }
+
+ // save public and secret keyrings
+ if (!cli_rnp_save_keyrings(rnp)) {
+ ERR_MSG("failed to save keyrings");
+ }
+ }
+ return res;
+}
+
+static bool
+import_sigs(cli_rnp_t *rnp, rnp_input_t input, const std::string &inname)
+{
+ bool res = false;
+ char * results = NULL;
+ json_object *jso = NULL;
+ json_object *sigs = NULL;
+ int unknown_sigs = 0;
+ int new_sigs = 0;
+ int old_sigs = 0;
+
+ if (rnp_import_signatures(rnp->ffi, input, 0, &results)) {
+ ERR_MSG("Failed to import signatures from %s", inname.c_str());
+ goto done;
+ }
+ // print information about imported signature(s)
+ jso = json_tokener_parse(results);
+ if (!jso || !json_object_object_get_ex(jso, "sigs", &sigs)) {
+ ERR_MSG("Invalid signature import result");
+ goto done;
+ }
+
+ for (size_t idx = 0; idx < (size_t) json_object_array_length(sigs); idx++) {
+ json_object *siginfo = json_object_array_get_idx(sigs, idx);
+ if (!siginfo) {
+ continue;
+ }
+ const char *status = json_obj_get_str(siginfo, "public");
+ std::string pub_status = status ? status : "unknown";
+ status = json_obj_get_str(siginfo, "secret");
+ std::string sec_status = status ? status : "unknown";
+
+ if ((pub_status == "new") || (sec_status == "new")) {
+ new_sigs++;
+ } else if ((pub_status == "unchanged") || (sec_status == "unchanged")) {
+ old_sigs++;
+ } else {
+ unknown_sigs++;
+ }
+ }
+
+ // print status information
+ ERR_MSG("Import finished: %d new signature%s, %d unchanged, %d unknown.",
+ new_sigs,
+ (new_sigs != 1) ? "s" : "",
+ old_sigs,
+ unknown_sigs);
+
+ // save public and secret keyrings
+ if ((new_sigs > 0) && !cli_rnp_save_keyrings(rnp)) {
+ ERR_MSG("Failed to save keyrings");
+ goto done;
+ }
+ res = true;
+done:
+ json_object_put(jso);
+ rnp_buffer_destroy(results);
+ return res;
+}
+
+static bool
+import(cli_rnp_t *rnp, const std::string &spec, int cmd)
+{
+ if (spec.empty()) {
+ ERR_MSG("Import path isn't specified");
+ return false;
+ }
+ rnp_input_t input = cli_rnp_input_from_specifier(*rnp, spec, NULL);
+ if (!input) {
+ ERR_MSG("Failed to create input for %s", spec.c_str());
+ return false;
+ }
+ if (cmd == CMD_IMPORT) {
+ char *contents = NULL;
+ if (rnp_guess_contents(input, &contents)) {
+ ERR_MSG("Warning! Failed to guess content type to import. Assuming keys.");
+ }
+ cmd = (contents && !strcmp(contents, "signature")) ? CMD_IMPORT_SIGS : CMD_IMPORT_KEYS;
+ rnp_buffer_destroy(contents);
+ }
+
+ bool res = false;
+ switch (cmd) {
+ case CMD_IMPORT_KEYS:
+ res = import_keys(rnp, input, spec);
+ break;
+ case CMD_IMPORT_SIGS:
+ res = import_sigs(rnp, input, spec);
+ break;
+ default:
+ ERR_MSG("Unexpected command: %d", cmd);
+ }
+ rnp_input_destroy(input);
+ return res;
+}
+
+/* print a usage message */
+void
+print_usage(const char *usagemsg)
+{
+ cli_rnp_print_praise();
+ puts(usagemsg);
+}
+
+/* do a command once for a specified file 'f' */
+bool
+rnp_cmd(cli_rnp_t *rnp, optdefs_t cmd, const char *f)
+{
+ std::string fs;
+
+ switch (cmd) {
+ case CMD_LIST_KEYS:
+ if (!f && rnp->cfg().get_count(CFG_USERID)) {
+ fs = rnp->cfg().get_str(CFG_USERID, 0);
+ f = fs.c_str();
+ }
+ return print_keys_info(rnp, stdout, f);
+ case CMD_EXPORT_KEY: {
+ if (!f && rnp->cfg().get_count(CFG_USERID)) {
+ fs = rnp->cfg().get_str(CFG_USERID, 0);
+ f = fs.c_str();
+ }
+ if (!f) {
+ ERR_MSG("No key specified.");
+ return 0;
+ }
+ return cli_rnp_export_keys(rnp, f);
+ }
+ case CMD_IMPORT:
+ case CMD_IMPORT_KEYS:
+ case CMD_IMPORT_SIGS:
+ return import(rnp, f ? f : "", cmd);
+ case CMD_GENERATE_KEY: {
+ if (!f) {
+ size_t count = rnp->cfg().get_count(CFG_USERID);
+ if (count == 1) {
+ fs = rnp->cfg().get_str(CFG_USERID, 0);
+ f = fs.c_str();
+ } else if (count > 1) {
+ ERR_MSG("Only single userid is supported for generated keys");
+ return false;
+ }
+ }
+ return cli_rnp_generate_key(rnp, f);
+ }
+ case CMD_EXPORT_REV: {
+ if (!f) {
+ ERR_MSG("You need to specify key to generate revocation for.");
+ return false;
+ }
+ return cli_rnp_export_revocation(rnp, f);
+ }
+ case CMD_REVOKE_KEY: {
+ if (!f) {
+ ERR_MSG("You need to specify key or subkey to revoke.");
+ return false;
+ }
+ return cli_rnp_revoke_key(rnp, f);
+ }
+ case CMD_REMOVE_KEY: {
+ if (!f) {
+ ERR_MSG("You need to specify key or subkey to remove.");
+ return false;
+ }
+ return cli_rnp_remove_key(rnp, f);
+ }
+ case CMD_EDIT_KEY: {
+ if (!f) {
+ ERR_MSG("You need to specify a key or subkey to edit.");
+ return false;
+ }
+ return rnp->edit_key(f);
+ }
+ case CMD_VERSION:
+ cli_rnp_print_praise();
+ return true;
+ case CMD_HELP:
+ default:
+ print_usage(usage);
+ return true;
+ }
+}
+
+/* set the option */
+bool
+setoption(rnp_cfg &cfg, optdefs_t *cmd, int val, const char *arg)
+{
+ switch (val) {
+ case OPT_COREDUMPS:
+#ifdef _WIN32
+ ERR_MSG("warning: --coredumps doesn't make sense on windows systems.");
+#endif
+ cfg.set_bool(CFG_COREDUMPS, true);
+ return true;
+ case CMD_GENERATE_KEY:
+ cfg.set_bool(CFG_NEEDSSECKEY, true);
+ *cmd = (optdefs_t) val;
+ return true;
+ case OPT_EXPERT:
+ cfg.set_bool(CFG_EXPERT, true);
+ return true;
+ case CMD_LIST_KEYS:
+ case CMD_EXPORT_KEY:
+ case CMD_EXPORT_REV:
+ case CMD_REVOKE_KEY:
+ case CMD_REMOVE_KEY:
+ case CMD_EDIT_KEY:
+ case CMD_IMPORT:
+ case CMD_IMPORT_KEYS:
+ case CMD_IMPORT_SIGS:
+ case CMD_HELP:
+ case CMD_VERSION:
+ *cmd = (optdefs_t) val;
+ return true;
+ /* options */
+ case OPT_KEY_STORE_FORMAT:
+ cfg.set_str(CFG_KEYSTOREFMT, arg);
+ return true;
+ case OPT_USERID:
+ cfg.add_str(CFG_USERID, arg);
+ return true;
+ case OPT_HOMEDIR:
+ cfg.set_str(CFG_HOMEDIR, arg);
+ return true;
+ case OPT_NUMBITS: {
+ int bits = 0;
+ if (!rnp::str_to_int(arg, bits) || (bits < 1024) || (bits > 16384)) {
+ ERR_MSG("wrong bits value: %s", arg);
+ return false;
+ }
+ cfg.set_int(CFG_NUMBITS, bits);
+ return true;
+ }
+ case OPT_ALLOW_WEAK_HASH:
+ cfg.set_bool(CFG_WEAK_HASH, true);
+ return true;
+ case OPT_HASH_ALG:
+ return cli_rnp_set_hash(cfg, arg);
+ case OPT_S2K_ITER: {
+ int iterations = atoi(arg);
+ if (!iterations) {
+ ERR_MSG("Wrong iterations value: %s", arg);
+ return false;
+ }
+ cfg.set_int(CFG_S2K_ITER, iterations);
+ return true;
+ }
+ case OPT_EXPIRATION:
+ cfg.set_str(CFG_KG_PRIMARY_EXPIRATION, arg);
+ cfg.set_str(CFG_KG_SUBKEY_EXPIRATION, arg);
+ return true;
+ case OPT_S2K_MSEC: {
+ int msec = 0;
+ if (!rnp::str_to_int(arg, msec) || !msec) {
+ ERR_MSG("Invalid s2k msec value: %s", arg);
+ return false;
+ }
+ cfg.set_int(CFG_S2K_MSEC, msec);
+ return true;
+ }
+ case OPT_PASSWDFD:
+ cfg.set_str(CFG_PASSFD, arg);
+ return true;
+ case OPT_PASSWD:
+ cfg.set_str(CFG_PASSWD, arg);
+ return true;
+ case OPT_RESULTS:
+ cfg.set_str(CFG_IO_RESS, arg);
+ return true;
+ case OPT_CIPHER:
+ return cli_rnp_set_cipher(cfg, arg);
+ case OPT_DEBUG:
+ ERR_MSG("Option --debug is deprecated, ignoring.");
+ return true;
+ case OPT_OUTPUT:
+ if (!arg) {
+ ERR_MSG("No output filename argument provided");
+ return false;
+ }
+ cfg.set_str(CFG_OUTFILE, arg);
+ return true;
+ case OPT_OVERWRITE:
+ cfg.set_bool(CFG_OVERWRITE, true);
+ return true;
+ case OPT_FORCE:
+ cfg.set_bool(CFG_FORCE, true);
+ return true;
+ case OPT_SECRET:
+ cfg.set_bool(CFG_SECRET, true);
+ return true;
+ case OPT_WITH_SIGS:
+ cfg.set_bool(CFG_WITH_SIGS, true);
+ return true;
+ case OPT_REV_TYPE: {
+ std::string revtype = arg;
+ if (revtype == "0") {
+ revtype = "no";
+ } else if (revtype == "1") {
+ revtype = "superseded";
+ } else if (revtype == "2") {
+ revtype = "compromised";
+ } else if (revtype == "3") {
+ revtype = "retired";
+ }
+ cfg.set_str(CFG_REV_TYPE, revtype);
+ return true;
+ }
+ case OPT_REV_REASON:
+ cfg.set_str(CFG_REV_REASON, arg);
+ return true;
+ case OPT_PERMISSIVE:
+ cfg.set_bool(CFG_PERMISSIVE, true);
+ return true;
+ case OPT_NOTTY:
+ cfg.set_bool(CFG_NOTTY, true);
+ return true;
+ case OPT_FIX_25519_BITS:
+ cfg.set_bool(CFG_FIX_25519_BITS, true);
+ return true;
+ case OPT_CHK_25519_BITS:
+ cfg.set_bool(CFG_CHK_25519_BITS, true);
+ return true;
+ case OPT_CURTIME:
+ cfg.set_str(CFG_CURTIME, arg);
+ return true;
+ case OPT_ADD_SUBKEY:
+ cfg.set_bool(CFG_ADD_SUBKEY, true);
+ return true;
+ case OPT_SET_EXPIRE:
+ cfg.set_str(CFG_SET_KEY_EXPIRE, arg);
+ return true;
+ default:
+ *cmd = CMD_HELP;
+ return true;
+ }
+}
+
+bool
+rnpkeys_init(cli_rnp_t &rnp, const rnp_cfg &cfg)
+{
+ rnp_cfg rnpcfg;
+ rnpcfg.load_defaults();
+ rnpcfg.set_int(CFG_NUMBITS, DEFAULT_RSA_NUMBITS);
+ rnpcfg.set_str(CFG_IO_RESS, "<stdout>");
+ rnpcfg.copy(cfg);
+
+ if (!cli_cfg_set_keystore_info(rnpcfg)) {
+ ERR_MSG("fatal: cannot set keystore info");
+ return false;
+ }
+ if (!rnp.init(rnpcfg)) {
+ ERR_MSG("fatal: failed to initialize rnpkeys");
+ return false;
+ }
+ if (!cli_rnp_check_weak_hash(&rnp)) {
+ ERR_MSG("Weak hash algorithm detected. Pass --allow-weak-hash option if you really "
+ "want to use it.");
+ return false;
+ }
+ /* TODO: at some point we should check for error here */
+ (void) rnp.load_keyrings(true);
+ return true;
+}
diff --git a/src/rnpkeys/rnpkeys.h b/src/rnpkeys/rnpkeys.h
new file mode 100644
index 0000000..611fcc1
--- /dev/null
+++ b/src/rnpkeys/rnpkeys.h
@@ -0,0 +1,80 @@
+#ifndef RNPKEYS_H_
+#define RNPKEYS_H_
+
+#include <stdbool.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#else
+#include "uniwin.h"
+#endif
+#include "../rnp/fficli.h"
+#include "logging.h"
+
+#define DEFAULT_RSA_NUMBITS 2048
+
+typedef enum {
+ /* commands */
+ CMD_NONE = 0,
+ CMD_LIST_KEYS = 260,
+ CMD_EXPORT_KEY,
+ CMD_IMPORT,
+ CMD_IMPORT_KEYS,
+ CMD_IMPORT_SIGS,
+ CMD_GENERATE_KEY,
+ CMD_EXPORT_REV,
+ CMD_REVOKE_KEY,
+ CMD_REMOVE_KEY,
+ CMD_EDIT_KEY,
+ CMD_VERSION,
+ CMD_HELP,
+
+ /* options */
+ OPT_KEY_STORE_FORMAT,
+ OPT_USERID,
+ OPT_HOMEDIR,
+ OPT_NUMBITS,
+ OPT_ALLOW_WEAK_HASH,
+ OPT_HASH_ALG,
+ OPT_COREDUMPS,
+ OPT_PASSWDFD,
+ OPT_PASSWD,
+ OPT_RESULTS,
+ OPT_CIPHER,
+ OPT_EXPERT,
+ OPT_OUTPUT,
+ OPT_OVERWRITE,
+ OPT_FORCE,
+ OPT_SECRET,
+ OPT_S2K_ITER,
+ OPT_S2K_MSEC,
+ OPT_EXPIRATION,
+ OPT_WITH_SIGS,
+ OPT_REV_TYPE,
+ OPT_REV_REASON,
+ OPT_PERMISSIVE,
+ OPT_NOTTY,
+ OPT_FIX_25519_BITS,
+ OPT_CHK_25519_BITS,
+ OPT_CURTIME,
+ OPT_ADD_SUBKEY,
+ OPT_SET_EXPIRE,
+
+ /* debug */
+ OPT_DEBUG
+} optdefs_t;
+
+bool rnp_cmd(cli_rnp_t *rnp, optdefs_t cmd, const char *f);
+bool setoption(rnp_cfg &cfg, optdefs_t *cmd, int val, const char *arg);
+void print_usage(const char *usagemsg);
+
+/**
+ * @brief Initializes rnpkeys. Function allocates memory dynamically for
+ * rnp argument, which must be freed by the caller.
+ *
+ * @param rnp initialized rnp context
+ * @param cfg configuration with settings from command line
+ * @return true on success, or false otherwise.
+ */
+bool rnpkeys_init(cli_rnp_t &rnp, const rnp_cfg &cfg);
+
+#endif /* _rnpkeys_ */
diff --git a/src/rnpkeys/tui.cpp b/src/rnpkeys/tui.cpp
new file mode 100644
index 0000000..73f26dc
--- /dev/null
+++ b/src/rnpkeys/tui.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <algorithm>
+#ifdef _MSC_VER
+#include "uniwin.h"
+#else
+#include <unistd.h>
+#endif
+#include <errno.h>
+#include <iterator>
+#include "rnp/rnpcfg.h"
+#include "rnpkeys.h"
+#include "defaults.h"
+#include "file-utils.h"
+#include "logging.h"
+
+/* -----------------------------------------------------------------------------
+ * @brief Reads input from file pointer and converts it securelly to ints
+ * Partially based on ERR34-C from SEI CERT C Coding Standard
+ *
+ * @param fp pointer to opened pipe
+ * @param result[out] result read from file pointer and converted to int
+ *
+ * @returns true and value in result if integer was parsed correctly,
+ * otherwise false
+ *
+-------------------------------------------------------------------------------- */
+static bool
+rnp_secure_get_long_from_fd(FILE *fp, long &result, bool allow_empty = true)
+{
+ char buff[BUFSIZ];
+ if (!fgets(buff, sizeof(buff), fp)) {
+ RNP_LOG("EOF or read error");
+ return false;
+ }
+
+ errno = 0;
+ char *end_ptr = NULL;
+ long num_long = strtol(buff, &end_ptr, 10);
+ if (ERANGE == errno) {
+ RNP_LOG("Number out of range");
+ return false;
+ }
+ if (end_ptr == buff) {
+ return allow_empty;
+ }
+ if ('\n' != *end_ptr && '\0' != *end_ptr) {
+ RNP_LOG("Unexpected end of line");
+ return false;
+ }
+
+ result = num_long;
+ return true;
+}
+
+static bool
+is_rsa_keysize_supported(uint32_t keysize)
+{
+ return ((keysize >= 1024) && (keysize <= 4096) && !(keysize % 8));
+}
+
+static const char *
+ask_curve_name(FILE *input_fp)
+{
+ std::vector<const char *> curves;
+ static const char *const known_curves[] = {
+ "NIST P-256",
+ "NIST P-384",
+ "NIST P-521",
+ "brainpoolP256r1",
+ "brainpoolP384r1",
+ "brainpoolP512r1",
+ "secp256k1",
+ };
+ const size_t curvenum = sizeof(known_curves) / sizeof(*known_curves);
+
+ try {
+ std::copy_if(known_curves,
+ known_curves + curvenum,
+ std::back_inserter(curves),
+ [](const char *curve) {
+ bool supported = false;
+ return !rnp_supports_feature(RNP_FEATURE_CURVE, curve, &supported) &&
+ supported;
+ });
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return NULL;
+ }
+ const size_t ccount = curves.size();
+ if (!ccount) {
+ return NULL;
+ }
+ bool ok = false;
+ const char *result = NULL;
+ int attempts = 0;
+ do {
+ if (attempts >= 10) {
+ printf("Too many attempts. Aborting.\n");
+ return NULL;
+ }
+ printf("Please select which elliptic curve you want:\n");
+ for (size_t i = 0; i < ccount; i++) {
+ printf("\t(%zu) %s\n", i + 1, curves[i]);
+ }
+ printf("(default %s)> ", DEFAULT_CURVE);
+ long val = 0;
+ ok = rnp_secure_get_long_from_fd(input_fp, val) && (val > 0) && (val <= (long) ccount);
+ if (ok) {
+ result = curves[val - 1];
+ }
+ attempts++;
+ } while (!ok);
+
+ return result;
+}
+
+static long
+ask_rsa_bitlen(FILE *input_fp)
+{
+ long result = 0;
+ do {
+ result = DEFAULT_RSA_NUMBITS;
+ printf("Please provide bit length of the key (between 1024 and 4096):\n(default %d)> ",
+ DEFAULT_RSA_NUMBITS);
+ } while (!rnp_secure_get_long_from_fd(input_fp, result) ||
+ !is_rsa_keysize_supported(result));
+ return result;
+}
+
+static long
+ask_elgamal_bitlen(FILE *input_fp)
+{
+ do {
+ printf(
+ "Please provide bit length of the ElGamal key (between %d and %d):\n(default %d) > ",
+ ELGAMAL_MIN_P_BITLEN,
+ ELGAMAL_MAX_P_BITLEN,
+ DEFAULT_ELGAMAL_NUMBITS);
+ long result = DEFAULT_ELGAMAL_NUMBITS;
+ if (!rnp_secure_get_long_from_fd(input_fp, result)) {
+ continue;
+ }
+ if ((result >= ELGAMAL_MIN_P_BITLEN) && (result <= ELGAMAL_MAX_P_BITLEN)) {
+ // round up to multiple of 32
+ result = ((result + 31) / 32) * 32;
+ printf("Bitlen of the key will be %lu\n", result);
+ return result;
+ }
+ } while (1);
+}
+
+static long
+ask_dsa_bitlen(FILE *input_fp)
+{
+ do {
+ printf(
+ "Please provide bit length of the DSA key (between %d and %d):\n(default %d) > ",
+ DSA_MIN_P_BITLEN,
+ DSA_MAX_P_BITLEN,
+ DSA_DEFAULT_P_BITLEN);
+ long result = DSA_DEFAULT_P_BITLEN;
+ if (!rnp_secure_get_long_from_fd(input_fp, result)) {
+ continue;
+ }
+ if ((result >= DSA_MIN_P_BITLEN) && (result <= DSA_MAX_P_BITLEN)) {
+ // round up to multiple of 64
+ result = ((result + 63) / 64) * 64;
+ printf("Bitlen of the key will be %lu\n", result);
+ return result;
+ }
+ } while (1);
+}
+
+static bool
+rnpkeys_ask_generate_params(rnp_cfg &cfg, FILE *input_fp)
+{
+ long option = 0;
+ do {
+ printf("Please select what kind of key you want:\n"
+ "\t(1) RSA (Encrypt or Sign)\n"
+ "\t(16) DSA + ElGamal\n"
+ "\t(17) DSA + RSA\n" // TODO: See #584
+ "\t(19) ECDSA + ECDH\n"
+ "\t(22) EDDSA + X25519\n"
+ "\t(99) SM2\n"
+ "> ");
+ if (!rnp_secure_get_long_from_fd(input_fp, option, false)) {
+ option = 0;
+ continue;
+ }
+ switch (option) {
+ case 1: {
+ int bits = ask_rsa_bitlen(input_fp);
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_int(CFG_KG_PRIMARY_BITS, bits);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 16: {
+ int bits = ask_dsa_bitlen(input_fp);
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DSA);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ELGAMAL);
+ cfg.set_int(CFG_KG_PRIMARY_BITS, bits);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 17: {
+ int bits = ask_dsa_bitlen(input_fp);
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DSA);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_int(CFG_KG_PRIMARY_BITS, bits);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 19: {
+ const char *curve = ask_curve_name(input_fp);
+ if (!curve) {
+ return false;
+ }
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_ECDSA);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH);
+ cfg.set_str(CFG_KG_PRIMARY_CURVE, curve);
+ cfg.set_str(CFG_KG_SUBKEY_CURVE, curve);
+ break;
+ }
+ case 22: {
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_EDDSA);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH);
+ cfg.set_str(CFG_KG_SUBKEY_CURVE, "Curve25519");
+ break;
+ }
+ case 99: {
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_SM2);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_SM2);
+ if (!cfg.has(CFG_KG_HASH)) {
+ cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SM3);
+ }
+ break;
+ }
+ default:
+ option = 0;
+ break;
+ }
+ } while (!option);
+
+ return true;
+}
+
+static bool
+rnpkeys_ask_generate_params_subkey(rnp_cfg &cfg, FILE *input_fp)
+{
+ long option = 0;
+ do {
+ printf("Please select subkey algorithm you want:\n"
+ "\t(1) RSA\n"
+ "\t(16) ElGamal\n"
+ "\t(17) DSA\n"
+ "\t(18) ECDH\n"
+ "\t(19) ECDSA\n"
+ "\t(22) EDDSA\n"
+ "\t(99) SM2"
+ "> ");
+ if (!rnp_secure_get_long_from_fd(input_fp, option, false)) {
+ option = 0;
+ continue;
+ }
+ switch (option) {
+ case 1: {
+ int bits = ask_rsa_bitlen(input_fp);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 16: {
+ int bits = ask_elgamal_bitlen(input_fp);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ELGAMAL);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 17: {
+ int bits = ask_dsa_bitlen(input_fp);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_DSA);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 18: {
+ const char *curve = ask_curve_name(input_fp);
+ if (!curve) {
+ return false;
+ }
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH);
+ cfg.set_str(CFG_KG_SUBKEY_CURVE, curve);
+ break;
+ }
+ case 19: {
+ const char *curve = ask_curve_name(input_fp);
+ if (!curve) {
+ return false;
+ }
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDSA);
+ cfg.set_str(CFG_KG_SUBKEY_CURVE, curve);
+ break;
+ }
+ case 22: {
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_EDDSA);
+ break;
+ }
+ case 99: {
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_SM2);
+ if (!cfg.has(CFG_KG_HASH)) {
+ cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SM3);
+ }
+ break;
+ }
+ default:
+ option = 0;
+ break;
+ }
+ } while (!option);
+
+ return true;
+}
+
+bool
+cli_rnp_set_generate_params(rnp_cfg &cfg, bool subkey)
+{
+ bool res = true;
+ // hash algorithms for signing and protection
+ if (cfg.has(CFG_HASH)) {
+ cfg.set_str(CFG_KG_HASH, cfg.get_str(CFG_HASH));
+ cfg.set_str(CFG_KG_PROT_HASH, cfg.get_str(CFG_HASH));
+ }
+
+ // key and subkey algorithms, bit length/curve
+ if (!cfg.get_bool(CFG_EXPERT)) {
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_int(CFG_KG_PRIMARY_BITS, cfg.get_int(CFG_NUMBITS));
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, cfg.get_int(CFG_NUMBITS));
+ } else {
+ FILE *input = stdin;
+ if (cfg.has(CFG_USERINPUTFD)) {
+ int inputfd = dup(cfg.get_int(CFG_USERINPUTFD));
+ if (inputfd != -1) {
+ input = rnp_fdopen(inputfd, "r");
+ if (!input) {
+ close(inputfd);
+ }
+ }
+ }
+ if (!input) {
+ return false;
+ }
+ if (subkey) {
+ res = rnpkeys_ask_generate_params_subkey(cfg, input);
+ } else {
+ res = rnpkeys_ask_generate_params(cfg, input);
+ }
+ if (input != stdin) {
+ fclose(input);
+ }
+ if (!res) {
+ return false;
+ }
+ }
+
+ // make sure hash algorithms are set
+ if (!cfg.has(CFG_KG_HASH)) {
+ cfg.set_str(CFG_KG_HASH, DEFAULT_HASH_ALG);
+ }
+ if (!cfg.has(CFG_KG_PROT_HASH)) {
+ cfg.set_str(CFG_KG_PROT_HASH, DEFAULT_HASH_ALG);
+ }
+
+ // protection symmetric algorithm
+ cfg.set_str(CFG_KG_PROT_ALG,
+ cfg.has(CFG_CIPHER) ? cfg.get_str(CFG_CIPHER) : DEFAULT_SYMM_ALG);
+ // protection iterations count
+ size_t iterations = cfg.get_int(CFG_S2K_ITER);
+ if (!iterations) {
+ res = res && !rnp_calculate_iterations(cfg.get_str(CFG_KG_PROT_HASH).c_str(),
+ cfg.get_int(CFG_S2K_MSEC),
+ &iterations);
+ }
+ cfg.set_int(CFG_KG_PROT_ITERATIONS, iterations);
+ return res;
+}