diff options
Diffstat (limited to 'src/lib/rnp.cpp')
-rw-r--r-- | src/lib/rnp.cpp | 8403 |
1 files changed, 8403 insertions, 0 deletions
diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp new file mode 100644 index 0000000..24c46f9 --- /dev/null +++ b/src/lib/rnp.cpp @@ -0,0 +1,8403 @@ +/*- + * Copyright (c) 2017-2021, 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. + */ + +#include "crypto.h" +#include "crypto/common.h" +#include "pgp-key.h" +#include "defaults.h" +#include <assert.h> +#include <json_object.h> +#include <json.h> +#include <librekey/key_store_pgp.h> +#include <librepgp/stream-ctx.h> +#include <librepgp/stream-common.h> +#include <librepgp/stream-armor.h> +#include <librepgp/stream-parse.h> +#include <librepgp/stream-write.h> +#include <librepgp/stream-sig.h> +#include <librepgp/stream-packet.h> +#include <librepgp/stream-key.h> +#include <librepgp/stream-dump.h> +#include <rnp/rnp.h> +#include <stdarg.h> +#include <stdlib.h> +#ifdef _MSC_VER +#include "uniwin.h" +#include <inttypes.h> +#else +#include <unistd.h> +#endif +#include <string.h> +#include <sys/stat.h> +#include <stdexcept> +#include "utils.h" +#include "str-utils.h" +#include "json-utils.h" +#include "version.h" +#include "ffi-priv-types.h" +#include "file-utils.h" + +#define FFI_LOG(ffi, ...) \ + do { \ + FILE *fp = stderr; \ + if (ffi && ffi->errs) { \ + fp = ffi->errs; \ + } \ + RNP_LOG_FD(fp, __VA_ARGS__); \ + } while (0) + +static pgp_key_t *get_key_require_public(rnp_key_handle_t handle); +static pgp_key_t *get_key_prefer_public(rnp_key_handle_t handle); +static pgp_key_t *get_key_require_secret(rnp_key_handle_t handle); + +static bool locator_to_str(const pgp_key_search_t &locator, + const char ** identifier_type, + char * identifier, + size_t identifier_size); + +static bool rnp_password_cb_bounce(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata_void); + +static rnp_result_t rnp_dump_src_to_json(pgp_source_t *src, uint32_t flags, char **result); + +static bool +call_key_callback(rnp_ffi_t ffi, const pgp_key_search_t &search, bool secret) +{ + if (!ffi->getkeycb) { + return false; + } + char identifier[RNP_LOCATOR_MAX_SIZE]; + const char *identifier_type = NULL; + if (!locator_to_str(search, &identifier_type, identifier, sizeof(identifier))) { + return false; + } + + ffi->getkeycb(ffi, ffi->getkeycb_ctx, identifier_type, identifier, secret); + return true; +} + +static pgp_key_t * +find_key(rnp_ffi_t ffi, + const pgp_key_search_t &search, + bool secret, + bool try_key_provider, + pgp_key_t * after = NULL) +{ + pgp_key_t *key = + rnp_key_store_search(secret ? ffi->secring : ffi->pubring, &search, after); + if (!key && try_key_provider && call_key_callback(ffi, search, secret)) { + // recurse and try the store search above once more + return find_key(ffi, search, secret, false, after); + } + return key; +} + +static pgp_key_t * +ffi_key_provider(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + rnp_ffi_t ffi = (rnp_ffi_t) userdata; + return find_key(ffi, ctx->search, ctx->secret, true); +} + +static void +rnp_ctx_init_ffi(rnp_ctx_t &ctx, rnp_ffi_t ffi) +{ + ctx.ctx = &ffi->context; + ctx.ealg = DEFAULT_PGP_SYMM_ALG; + ctx.aalg = PGP_AEAD_NONE; + ctx.abits = DEFAULT_AEAD_CHUNK_BITS; +} + +static const id_str_pair sig_type_map[] = {{PGP_SIG_BINARY, "binary"}, + {PGP_SIG_TEXT, "text"}, + {PGP_SIG_STANDALONE, "standalone"}, + {PGP_CERT_GENERIC, "certification (generic)"}, + {PGP_CERT_PERSONA, "certification (persona)"}, + {PGP_CERT_CASUAL, "certification (casual)"}, + {PGP_CERT_POSITIVE, "certification (positive)"}, + {PGP_SIG_SUBKEY, "subkey binding"}, + {PGP_SIG_PRIMARY, "primary key binding"}, + {PGP_SIG_DIRECT, "direct"}, + {PGP_SIG_REV_KEY, "key revocation"}, + {PGP_SIG_REV_SUBKEY, "subkey revocation"}, + {PGP_SIG_REV_CERT, "certification revocation"}, + {PGP_SIG_TIMESTAMP, "timestamp"}, + {PGP_SIG_3RD_PARTY, "third-party"}, + {0, NULL}}; + +static const id_str_pair pubkey_alg_map[] = { + {PGP_PKA_RSA, RNP_ALGNAME_RSA}, + {PGP_PKA_RSA_ENCRYPT_ONLY, RNP_ALGNAME_RSA}, + {PGP_PKA_RSA_SIGN_ONLY, RNP_ALGNAME_RSA}, + {PGP_PKA_ELGAMAL, RNP_ALGNAME_ELGAMAL}, + {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, RNP_ALGNAME_ELGAMAL}, + {PGP_PKA_DSA, RNP_ALGNAME_DSA}, + {PGP_PKA_ECDH, RNP_ALGNAME_ECDH}, + {PGP_PKA_ECDSA, RNP_ALGNAME_ECDSA}, + {PGP_PKA_EDDSA, RNP_ALGNAME_EDDSA}, + {PGP_PKA_SM2, RNP_ALGNAME_SM2}, + {0, NULL}}; + +static const id_str_pair symm_alg_map[] = {{PGP_SA_IDEA, RNP_ALGNAME_IDEA}, + {PGP_SA_TRIPLEDES, RNP_ALGNAME_TRIPLEDES}, + {PGP_SA_CAST5, RNP_ALGNAME_CAST5}, + {PGP_SA_BLOWFISH, RNP_ALGNAME_BLOWFISH}, + {PGP_SA_AES_128, RNP_ALGNAME_AES_128}, + {PGP_SA_AES_192, RNP_ALGNAME_AES_192}, + {PGP_SA_AES_256, RNP_ALGNAME_AES_256}, + {PGP_SA_TWOFISH, RNP_ALGNAME_TWOFISH}, + {PGP_SA_CAMELLIA_128, RNP_ALGNAME_CAMELLIA_128}, + {PGP_SA_CAMELLIA_192, RNP_ALGNAME_CAMELLIA_192}, + {PGP_SA_CAMELLIA_256, RNP_ALGNAME_CAMELLIA_256}, + {PGP_SA_SM4, RNP_ALGNAME_SM4}, + {0, NULL}}; + +static const id_str_pair aead_alg_map[] = { + {PGP_AEAD_NONE, "None"}, {PGP_AEAD_EAX, "EAX"}, {PGP_AEAD_OCB, "OCB"}, {0, NULL}}; + +static const id_str_pair cipher_mode_map[] = {{PGP_CIPHER_MODE_CFB, "CFB"}, + {PGP_CIPHER_MODE_CBC, "CBC"}, + {PGP_CIPHER_MODE_OCB, "OCB"}, + {0, NULL}}; + +static const id_str_pair compress_alg_map[] = {{PGP_C_NONE, "Uncompressed"}, + {PGP_C_ZIP, "ZIP"}, + {PGP_C_ZLIB, "ZLIB"}, + {PGP_C_BZIP2, "BZip2"}, + {0, NULL}}; + +static const id_str_pair hash_alg_map[] = {{PGP_HASH_MD5, RNP_ALGNAME_MD5}, + {PGP_HASH_SHA1, RNP_ALGNAME_SHA1}, + {PGP_HASH_RIPEMD, RNP_ALGNAME_RIPEMD160}, + {PGP_HASH_SHA256, RNP_ALGNAME_SHA256}, + {PGP_HASH_SHA384, RNP_ALGNAME_SHA384}, + {PGP_HASH_SHA512, RNP_ALGNAME_SHA512}, + {PGP_HASH_SHA224, RNP_ALGNAME_SHA224}, + {PGP_HASH_SHA3_256, RNP_ALGNAME_SHA3_256}, + {PGP_HASH_SHA3_512, RNP_ALGNAME_SHA3_512}, + {PGP_HASH_SM3, RNP_ALGNAME_SM3}, + {0, NULL}}; + +static const id_str_pair s2k_type_map[] = { + {PGP_S2KS_SIMPLE, "Simple"}, + {PGP_S2KS_SALTED, "Salted"}, + {PGP_S2KS_ITERATED_AND_SALTED, "Iterated and salted"}, + {0, NULL}}; + +static const id_str_pair key_usage_map[] = { + {PGP_KF_SIGN, "sign"}, + {PGP_KF_CERTIFY, "certify"}, + {PGP_KF_ENCRYPT, "encrypt"}, + {PGP_KF_AUTH, "authenticate"}, + {0, NULL}, +}; + +static const id_str_pair key_flags_map[] = { + {PGP_KF_SPLIT, "split"}, + {PGP_KF_SHARED, "shared"}, + {0, NULL}, +}; + +static const id_str_pair identifier_type_map[] = {{PGP_KEY_SEARCH_USERID, "userid"}, + {PGP_KEY_SEARCH_KEYID, "keyid"}, + {PGP_KEY_SEARCH_FINGERPRINT, "fingerprint"}, + {PGP_KEY_SEARCH_GRIP, "grip"}, + {0, NULL}}; + +static const id_str_pair key_server_prefs_map[] = {{PGP_KEY_SERVER_NO_MODIFY, "no-modify"}, + {0, NULL}}; + +static const id_str_pair armor_type_map[] = {{PGP_ARMORED_MESSAGE, "message"}, + {PGP_ARMORED_PUBLIC_KEY, "public key"}, + {PGP_ARMORED_SECRET_KEY, "secret key"}, + {PGP_ARMORED_SIGNATURE, "signature"}, + {PGP_ARMORED_CLEARTEXT, "cleartext"}, + {0, NULL}}; + +static const id_str_pair key_import_status_map[] = { + {PGP_KEY_IMPORT_STATUS_UNKNOWN, "unknown"}, + {PGP_KEY_IMPORT_STATUS_UNCHANGED, "unchanged"}, + {PGP_KEY_IMPORT_STATUS_UPDATED, "updated"}, + {PGP_KEY_IMPORT_STATUS_NEW, "new"}, + {0, NULL}}; + +static const id_str_pair sig_import_status_map[] = { + {PGP_SIG_IMPORT_STATUS_UNKNOWN, "unknown"}, + {PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY, "unknown key"}, + {PGP_SIG_IMPORT_STATUS_UNCHANGED, "unchanged"}, + {PGP_SIG_IMPORT_STATUS_NEW, "new"}, + {0, NULL}}; + +static const id_str_pair revocation_code_map[] = { + {PGP_REVOCATION_NO_REASON, "no"}, + {PGP_REVOCATION_SUPERSEDED, "superseded"}, + {PGP_REVOCATION_COMPROMISED, "compromised"}, + {PGP_REVOCATION_RETIRED, "retired"}, + {PGP_REVOCATION_NO_LONGER_VALID, "no longer valid"}, + {0, NULL}}; + +static bool +symm_alg_supported(int alg) +{ + return pgp_is_sa_supported(alg, true); +} + +static bool +hash_alg_supported(int alg) +{ + switch (alg) { + case PGP_HASH_MD5: + case PGP_HASH_SHA1: +#if defined(ENABLE_RIPEMD160) + case PGP_HASH_RIPEMD: +#endif + case PGP_HASH_SHA256: + case PGP_HASH_SHA384: + case PGP_HASH_SHA512: + case PGP_HASH_SHA224: + case PGP_HASH_SHA3_256: + case PGP_HASH_SHA3_512: +#if defined(ENABLE_SM2) + case PGP_HASH_SM3: +#endif + return true; + default: + return false; + } +} + +static bool +aead_alg_supported(int alg) +{ + switch (alg) { + case PGP_AEAD_NONE: +#if defined(ENABLE_AEAD) +#if !defined(CRYPTO_BACKEND_OPENSSL) + case PGP_AEAD_EAX: +#endif + case PGP_AEAD_OCB: +#endif + return true; + default: + return false; + } +} + +static bool +pub_alg_supported(int alg) +{ + switch (alg) { + case PGP_PKA_RSA: + case PGP_PKA_ELGAMAL: + case PGP_PKA_DSA: + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: +#if defined(ENABLE_SM2) + case PGP_PKA_SM2: +#endif + return true; + default: + return false; + } +} + +static bool +z_alg_supported(int alg) +{ + switch (alg) { + case PGP_C_NONE: + case PGP_C_ZIP: + case PGP_C_ZLIB: + case PGP_C_BZIP2: + return true; + default: + return false; + } +} + +static bool +curve_str_to_type(const char *str, pgp_curve_t *value) +{ + *value = find_curve_by_name(str); + return curve_supported(*value); +} + +static bool +curve_type_to_str(pgp_curve_t type, const char **str) +{ + const ec_curve_desc_t *desc = get_curve_desc(type); + if (!desc) { + return false; + } + *str = desc->pgp_name; + return true; +} + +static bool +str_to_cipher(const char *str, pgp_symm_alg_t *cipher) +{ + auto alg = id_str_pair::lookup(symm_alg_map, str, PGP_SA_UNKNOWN); + if (!symm_alg_supported(alg)) { + return false; + } + *cipher = static_cast<pgp_symm_alg_t>(alg); + return true; +} + +static bool +str_to_hash_alg(const char *str, pgp_hash_alg_t *hash_alg) +{ + auto alg = id_str_pair::lookup(hash_alg_map, str, PGP_HASH_UNKNOWN); + if (!hash_alg_supported(alg)) { + return false; + } + *hash_alg = static_cast<pgp_hash_alg_t>(alg); + return true; +} + +static bool +str_to_aead_alg(const char *str, pgp_aead_alg_t *aead_alg) +{ + auto alg = id_str_pair::lookup(aead_alg_map, str, PGP_AEAD_UNKNOWN); + if (!aead_alg_supported(alg)) { + return false; + } + *aead_alg = static_cast<pgp_aead_alg_t>(alg); + return true; +} + +static bool +str_to_compression_alg(const char *str, pgp_compression_type_t *zalg) +{ + auto alg = id_str_pair::lookup(compress_alg_map, str, PGP_C_UNKNOWN); + if (!z_alg_supported(alg)) { + return false; + } + *zalg = static_cast<pgp_compression_type_t>(alg); + return true; +} + +static bool +str_to_revocation_type(const char *str, pgp_revocation_type_t *code) +{ + pgp_revocation_type_t rev = static_cast<pgp_revocation_type_t>( + id_str_pair::lookup(revocation_code_map, str, PGP_REVOCATION_NO_REASON)); + if ((rev == PGP_REVOCATION_NO_REASON) && !rnp::str_case_eq(str, "no")) { + return false; + } + *code = rev; + return true; +} + +static bool +str_to_cipher_mode(const char *str, pgp_cipher_mode_t *mode) +{ + pgp_cipher_mode_t c_mode = static_cast<pgp_cipher_mode_t>( + id_str_pair::lookup(cipher_mode_map, str, PGP_CIPHER_MODE_NONE)); + if (c_mode == PGP_CIPHER_MODE_NONE) { + return false; + } + + *mode = c_mode; + return true; +} + +static bool +str_to_pubkey_alg(const char *str, pgp_pubkey_alg_t *pub_alg) +{ + auto alg = id_str_pair::lookup(pubkey_alg_map, str, PGP_PKA_NOTHING); + if (!pub_alg_supported(alg)) { + return false; + } + *pub_alg = static_cast<pgp_pubkey_alg_t>(alg); + return true; +} + +static bool +str_to_key_flag(const char *str, uint8_t *flag) +{ + uint8_t _flag = id_str_pair::lookup(key_usage_map, str); + if (!_flag) { + return false; + } + *flag = _flag; + return true; +} + +static bool +parse_ks_format(pgp_key_store_format_t *key_store_format, const char *format) +{ + if (!strcmp(format, RNP_KEYSTORE_GPG)) { + *key_store_format = PGP_KEY_STORE_GPG; + } else if (!strcmp(format, RNP_KEYSTORE_KBX)) { + *key_store_format = PGP_KEY_STORE_KBX; + } else if (!strcmp(format, RNP_KEYSTORE_G10)) { + *key_store_format = PGP_KEY_STORE_G10; + } else { + return false; + } + return true; +} + +static rnp_result_t +hex_encode_value(const uint8_t * value, + size_t len, + char ** res, + rnp::hex_format_t format = rnp::HEX_UPPERCASE) +{ + size_t hex_len = len * 2 + 1; + *res = (char *) malloc(hex_len); + if (!*res) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (!rnp::hex_encode(value, len, *res, hex_len, format)) { + free(*res); + *res = NULL; + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} + +static rnp_result_t +get_map_value(const id_str_pair *map, int val, char **res) +{ + const char *str = id_str_pair::lookup(map, val, NULL); + if (!str) { + return RNP_ERROR_BAD_PARAMETERS; + } + char *strcp = strdup(str); + if (!strcp) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *res = strcp; + return RNP_SUCCESS; +} + +static rnp_result_t +ret_str_value(const char *str, char **res) +{ + if (!str) { + return RNP_ERROR_BAD_PARAMETERS; + } + char *strcp = strdup(str); + if (!strcp) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *res = strcp; + return RNP_SUCCESS; +} + +static uint32_t +ffi_exception(FILE *fp, const char *func, const char *msg, uint32_t ret = RNP_ERROR_GENERIC) +{ + if (rnp_log_switch()) { + fprintf( + fp, "[%s()] Error 0x%08X (%s): %s\n", func, ret, rnp_result_to_string(ret), msg); + } + return ret; +} + +#define FFI_GUARD_FP(fp) \ + catch (rnp::rnp_exception & e) \ + { \ + return ffi_exception((fp), __func__, e.what(), e.code()); \ + } \ + catch (std::bad_alloc &) \ + { \ + return ffi_exception((fp), __func__, "bad_alloc", RNP_ERROR_OUT_OF_MEMORY); \ + } \ + catch (std::exception & e) \ + { \ + return ffi_exception((fp), __func__, e.what()); \ + } \ + catch (...) \ + { \ + return ffi_exception((fp), __func__, "unknown exception"); \ + } + +#define FFI_GUARD FFI_GUARD_FP((stderr)) + +rnp_ffi_st::rnp_ffi_st(pgp_key_store_format_t pub_fmt, pgp_key_store_format_t sec_fmt) +{ + errs = stderr; + pubring = new rnp_key_store_t(pub_fmt, "", context); + secring = new rnp_key_store_t(sec_fmt, "", context); + getkeycb = NULL; + getkeycb_ctx = NULL; + getpasscb = NULL; + getpasscb_ctx = NULL; + key_provider.callback = ffi_key_provider; + key_provider.userdata = this; + pass_provider.callback = rnp_password_cb_bounce; + pass_provider.userdata = this; +} + +rnp::RNG & +rnp_ffi_st::rng() noexcept +{ + return context.rng; +} + +rnp::SecurityProfile & +rnp_ffi_st::profile() noexcept +{ + return context.profile; +} + +rnp_result_t +rnp_ffi_create(rnp_ffi_t *ffi, const char *pub_format, const char *sec_format) +try { + // checks + if (!ffi || !pub_format || !sec_format) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_store_format_t pub_ks_format = PGP_KEY_STORE_UNKNOWN; + pgp_key_store_format_t sec_ks_format = PGP_KEY_STORE_UNKNOWN; + if (!parse_ks_format(&pub_ks_format, pub_format) || + !parse_ks_format(&sec_ks_format, sec_format)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + struct rnp_ffi_st *ob = new rnp_ffi_st(pub_ks_format, sec_ks_format); + *ffi = ob; + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +is_std_file(FILE *fp) +{ + return fp == stdout || fp == stderr; +} + +static void +close_io_file(FILE **fp) +{ + if (*fp && !is_std_file(*fp)) { + fclose(*fp); + } + *fp = NULL; +} + +rnp_ffi_st::~rnp_ffi_st() +{ + close_io_file(&errs); + delete pubring; + delete secring; +} + +rnp_result_t +rnp_ffi_destroy(rnp_ffi_t ffi) +try { + if (ffi) { + delete ffi; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_ffi_set_log_fd(rnp_ffi_t ffi, int fd) +try { + // checks + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + + // open + FILE *errs = rnp_fdopen(fd, "a"); + if (!errs) { + return RNP_ERROR_ACCESS; + } + // close previous streams and replace them + close_io_file(&ffi->errs); + ffi->errs = errs; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_ffi_set_key_provider(rnp_ffi_t ffi, rnp_get_key_cb getkeycb, void *getkeycb_ctx) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + ffi->getkeycb = getkeycb; + ffi->getkeycb_ctx = getkeycb_ctx; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_ffi_set_pass_provider(rnp_ffi_t ffi, rnp_password_cb getpasscb, void *getpasscb_ctx) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + ffi->getpasscb = getpasscb; + ffi->getpasscb_ctx = getpasscb_ctx; + return RNP_SUCCESS; +} +FFI_GUARD + +static const char * +operation_description(uint8_t op) +{ + switch (op) { + case PGP_OP_ADD_SUBKEY: + return "add subkey"; + case PGP_OP_ADD_USERID: + return "add userid"; + case PGP_OP_SIGN: + return "sign"; + case PGP_OP_DECRYPT: + return "decrypt"; + case PGP_OP_UNLOCK: + return "unlock"; + case PGP_OP_PROTECT: + return "protect"; + case PGP_OP_UNPROTECT: + return "unprotect"; + case PGP_OP_DECRYPT_SYM: + return "decrypt (symmetric)"; + case PGP_OP_ENCRYPT_SYM: + return "encrypt (symmetric)"; + default: + return "unknown"; + } +} + +static bool +rnp_password_cb_bounce(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata_void) +{ + rnp_ffi_t ffi = (rnp_ffi_t) userdata_void; + + if (!ffi || !ffi->getpasscb) { + return false; + } + + struct rnp_key_handle_st key = {}; + key.ffi = ffi; + key.sec = (pgp_key_t *) ctx->key; + return ffi->getpasscb(ffi, + ffi->getpasscb_ctx, + ctx->key ? &key : NULL, + operation_description(ctx->op), + password, + password_size); +} + +const char * +rnp_result_to_string(rnp_result_t result) +{ + switch (result) { + case RNP_SUCCESS: + return "Success"; + + case RNP_ERROR_GENERIC: + return "Unknown error"; + case RNP_ERROR_BAD_FORMAT: + return "Bad format"; + case RNP_ERROR_BAD_PARAMETERS: + return "Bad parameters"; + case RNP_ERROR_NOT_IMPLEMENTED: + return "Not implemented"; + case RNP_ERROR_NOT_SUPPORTED: + return "Not supported"; + case RNP_ERROR_OUT_OF_MEMORY: + return "Out of memory"; + case RNP_ERROR_SHORT_BUFFER: + return "Buffer too short"; + case RNP_ERROR_NULL_POINTER: + return "Null pointer"; + + case RNP_ERROR_ACCESS: + return "Error accessing file"; + case RNP_ERROR_READ: + return "Error reading file"; + case RNP_ERROR_WRITE: + return "Error writing file"; + + case RNP_ERROR_BAD_STATE: + return "Bad state"; + case RNP_ERROR_MAC_INVALID: + return "Invalid MAC"; + case RNP_ERROR_SIGNATURE_INVALID: + return "Invalid signature"; + case RNP_ERROR_KEY_GENERATION: + return "Error during key generation"; + case RNP_ERROR_BAD_PASSWORD: + return "Bad password"; + case RNP_ERROR_KEY_NOT_FOUND: + return "Key not found"; + case RNP_ERROR_NO_SUITABLE_KEY: + return "No suitable key"; + case RNP_ERROR_DECRYPT_FAILED: + return "Decryption failed"; + case RNP_ERROR_RNG: + return "Failure of random number generator"; + case RNP_ERROR_SIGNING_FAILED: + return "Signing failed"; + case RNP_ERROR_NO_SIGNATURES_FOUND: + return "No signatures found cannot verify"; + + case RNP_ERROR_SIGNATURE_EXPIRED: + return "Expired signature"; + case RNP_ERROR_VERIFICATION_FAILED: + return "Signature verification failed cannot verify"; + case RNP_ERROR_SIGNATURE_UNKNOWN: + return "Unknown signature"; + + case RNP_ERROR_NOT_ENOUGH_DATA: + return "Not enough data"; + case RNP_ERROR_UNKNOWN_TAG: + return "Unknown tag"; + case RNP_ERROR_PACKET_NOT_CONSUMED: + return "Packet not consumed"; + case RNP_ERROR_NO_USERID: + return "No userid"; + case RNP_ERROR_EOF: + return "EOF detected"; + } + + return "Unsupported error code"; +} + +const char * +rnp_version_string() +{ + return RNP_VERSION_STRING; +} + +const char * +rnp_version_string_full() +{ + return RNP_VERSION_STRING_FULL; +} + +uint32_t +rnp_version() +{ + return RNP_VERSION_CODE; +} + +uint32_t +rnp_version_for(uint32_t major, uint32_t minor, uint32_t patch) +{ + if (major > RNP_VERSION_COMPONENT_MASK || minor > RNP_VERSION_COMPONENT_MASK || + patch > RNP_VERSION_COMPONENT_MASK) { + RNP_LOG("invalid version, out of range: %d.%d.%d", major, minor, patch); + return 0; + } + return RNP_VERSION_CODE_FOR(major, minor, patch); +} + +uint32_t +rnp_version_major(uint32_t version) +{ + return (version >> RNP_VERSION_MAJOR_SHIFT) & RNP_VERSION_COMPONENT_MASK; +} + +uint32_t +rnp_version_minor(uint32_t version) +{ + return (version >> RNP_VERSION_MINOR_SHIFT) & RNP_VERSION_COMPONENT_MASK; +} + +uint32_t +rnp_version_patch(uint32_t version) +{ + return (version >> RNP_VERSION_PATCH_SHIFT) & RNP_VERSION_COMPONENT_MASK; +} + +uint64_t +rnp_version_commit_timestamp() +{ + return RNP_VERSION_COMMIT_TIMESTAMP; +} + +#ifndef RNP_NO_DEPRECATED +rnp_result_t +rnp_enable_debug(const char *file) +try { + return RNP_SUCCESS; +} +FFI_GUARD +#endif + +#ifndef RNP_NO_DEPRECATED +rnp_result_t +rnp_disable_debug() +try { + return RNP_SUCCESS; +} +FFI_GUARD +#endif + +rnp_result_t +rnp_get_default_homedir(char **homedir) +try { + // checks + if (!homedir) { + return RNP_ERROR_NULL_POINTER; + } + + // get the users home dir + auto home = rnp::path::HOME(".rnp"); + if (home.empty()) { + return RNP_ERROR_NOT_SUPPORTED; + } + *homedir = strdup(home.c_str()); + if (!*homedir) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_detect_homedir_info( + const char *homedir, char **pub_format, char **pub_path, char **sec_format, char **sec_path) +try { + // checks + if (!homedir || !pub_format || !pub_path || !sec_format || !sec_path) { + return RNP_ERROR_NULL_POINTER; + } + + // we only support the common cases of GPG+GPG or GPG+G10, we don't + // support unused combinations like KBX+KBX + + *pub_format = NULL; + *pub_path = NULL; + *sec_format = NULL; + *sec_path = NULL; + + // check for pubring.kbx file and for private-keys-v1.d dir + std::string pub = rnp::path::append(homedir, "pubring.kbx"); + std::string sec = rnp::path::append(homedir, "private-keys-v1.d"); + if (rnp::path::exists(pub) && rnp::path::exists(sec, true)) { + *pub_format = strdup("KBX"); + *sec_format = strdup("G10"); + } else { + // check for pubring.gpg and secring.gpg + pub = rnp::path::append(homedir, "pubring.gpg"); + sec = rnp::path::append(homedir, "secring.gpg"); + if (rnp::path::exists(pub) && rnp::path::exists(sec)) { + *pub_format = strdup("GPG"); + *sec_format = strdup("GPG"); + } else { + // we leave the *formats as NULL if we were not able to determine the format + // (but no error occurred) + return RNP_SUCCESS; + } + } + + // set pathes + *pub_path = strdup(pub.c_str()); + *sec_path = strdup(sec.c_str()); + + // check for allocation failures + if (*pub_format && *pub_path && *sec_format && *sec_path) { + return RNP_SUCCESS; + } + + free(*pub_format); + *pub_format = NULL; + free(*pub_path); + *pub_path = NULL; + free(*sec_format); + *sec_format = NULL; + free(*sec_path); + *sec_path = NULL; + return RNP_ERROR_OUT_OF_MEMORY; +} +FFI_GUARD + +rnp_result_t +rnp_detect_key_format(const uint8_t buf[], size_t buf_len, char **format) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + + // checks + if (!buf || !format) { + return RNP_ERROR_NULL_POINTER; + } + if (!buf_len) { + return RNP_ERROR_SHORT_BUFFER; + } + + *format = NULL; + // ordered from most reliable detection to least + const char *guess = NULL; + if (buf_len >= 12 && memcmp(buf + 8, "KBXf", 4) == 0) { + // KBX has a magic KBXf marker + guess = "KBX"; + } else if (buf_len >= 5 && memcmp(buf, "-----", 5) == 0) { + // likely armored GPG + guess = "GPG"; + } else if (buf[0] == '(') { + // G10 is s-exprs and should start end end with parentheses + guess = "G10"; + } else if (buf[0] & PGP_PTAG_ALWAYS_SET) { + // this is harder to reliably determine, but could likely be improved + guess = "GPG"; + } + if (guess) { + *format = strdup(guess); + if (!*format) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + + // success + ret = RNP_SUCCESS; +done: + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_calculate_iterations(const char *hash, size_t msec, size_t *iterations) +try { + if (!hash || !iterations) { + return RNP_ERROR_NULL_POINTER; + } + pgp_hash_alg_t halg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &halg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *iterations = pgp_s2k_compute_iters(halg, msec, 0); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_supports_feature(const char *type, const char *name, bool *supported) +try { + if (!type || !name || !supported) { + return RNP_ERROR_NULL_POINTER; + } + if (rnp::str_case_eq(type, RNP_FEATURE_SYMM_ALG)) { + pgp_symm_alg_t alg = PGP_SA_UNKNOWN; + *supported = str_to_cipher(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_AEAD_ALG)) { + pgp_aead_alg_t alg = PGP_AEAD_UNKNOWN; + *supported = str_to_aead_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PROT_MODE)) { + // for now we support only CFB for key encryption + *supported = rnp::str_case_eq(name, "CFB"); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PK_ALG)) { + pgp_pubkey_alg_t alg = PGP_PKA_NOTHING; + *supported = str_to_pubkey_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_HASH_ALG)) { + pgp_hash_alg_t alg = PGP_HASH_UNKNOWN; + *supported = str_to_hash_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_COMP_ALG)) { + pgp_compression_type_t alg = PGP_C_UNKNOWN; + *supported = str_to_compression_alg(name, &alg); + } else if (rnp::str_case_eq(type, RNP_FEATURE_CURVE)) { + pgp_curve_t curve = PGP_CURVE_UNKNOWN; + *supported = curve_str_to_type(name, &curve); + } else { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +json_array_add_id_str(json_object *arr, const id_str_pair *map, bool (*check)(int)) +{ + while (map->str) { + if (check(map->id) && !array_add_element_json(arr, json_object_new_string(map->str))) { + return RNP_ERROR_OUT_OF_MEMORY; + } + map++; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_supported_features(const char *type, char **result) +try { + if (!type || !result) { + return RNP_ERROR_NULL_POINTER; + } + + json_object *features = json_object_new_array(); + if (!features) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + if (rnp::str_case_eq(type, RNP_FEATURE_SYMM_ALG)) { + ret = json_array_add_id_str(features, symm_alg_map, symm_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_AEAD_ALG)) { + ret = json_array_add_id_str(features, aead_alg_map, aead_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PROT_MODE)) { + ret = json_array_add_id_str( + features, cipher_mode_map, [](int alg) { return alg == PGP_CIPHER_MODE_CFB; }); + } else if (rnp::str_case_eq(type, RNP_FEATURE_PK_ALG)) { + ret = json_array_add_id_str(features, pubkey_alg_map, pub_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_HASH_ALG)) { + ret = json_array_add_id_str(features, hash_alg_map, hash_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_COMP_ALG)) { + ret = json_array_add_id_str(features, compress_alg_map, z_alg_supported); + } else if (rnp::str_case_eq(type, RNP_FEATURE_CURVE)) { + for (pgp_curve_t curve = PGP_CURVE_NIST_P_256; curve < PGP_CURVE_MAX; + curve = (pgp_curve_t)(curve + 1)) { + const ec_curve_desc_t *desc = get_curve_desc(curve); + if (!desc) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + if (!desc->supported) { + continue; + } + if (!array_add_element_json(features, json_object_new_string(desc->pgp_name))) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + ret = RNP_SUCCESS; + } + + if (ret) { + goto done; + } + + *result = (char *) json_object_to_json_string_ext(features, JSON_C_TO_STRING_PRETTY); + if (!*result) { + ret = RNP_ERROR_BAD_STATE; + goto done; + } + *result = strdup(*result); + if (!*result) { + ret = RNP_ERROR_OUT_OF_MEMORY; + } +done: + json_object_put(features); + return ret; +} +FFI_GUARD + +static bool +get_feature_sec_value( + rnp_ffi_t ffi, const char *stype, const char *sname, rnp::FeatureType &type, int &value) +{ + /* check type */ + if (!rnp::str_case_eq(stype, RNP_FEATURE_HASH_ALG)) { + FFI_LOG(ffi, "Unsupported feature type: %s", stype); + return false; + } + type = rnp::FeatureType::Hash; + /* check feature name */ + pgp_hash_alg_t alg = PGP_HASH_UNKNOWN; + if (sname && !str_to_hash_alg(sname, &alg)) { + FFI_LOG(ffi, "Unknown hash algorithm: %s", sname); + return false; + } + value = alg; + return true; +} + +static bool +get_feature_sec_level(rnp_ffi_t ffi, uint32_t flevel, rnp::SecurityLevel &level) +{ + switch (flevel) { + case RNP_SECURITY_PROHIBITED: + level = rnp::SecurityLevel::Disabled; + break; + case RNP_SECURITY_INSECURE: + level = rnp::SecurityLevel::Insecure; + break; + case RNP_SECURITY_DEFAULT: + level = rnp::SecurityLevel::Default; + break; + default: + FFI_LOG(ffi, "Invalid security level : %" PRIu32, flevel); + return false; + } + return true; +} + +static bool +extract_flag(uint32_t &flags, uint32_t flag) +{ + bool res = flags & flag; + flags &= ~flag; + return res; +} + +rnp_result_t +rnp_add_security_rule(rnp_ffi_t ffi, + const char *type, + const char *name, + uint32_t flags, + uint64_t from, + uint32_t level) +try { + if (!ffi || !type || !name) { + return RNP_ERROR_NULL_POINTER; + } + /* convert values */ + rnp::FeatureType ftype; + int fvalue; + rnp::SecurityLevel sec_level; + if (!get_feature_sec_value(ffi, type, name, ftype, fvalue) || + !get_feature_sec_level(ffi, level, sec_level)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* check flags */ + bool rule_override = extract_flag(flags, RNP_SECURITY_OVERRIDE); + bool verify_key = extract_flag(flags, RNP_SECURITY_VERIFY_KEY); + bool verify_data = extract_flag(flags, RNP_SECURITY_VERIFY_DATA); + if (flags) { + FFI_LOG(ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* add rule */ + rnp::SecurityRule newrule(ftype, fvalue, sec_level, from); + newrule.override = rule_override; + /* Add rule for any action */ + if (!verify_key && !verify_data) { + ffi->profile().add_rule(newrule); + return RNP_SUCCESS; + } + /* Add rule for each specified key usage */ + if (verify_key) { + newrule.action = rnp::SecurityAction::VerifyKey; + ffi->profile().add_rule(newrule); + } + if (verify_data) { + newrule.action = rnp::SecurityAction::VerifyData; + ffi->profile().add_rule(newrule); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp::SecurityAction +get_security_action(uint32_t flags) +{ + if (flags & RNP_SECURITY_VERIFY_KEY) { + return rnp::SecurityAction::VerifyKey; + } + if (flags & RNP_SECURITY_VERIFY_DATA) { + return rnp::SecurityAction::VerifyData; + } + return rnp::SecurityAction::Any; +} + +rnp_result_t +rnp_get_security_rule(rnp_ffi_t ffi, + const char *type, + const char *name, + uint64_t time, + uint32_t * flags, + uint64_t * from, + uint32_t * level) +try { + if (!ffi || !type || !name || !level) { + return RNP_ERROR_NULL_POINTER; + } + /* convert values */ + rnp::FeatureType ftype; + int fvalue; + if (!get_feature_sec_value(ffi, type, name, ftype, fvalue)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* init default rule */ + rnp::SecurityRule rule(ftype, fvalue, ffi->profile().def_level()); + /* Check whether limited usage is requested */ + auto action = get_security_action(flags ? *flags : 0); + /* check whether rule exists */ + if (ffi->profile().has_rule(ftype, fvalue, time, action)) { + rule = ffi->profile().get_rule(ftype, fvalue, time, action); + } + /* fill the results */ + if (flags) { + *flags = rule.override ? RNP_SECURITY_OVERRIDE : 0; + switch (rule.action) { + case rnp::SecurityAction::VerifyKey: + *flags |= RNP_SECURITY_VERIFY_KEY; + break; + case rnp::SecurityAction::VerifyData: + *flags |= RNP_SECURITY_VERIFY_DATA; + break; + default: + break; + } + } + if (from) { + *from = rule.from; + } + switch (rule.level) { + case rnp::SecurityLevel::Disabled: + *level = RNP_SECURITY_PROHIBITED; + break; + case rnp::SecurityLevel::Insecure: + *level = RNP_SECURITY_INSECURE; + break; + case rnp::SecurityLevel::Default: + *level = RNP_SECURITY_DEFAULT; + break; + default: + FFI_LOG(ffi, "Invalid security level."); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_remove_security_rule(rnp_ffi_t ffi, + const char *type, + const char *name, + uint32_t level, + uint32_t flags, + uint64_t from, + size_t * removed) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + /* check flags */ + bool remove_all = extract_flag(flags, RNP_SECURITY_REMOVE_ALL); + bool rule_override = extract_flag(flags, RNP_SECURITY_OVERRIDE); + rnp::SecurityAction action = get_security_action(flags); + extract_flag(flags, RNP_SECURITY_VERIFY_DATA | RNP_SECURITY_VERIFY_KEY); + if (flags) { + FFI_LOG(ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* remove all rules */ + size_t rules = ffi->profile().size(); + if (!type) { + ffi->profile().clear_rules(); + goto success; + } + rnp::FeatureType ftype; + int fvalue; + rnp::SecurityLevel flevel; + if (!get_feature_sec_value(ffi, type, name, ftype, fvalue) || + !get_feature_sec_level(ffi, level, flevel)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* remove all rules for the specified type */ + if (!name) { + ffi->profile().clear_rules(ftype); + goto success; + } + if (remove_all) { + /* remove all rules for the specified type and name */ + ffi->profile().clear_rules(ftype, fvalue); + } else { + /* remove specific rule */ + rnp::SecurityRule rule(ftype, fvalue, flevel, from, action); + rule.override = rule_override; + ffi->profile().del_rule(rule); + } +success: + if (removed) { + *removed = rules - ffi->profile().size(); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_request_password(rnp_ffi_t ffi, rnp_key_handle_t key, const char *context, char **password) +try { + if (!ffi || !password || !ffi->getpasscb) { + return RNP_ERROR_NULL_POINTER; + } + + rnp::secure_vector<char> pass(MAX_PASSWORD_LENGTH, '\0'); + bool req_res = + ffi->getpasscb(ffi, ffi->getpasscb_ctx, key, context, pass.data(), pass.size()); + if (!req_res) { + return RNP_ERROR_GENERIC; + } + size_t pass_len = strlen(pass.data()) + 1; + *password = (char *) malloc(pass_len); + if (!*password) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*password, pass.data(), pass_len); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_set_timestamp(rnp_ffi_t ffi, uint64_t time) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + ffi->context.set_time(time); + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +load_keys_from_input(rnp_ffi_t ffi, rnp_input_t input, rnp_key_store_t *store) +{ + pgp_key_provider_t chained(rnp_key_provider_store, store); + const pgp_key_provider_t *key_providers[] = {&chained, &ffi->key_provider, NULL}; + const pgp_key_provider_t key_provider(rnp_key_provider_chained, key_providers); + + if (!input->src_directory.empty()) { + // load the keys + store->path = input->src_directory; + if (!rnp_key_store_load_from_path(store, &key_provider)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; + } + + // load the keys + if (!rnp_key_store_load_from_src(store, &input->src, &key_provider)) { + return RNP_ERROR_BAD_FORMAT; + } + return RNP_SUCCESS; +} + +static bool +key_needs_conversion(const pgp_key_t *key, const rnp_key_store_t *store) +{ + pgp_key_store_format_t key_format = key->format; + pgp_key_store_format_t store_format = store->format; + /* pgp_key_t->format is only ever GPG or G10. + * + * The key store, however, could have a format of KBX, GPG, or G10. + * A KBX (and GPG) key store can only handle a pgp_key_t with a format of GPG. + * A G10 key store can only handle a pgp_key_t with a format of G10. + */ + // should never be the case + assert(key_format != PGP_KEY_STORE_KBX); + // normalize the store format + if (store_format == PGP_KEY_STORE_KBX) { + store_format = PGP_KEY_STORE_GPG; + } + // from here, both the key and store formats can only be GPG or G10 + return key_format != store_format; +} + +static rnp_result_t +do_load_keys(rnp_ffi_t ffi, + rnp_input_t input, + pgp_key_store_format_t format, + key_type_t key_type) +{ + // create a temporary key store to hold the keys + std::unique_ptr<rnp_key_store_t> tmp_store; + try { + tmp_store = + std::unique_ptr<rnp_key_store_t>(new rnp_key_store_t(format, "", ffi->context)); + } catch (const std::invalid_argument &e) { + FFI_LOG(ffi, "Failed to create key store of format: %d", (int) format); + return RNP_ERROR_BAD_PARAMETERS; + } + + // load keys into our temporary store + rnp_result_t tmpret = load_keys_from_input(ffi, input, tmp_store.get()); + if (tmpret) { + return tmpret; + } + // go through all the loaded keys + for (auto &key : tmp_store->keys) { + // check that the key is the correct type and has not already been loaded + // add secret key part if it is and we need it + if (key.is_secret() && ((key_type == KEY_TYPE_SECRET) || (key_type == KEY_TYPE_ANY))) { + if (key_needs_conversion(&key, ffi->secring)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); + return RNP_ERROR_NOT_IMPLEMENTED; + } + + if (!rnp_key_store_add_key(ffi->secring, &key)) { + FFI_LOG(ffi, "Failed to add secret key"); + return RNP_ERROR_GENERIC; + } + } + + // add public key part if needed + if ((key.format == PGP_KEY_STORE_G10) || + ((key_type != KEY_TYPE_ANY) && (key_type != KEY_TYPE_PUBLIC))) { + continue; + } + + pgp_key_t keycp; + try { + keycp = pgp_key_t(key, true); + } catch (const std::exception &e) { + RNP_LOG("Failed to copy public key part: %s", e.what()); + return RNP_ERROR_GENERIC; + } + + /* TODO: We could do this a few different ways. There isn't an obvious reason + * to restrict what formats we load, so we don't necessarily need to require a + * conversion just to load and use a G10 key when using GPG keyrings, for + * example. We could just convert when saving. + */ + + if (key_needs_conversion(&key, ffi->pubring)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); + return RNP_ERROR_NOT_IMPLEMENTED; + } + + if (!rnp_key_store_add_key(ffi->pubring, &keycp)) { + FFI_LOG(ffi, "Failed to add public key"); + return RNP_ERROR_GENERIC; + } + } + // success, even if we didn't actually load any + return RNP_SUCCESS; +} + +static key_type_t +flags_to_key_type(uint32_t *flags) +{ + key_type_t type = KEY_TYPE_NONE; + // figure out what type of keys to operate on, based on flags + if ((*flags & RNP_LOAD_SAVE_PUBLIC_KEYS) && (*flags & RNP_LOAD_SAVE_SECRET_KEYS)) { + type = KEY_TYPE_ANY; + extract_flag(*flags, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS); + } else if (*flags & RNP_LOAD_SAVE_PUBLIC_KEYS) { + type = KEY_TYPE_PUBLIC; + extract_flag(*flags, RNP_LOAD_SAVE_PUBLIC_KEYS); + } else if (*flags & RNP_LOAD_SAVE_SECRET_KEYS) { + type = KEY_TYPE_SECRET; + extract_flag(*flags, RNP_LOAD_SAVE_SECRET_KEYS); + } + return type; +} + +rnp_result_t +rnp_load_keys(rnp_ffi_t ffi, const char *format, rnp_input_t input, uint32_t flags) +try { + // checks + if (!ffi || !format || !input) { + return RNP_ERROR_NULL_POINTER; + } + key_type_t type = flags_to_key_type(&flags); + if (!type) { + FFI_LOG(ffi, "invalid flags - must have public and/or secret keys"); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_store_format_t ks_format = PGP_KEY_STORE_UNKNOWN; + if (!parse_ks_format(&ks_format, format)) { + FFI_LOG(ffi, "invalid key store format: %s", format); + return RNP_ERROR_BAD_PARAMETERS; + } + + // check for any unrecognized flags (not forward-compat, but maybe still a good idea) + if (flags) { + FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + return do_load_keys(ffi, input, ks_format, type); +} +FFI_GUARD + +rnp_result_t +rnp_unload_keys(rnp_ffi_t ffi, uint32_t flags) +try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + + if (flags & ~(RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (flags & RNP_KEY_UNLOAD_PUBLIC) { + rnp_key_store_clear(ffi->pubring); + } + if (flags & RNP_KEY_UNLOAD_SECRET) { + rnp_key_store_clear(ffi->secring); + } + + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_input_dearmor_if_needed(rnp_input_t input, bool noheaders = false) +{ + if (!input) { + return RNP_ERROR_NULL_POINTER; + } + if (!input->src_directory.empty()) { + return RNP_ERROR_BAD_PARAMETERS; + } + bool require_armor = false; + /* check whether we already have armored stream */ + if (input->src.type == PGP_STREAM_ARMORED) { + if (!src_eof(&input->src)) { + /* be ready for the case of damaged armoring */ + return src_error(&input->src) ? RNP_ERROR_READ : RNP_SUCCESS; + } + /* eof - probably next we have another armored message */ + src_close(&input->src); + rnp_input_st *base = (rnp_input_st *) input->app_ctx; + *input = std::move(*base); + delete base; + /* we should not mix armored data with binary */ + require_armor = true; + } + if (src_eof(&input->src)) { + return RNP_ERROR_EOF; + } + /* check whether input is armored only if base64 is not forced */ + if (!noheaders && !is_armored_source(&input->src)) { + return require_armor ? RNP_ERROR_BAD_FORMAT : RNP_SUCCESS; + } + + /* Store original input in app_ctx and replace src/app_ctx with armored data */ + rnp_input_t app_ctx = new rnp_input_st(); + *app_ctx = std::move(*input); + + rnp_result_t ret = init_armored_src(&input->src, &app_ctx->src, noheaders); + if (ret) { + /* original src may be changed during init_armored_src call, so copy it back */ + *input = std::move(*app_ctx); + delete app_ctx; + return ret; + } + input->app_ctx = app_ctx; + return RNP_SUCCESS; +} + +static const char * +key_status_to_str(pgp_key_import_status_t status) +{ + if (status == PGP_KEY_IMPORT_STATUS_UNKNOWN) { + return "none"; + } + return id_str_pair::lookup(key_import_status_map, status, "none"); +} + +static rnp_result_t +add_key_status(json_object * keys, + const pgp_key_t * key, + pgp_key_import_status_t pub, + pgp_key_import_status_t sec) +{ + json_object *jsokey = json_object_new_object(); + if (!jsokey) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (!obj_add_field_json( + jsokey, "public", json_object_new_string(key_status_to_str(pub))) || + !obj_add_field_json( + jsokey, "secret", json_object_new_string(key_status_to_str(sec))) || + !obj_add_hex_json(jsokey, "fingerprint", key->fp().fingerprint, key->fp().length) || + !array_add_element_json(keys, jsokey)) { + json_object_put(jsokey); + return RNP_ERROR_OUT_OF_MEMORY; + } + + return RNP_SUCCESS; +} + +rnp_result_t +rnp_import_keys(rnp_ffi_t ffi, rnp_input_t input, uint32_t flags, char **results) +try { + if (!ffi || !input) { + return RNP_ERROR_NULL_POINTER; + } + bool sec = extract_flag(flags, RNP_LOAD_SAVE_SECRET_KEYS); + bool pub = extract_flag(flags, RNP_LOAD_SAVE_PUBLIC_KEYS); + if (!pub && !sec) { + FFI_LOG(ffi, "bad flags: need to specify public and/or secret keys"); + return RNP_ERROR_BAD_PARAMETERS; + } + bool skipbad = extract_flag(flags, RNP_LOAD_SAVE_PERMISSIVE); + bool single = extract_flag(flags, RNP_LOAD_SAVE_SINGLE); + bool base64 = extract_flag(flags, RNP_LOAD_SAVE_BASE64); + if (flags) { + FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + rnp_key_store_t tmp_store(PGP_KEY_STORE_GPG, "", ffi->context); + + /* check whether input is base64 */ + if (base64 && is_base64_source(input->src)) { + ret = rnp_input_dearmor_if_needed(input, true); + if (ret) { + return ret; + } + } + + // load keys to temporary keystore. + if (single) { + /* we need to init and handle dearmor on this layer since it may be used for the next + * keys import */ + ret = rnp_input_dearmor_if_needed(input); + if (ret == RNP_ERROR_EOF) { + return ret; + } + if (ret) { + FFI_LOG(ffi, "Failed to init/check dearmor."); + return ret; + } + ret = rnp_key_store_pgp_read_key_from_src(tmp_store, input->src, skipbad); + if (ret) { + return ret; + } + } else { + ret = rnp_key_store_pgp_read_from_src(&tmp_store, &input->src, skipbad); + if (ret) { + return ret; + } + } + + json_object *jsores = json_object_new_object(); + if (!jsores) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp::JSONObject jsowrap(jsores); + json_object * jsokeys = json_object_new_array(); + if (!obj_add_field_json(jsores, "keys", jsokeys)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + // import keys to the main keystore. + for (auto &key : tmp_store.keys) { + pgp_key_import_status_t pub_status = PGP_KEY_IMPORT_STATUS_UNKNOWN; + pgp_key_import_status_t sec_status = PGP_KEY_IMPORT_STATUS_UNKNOWN; + if (!pub && key.is_public()) { + continue; + } + // if we got here then we add public key itself or public part of the secret key + if (!rnp_key_store_import_key(ffi->pubring, &key, true, &pub_status)) { + return RNP_ERROR_BAD_PARAMETERS; + } + // import secret key part if available and requested + if (sec && key.is_secret()) { + if (!rnp_key_store_import_key(ffi->secring, &key, false, &sec_status)) { + return RNP_ERROR_BAD_PARAMETERS; + } + // add uids, certifications and other stuff from the public key if any + pgp_key_t *expub = rnp_key_store_get_key_by_fpr(ffi->pubring, key.fp()); + if (expub && !rnp_key_store_import_key(ffi->secring, expub, true, NULL)) { + return RNP_ERROR_BAD_PARAMETERS; + } + } + // now add key fingerprint to json based on statuses + rnp_result_t tmpret = add_key_status(jsokeys, &key, pub_status, sec_status); + if (tmpret) { + return tmpret; + } + } + + if (results) { + *results = (char *) json_object_to_json_string_ext(jsores, JSON_C_TO_STRING_PRETTY); + if (!*results) { + return RNP_ERROR_GENERIC; + } + *results = strdup(*results); + if (!*results) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} +FFI_GUARD + +static const char * +sig_status_to_str(pgp_sig_import_status_t status) +{ + if (status == PGP_SIG_IMPORT_STATUS_UNKNOWN) { + return "none"; + } + return id_str_pair::lookup(sig_import_status_map, status, "none"); +} + +static rnp_result_t +add_sig_status(json_object * sigs, + const pgp_key_t * signer, + pgp_sig_import_status_t pub, + pgp_sig_import_status_t sec) +{ + json_object *jsosig = json_object_new_object(); + if (!jsosig) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (!obj_add_field_json( + jsosig, "public", json_object_new_string(sig_status_to_str(pub))) || + !obj_add_field_json( + jsosig, "secret", json_object_new_string(sig_status_to_str(sec)))) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + + if (signer) { + const pgp_fingerprint_t &fp = signer->fp(); + if (!obj_add_hex_json(jsosig, "signer fingerprint", fp.fingerprint, fp.length)) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + if (!array_add_element_json(sigs, jsosig)) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + + return RNP_SUCCESS; +} + +rnp_result_t +rnp_import_signatures(rnp_ffi_t ffi, rnp_input_t input, uint32_t flags, char **results) +try { + if (!ffi || !input) { + return RNP_ERROR_NULL_POINTER; + } + if (flags) { + FFI_LOG(ffi, "wrong flags: %d", (int) flags); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_signature_list_t sigs; + rnp_result_t sigret = process_pgp_signatures(input->src, sigs); + if (sigret) { + FFI_LOG(ffi, "failed to parse signature(s)"); + return sigret; + } + + json_object *jsores = json_object_new_object(); + if (!jsores) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp::JSONObject jsowrap(jsores); + json_object * jsosigs = json_object_new_array(); + if (!obj_add_field_json(jsores, "sigs", jsosigs)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + for (auto &sig : sigs) { + pgp_sig_import_status_t pub_status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + pgp_sig_import_status_t sec_status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + pgp_key_t *pkey = rnp_key_store_import_signature(ffi->pubring, &sig, &pub_status); + pgp_key_t *skey = rnp_key_store_import_signature(ffi->secring, &sig, &sec_status); + sigret = add_sig_status(jsosigs, pkey ? pkey : skey, pub_status, sec_status); + if (sigret) { + return sigret; + } + } + + if (results) { + *results = (char *) json_object_to_json_string_ext(jsores, JSON_C_TO_STRING_PRETTY); + if (!*results) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *results = strdup(*results); + if (!*results) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +copy_store_keys(rnp_ffi_t ffi, rnp_key_store_t *dest, rnp_key_store_t *src) +{ + for (auto &key : src->keys) { + if (!rnp_key_store_add_key(dest, &key)) { + FFI_LOG(ffi, "failed to add key to the store"); + return false; + } + } + return true; +} + +static rnp_result_t +do_save_keys(rnp_ffi_t ffi, + rnp_output_t output, + pgp_key_store_format_t format, + key_type_t key_type) +{ + rnp_result_t ret = RNP_ERROR_GENERIC; + + // create a temporary key store to hold the keys + rnp_key_store_t *tmp_store = NULL; + try { + tmp_store = new rnp_key_store_t(format, "", ffi->context); + } catch (const std::invalid_argument &e) { + FFI_LOG(ffi, "Failed to create key store of format: %d", (int) format); + return RNP_ERROR_BAD_PARAMETERS; + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + // include the public keys, if desired + if (key_type == KEY_TYPE_PUBLIC || key_type == KEY_TYPE_ANY) { + if (!copy_store_keys(ffi, tmp_store, ffi->pubring)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + // include the secret keys, if desired + if (key_type == KEY_TYPE_SECRET || key_type == KEY_TYPE_ANY) { + if (!copy_store_keys(ffi, tmp_store, ffi->secring)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + } + // preliminary check on the format + for (auto &key : tmp_store->keys) { + if (key_needs_conversion(&key, tmp_store)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); + ret = RNP_ERROR_NOT_IMPLEMENTED; + goto done; + } + } + // write + if (output->dst_directory) { + try { + tmp_store->path = output->dst_directory; + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if (!rnp_key_store_write_to_path(tmp_store)) { + ret = RNP_ERROR_WRITE; + goto done; + } + ret = RNP_SUCCESS; + } else { + if (!rnp_key_store_write_to_dst(tmp_store, &output->dst)) { + ret = RNP_ERROR_WRITE; + goto done; + } + dst_flush(&output->dst); + output->keep = (output->dst.werr == RNP_SUCCESS); + ret = output->dst.werr; + } + +done: + delete tmp_store; + return ret; +} + +rnp_result_t +rnp_save_keys(rnp_ffi_t ffi, const char *format, rnp_output_t output, uint32_t flags) +try { + // checks + if (!ffi || !format || !output) { + return RNP_ERROR_NULL_POINTER; + } + key_type_t type = flags_to_key_type(&flags); + if (!type) { + FFI_LOG(ffi, "invalid flags - must have public and/or secret keys"); + return RNP_ERROR_BAD_PARAMETERS; + } + // check for any unrecognized flags (not forward-compat, but maybe still a good idea) + if (flags) { + FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_store_format_t ks_format = PGP_KEY_STORE_UNKNOWN; + if (!parse_ks_format(&ks_format, format)) { + FFI_LOG(ffi, "unknown key store format: %s", format); + return RNP_ERROR_BAD_PARAMETERS; + } + return do_save_keys(ffi, output, ks_format, type); +} +FFI_GUARD + +rnp_result_t +rnp_get_public_key_count(rnp_ffi_t ffi, size_t *count) +try { + if (!ffi || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = rnp_key_store_get_key_count(ffi->pubring); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_get_secret_key_count(rnp_ffi_t ffi, size_t *count) +try { + if (!ffi || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = rnp_key_store_get_key_count(ffi->secring); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_input_st::rnp_input_st() : reader(NULL), closer(NULL), app_ctx(NULL) +{ + memset(&src, 0, sizeof(src)); +} + +rnp_input_st & +rnp_input_st::operator=(rnp_input_st &&input) +{ + src_close(&src); + src = std::move(input.src); + memset(&input.src, 0, sizeof(input.src)); + reader = input.reader; + input.reader = NULL; + closer = input.closer; + input.closer = NULL; + app_ctx = input.app_ctx; + input.app_ctx = NULL; + src_directory = std::move(input.src_directory); + return *this; +} + +rnp_input_st::~rnp_input_st() +{ + bool armored = src.type == PGP_STREAM_ARMORED; + src_close(&src); + if (armored) { + rnp_input_t armored = (rnp_input_t) app_ctx; + delete armored; + app_ctx = NULL; + } +} + +rnp_result_t +rnp_input_from_path(rnp_input_t *input, const char *path) +try { + if (!input || !path) { + return RNP_ERROR_NULL_POINTER; + } + rnp_input_st *ob = new rnp_input_st(); + struct stat st = {0}; + if (rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + // a bit hacky, just save the directory path + ob->src_directory = path; + // return error on attempt to read from this source + (void) init_null_src(&ob->src); + } else { + // simple input from a file + rnp_result_t ret = init_file_src(&ob->src, path); + if (ret) { + delete ob; + return ret; + } + } + *input = ob; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_input_from_stdin(rnp_input_t *input) +try { + if (!input) { + return RNP_ERROR_NULL_POINTER; + } + *input = new rnp_input_st(); + rnp_result_t ret = init_stdin_src(&(*input)->src); + if (ret) { + delete *input; + *input = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_input_from_memory(rnp_input_t *input, const uint8_t buf[], size_t buf_len, bool do_copy) +try { + if (!input || !buf) { + return RNP_ERROR_NULL_POINTER; + } + if (!buf_len) { + return RNP_ERROR_SHORT_BUFFER; + } + *input = new rnp_input_st(); + uint8_t *data = (uint8_t *) buf; + if (do_copy) { + data = (uint8_t *) malloc(buf_len); + if (!data) { + delete *input; + *input = NULL; + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(data, buf, buf_len); + } + rnp_result_t ret = init_mem_src(&(*input)->src, data, buf_len, do_copy); + if (ret) { + if (do_copy) { + free(data); + } + delete *input; + *input = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +input_reader_bounce(pgp_source_t *src, void *buf, size_t len, size_t *read) +{ + rnp_input_t input = (rnp_input_t) src->param; + if (!input->reader) { + return false; + } + return input->reader(input->app_ctx, buf, len, read); +} + +static void +input_closer_bounce(pgp_source_t *src) +{ + rnp_input_t input = (rnp_input_t) src->param; + if (input->closer) { + input->closer(input->app_ctx); + } +} + +rnp_result_t +rnp_input_from_callback(rnp_input_t * input, + rnp_input_reader_t *reader, + rnp_input_closer_t *closer, + void * app_ctx) +try { + // checks + if (!input || !reader) { + return RNP_ERROR_NULL_POINTER; + } + rnp_input_st *obj = new rnp_input_st(); + pgp_source_t *src = &obj->src; + obj->reader = reader; + obj->closer = closer; + obj->app_ctx = app_ctx; + if (!init_src_common(src, 0)) { + delete obj; + return RNP_ERROR_OUT_OF_MEMORY; + } + src->param = obj; + src->read = input_reader_bounce; + src->close = input_closer_bounce; + src->type = PGP_STREAM_MEMORY; + *input = obj; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_input_destroy(rnp_input_t input) +try { + delete input; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_path(rnp_output_t *output, const char *path) +try { + struct rnp_output_st *ob = NULL; + struct stat st = {0}; + + if (!output || !path) { + return RNP_ERROR_NULL_POINTER; + } + ob = (rnp_output_st *) calloc(1, sizeof(*ob)); + if (!ob) { + return RNP_ERROR_OUT_OF_MEMORY; + } + if (rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + // a bit hacky, just save the directory path + ob->dst_directory = strdup(path); + if (!ob->dst_directory) { + free(ob); + return RNP_ERROR_OUT_OF_MEMORY; + } + } else { + // simple output to a file + rnp_result_t ret = init_file_dest(&ob->dst, path, true); + if (ret) { + free(ob); + return ret; + } + } + *output = ob; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_file(rnp_output_t *output, const char *path, uint32_t flags) +try { + if (!output || !path) { + return RNP_ERROR_NULL_POINTER; + } + bool overwrite = extract_flag(flags, RNP_OUTPUT_FILE_OVERWRITE); + bool random = extract_flag(flags, RNP_OUTPUT_FILE_RANDOM); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_output_t res = (rnp_output_t) calloc(1, sizeof(*res)); + if (!res) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = RNP_ERROR_GENERIC; + if (random) { + ret = init_tmpfile_dest(&res->dst, path, overwrite); + } else { + ret = init_file_dest(&res->dst, path, overwrite); + } + if (ret) { + free(res); + return ret; + } + *output = res; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_stdout(rnp_output_t *output) +try { + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + rnp_output_t res = (rnp_output_t) calloc(1, sizeof(*res)); + if (!res) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_stdout_dest(&res->dst); + if (ret) { + free(res); + return ret; + } + *output = res; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_memory(rnp_output_t *output, size_t max_alloc) +try { + // checks + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_mem_dest(&(*output)->dst, NULL, max_alloc); + if (ret) { + free(*output); + *output = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_armor(rnp_output_t base, rnp_output_t *output, const char *type) +try { + if (!base || !output) { + return RNP_ERROR_NULL_POINTER; + } + pgp_armored_msg_t msgtype = PGP_ARMORED_MESSAGE; + if (type) { + msgtype = static_cast<pgp_armored_msg_t>( + id_str_pair::lookup(armor_type_map, type, PGP_ARMORED_UNKNOWN)); + if (msgtype == PGP_ARMORED_UNKNOWN) { + RNP_LOG("Unsupported armor type: %s", type); + return RNP_ERROR_BAD_PARAMETERS; + } + } + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_armored_dst(&(*output)->dst, &base->dst, msgtype); + if (ret) { + free(*output); + *output = NULL; + return ret; + } + (*output)->app_ctx = base; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_memory_get_buf(rnp_output_t output, uint8_t **buf, size_t *len, bool do_copy) +try { + if (!output || !buf || !len) { + return RNP_ERROR_NULL_POINTER; + } + + *len = output->dst.writeb; + *buf = (uint8_t *) mem_dest_get_memory(&output->dst); + if (!*buf) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (do_copy) { + uint8_t *tmp_buf = *buf; + *buf = (uint8_t *) malloc(*len); + if (!*buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*buf, tmp_buf, *len); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +output_writer_bounce(pgp_dest_t *dst, const void *buf, size_t len) +{ + rnp_output_t output = (rnp_output_t) dst->param; + if (!output->writer) { + return RNP_ERROR_NULL_POINTER; + } + if (!output->writer(output->app_ctx, buf, len)) { + return RNP_ERROR_WRITE; + } + return RNP_SUCCESS; +} + +static void +output_closer_bounce(pgp_dest_t *dst, bool discard) +{ + rnp_output_t output = (rnp_output_t) dst->param; + if (output->closer) { + output->closer(output->app_ctx, discard); + } +} + +rnp_result_t +rnp_output_to_null(rnp_output_t *output) +try { + // checks + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t ret = init_null_dest(&(*output)->dst); + if (ret) { + free(*output); + *output = NULL; + return ret; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_write(rnp_output_t output, const void *data, size_t size, size_t *written) +try { + if (!output || (!data && size)) { + return RNP_ERROR_NULL_POINTER; + } + if (!data && !size) { + if (written) { + *written = 0; + } + return RNP_SUCCESS; + } + size_t old = output->dst.writeb + output->dst.clen; + dst_write(&output->dst, data, size); + if (!output->dst.werr && written) { + *written = output->dst.writeb + output->dst.clen - old; + } + output->keep = !output->dst.werr; + return output->dst.werr; +} +FFI_GUARD + +rnp_result_t +rnp_output_to_callback(rnp_output_t * output, + rnp_output_writer_t *writer, + rnp_output_closer_t *closer, + void * app_ctx) +try { + // checks + if (!output || !writer) { + return RNP_ERROR_NULL_POINTER; + } + + *output = (rnp_output_t) calloc(1, sizeof(**output)); + if (!*output) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*output)->writer = writer; + (*output)->closer = closer; + (*output)->app_ctx = app_ctx; + + pgp_dest_t *dst = &(*output)->dst; + dst->write = output_writer_bounce; + dst->close = output_closer_bounce; + dst->param = *output; + dst->type = PGP_STREAM_MEMORY; + dst->writeb = 0; + dst->werr = RNP_SUCCESS; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_output_finish(rnp_output_t output) +try { + if (!output) { + return RNP_ERROR_NULL_POINTER; + } + return dst_finish(&output->dst); +} +FFI_GUARD + +rnp_result_t +rnp_output_destroy(rnp_output_t output) +try { + if (output) { + if (output->dst.type == PGP_STREAM_ARMORED) { + ((rnp_output_t) output->app_ctx)->keep = output->keep; + } + dst_close(&output->dst, !output->keep); + free(output->dst_directory); + free(output); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_op_add_signature(rnp_ffi_t ffi, + rnp_op_sign_signatures_t &signatures, + rnp_key_handle_t key, + rnp_ctx_t & ctx, + rnp_op_sign_signature_t * sig) +{ + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *signkey = + find_suitable_key(PGP_OP_SIGN, get_key_require_secret(key), &key->ffi->key_provider); + if (!signkey) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + + try { + signatures.emplace_back(); + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_op_sign_signature_t newsig = &signatures.back(); + newsig->signer.key = signkey; + /* set default create/expire times */ + newsig->signer.sigcreate = ctx.sigcreate; + newsig->signer.sigexpire = ctx.sigexpire; + newsig->ffi = ffi; + + if (sig) { + *sig = newsig; + } + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_armor(rnp_ctx_t &ctx, bool armored) +{ + ctx.armor = armored; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_compression(rnp_ffi_t ffi, rnp_ctx_t &ctx, const char *compression, int level) +{ + if (!compression) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_compression_type_t zalg = PGP_C_UNKNOWN; + if (!str_to_compression_alg(compression, &zalg)) { + FFI_LOG(ffi, "Invalid compression: %s", compression); + return RNP_ERROR_BAD_PARAMETERS; + } + ctx.zalg = (int) zalg; + ctx.zlevel = level; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_hash(rnp_ffi_t ffi, rnp_ctx_t &ctx, const char *hash) +{ + if (!hash) { + return RNP_ERROR_NULL_POINTER; + } + + if (!str_to_hash_alg(hash, &ctx.halg)) { + FFI_LOG(ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_creation_time(rnp_ctx_t &ctx, uint32_t create) +{ + ctx.sigcreate = create; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_expiration_time(rnp_ctx_t &ctx, uint32_t expire) +{ + ctx.sigexpire = expire; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_flags(rnp_ffi_t ffi, rnp_ctx_t &ctx, uint32_t flags) +{ + ctx.no_wrap = extract_flag(flags, RNP_ENCRYPT_NOWRAP); + if (flags) { + FFI_LOG(ffi, "Unknown operation flags: %x", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_file_name(rnp_ctx_t &ctx, const char *filename) +{ + ctx.filename = filename ? filename : ""; + return RNP_SUCCESS; +} + +static rnp_result_t +rnp_op_set_file_mtime(rnp_ctx_t &ctx, uint32_t mtime) +{ + ctx.filemtime = mtime; + return RNP_SUCCESS; +} + +rnp_result_t +rnp_op_encrypt_create(rnp_op_encrypt_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t output) +try { + // checks + if (!op || !ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_encrypt_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->ffi = ffi; + (*op)->input = input; + (*op)->output = output; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_add_recipient(rnp_op_encrypt_t op, rnp_key_handle_t handle) +try { + // checks + if (!op || !handle) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = find_suitable_key( + PGP_OP_ENCRYPT, get_key_prefer_public(handle), &handle->ffi->key_provider); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + op->rnpctx.recipients.push_back(key); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_add_signature(rnp_op_encrypt_t op, + rnp_key_handle_t key, + rnp_op_sign_signature_t *sig) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_add_signature(op->ffi, op->signatures, key, op->rnpctx, sig); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_hash(rnp_op_encrypt_t op, const char *hash) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_hash(op->ffi, op->rnpctx, hash); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_creation_time(rnp_op_encrypt_t op, uint32_t create) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_creation_time(op->rnpctx, create); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_expiration_time(rnp_op_encrypt_t op, uint32_t expire) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_expiration_time(op->rnpctx, expire); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_add_password(rnp_op_encrypt_t op, + const char * password, + const char * s2k_hash, + size_t iterations, + const char * s2k_cipher) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (password && !*password) { + // no blank passwords + FFI_LOG(op->ffi, "Blank password"); + return RNP_ERROR_BAD_PARAMETERS; + } + + // set some defaults + if (!s2k_hash) { + s2k_hash = DEFAULT_HASH_ALG; + } + if (!s2k_cipher) { + s2k_cipher = DEFAULT_SYMM_ALG; + } + // parse + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(s2k_hash, &hash_alg)) { + FFI_LOG(op->ffi, "Invalid hash: %s", s2k_hash); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN; + if (!str_to_cipher(s2k_cipher, &symm_alg)) { + FFI_LOG(op->ffi, "Invalid cipher: %s", s2k_cipher); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp::secure_vector<char> ask_pass(MAX_PASSWORD_LENGTH, '\0'); + if (!password) { + pgp_password_ctx_t pswdctx(PGP_OP_ENCRYPT_SYM); + if (!pgp_request_password( + &op->ffi->pass_provider, &pswdctx, ask_pass.data(), ask_pass.size())) { + return RNP_ERROR_BAD_PASSWORD; + } + password = ask_pass.data(); + } + return op->rnpctx.add_encryption_password(password, hash_alg, symm_alg, iterations); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_armor(rnp_op_encrypt_t op, bool armored) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_armor(op->rnpctx, armored); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_cipher(rnp_op_encrypt_t op, const char *cipher) +try { + // checks + if (!op || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_cipher(cipher, &op->rnpctx.ealg)) { + FFI_LOG(op->ffi, "Invalid cipher: %s", cipher); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_aead(rnp_op_encrypt_t op, const char *alg) +try { + // checks + if (!op || !alg) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_aead_alg(alg, &op->rnpctx.aalg)) { + FFI_LOG(op->ffi, "Invalid AEAD algorithm: %s", alg); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_aead_bits(rnp_op_encrypt_t op, int bits) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if ((bits < 0) || (bits > 16)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->rnpctx.abits = bits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_compression(rnp_op_encrypt_t op, const char *compression, int level) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_compression(op->ffi, op->rnpctx, compression, level); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_flags(rnp_op_encrypt_t op, uint32_t flags) +try { + // checks + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_flags(op->ffi, op->rnpctx, flags); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_file_name(rnp_op_encrypt_t op, const char *filename) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_name(op->rnpctx, filename); +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_set_file_mtime(rnp_op_encrypt_t op, uint32_t mtime) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_mtime(op->rnpctx, mtime); +} +FFI_GUARD + +static pgp_write_handler_t +pgp_write_handler(pgp_password_provider_t *pass_provider, + rnp_ctx_t * rnpctx, + void * param, + pgp_key_provider_t * key_provider) +{ + pgp_write_handler_t handler; + memset(&handler, 0, sizeof(handler)); + handler.password_provider = pass_provider; + handler.ctx = rnpctx; + handler.param = param; + handler.key_provider = key_provider; + return handler; +} + +static rnp_result_t +rnp_op_add_signatures(rnp_op_sign_signatures_t &opsigs, rnp_ctx_t &ctx) +{ + for (auto &sig : opsigs) { + if (!sig.signer.key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + + rnp_signer_info_t sinfo = sig.signer; + if (!sig.hash_set) { + sinfo.halg = ctx.halg; + } + if (!sig.expiry_set) { + sinfo.sigexpire = ctx.sigexpire; + } + if (!sig.create_set) { + sinfo.sigcreate = ctx.sigcreate; + } + ctx.signers.push_back(sinfo); + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_op_encrypt_execute(rnp_op_encrypt_t op) +try { + // checks + if (!op || !op->input || !op->output) { + return RNP_ERROR_NULL_POINTER; + } + + // set the default hash alg if none was specified + if (!op->rnpctx.halg) { + op->rnpctx.halg = DEFAULT_PGP_HASH_ALG; + } + pgp_write_handler_t handler = + pgp_write_handler(&op->ffi->pass_provider, &op->rnpctx, NULL, &op->ffi->key_provider); + + rnp_result_t ret; + if (!op->signatures.empty() && (ret = rnp_op_add_signatures(op->signatures, op->rnpctx))) { + return ret; + } + ret = rnp_encrypt_sign_src(&handler, &op->input->src, &op->output->dst); + + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + op->input = NULL; + op->output = NULL; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_encrypt_destroy(rnp_op_encrypt_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_create(rnp_op_sign_t *op, rnp_ffi_t ffi, rnp_input_t input, rnp_output_t output) +try { + // checks + if (!op || !ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_sign_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->ffi = ffi; + (*op)->input = input; + (*op)->output = output; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_cleartext_create(rnp_op_sign_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t output) +try { + rnp_result_t res = rnp_op_sign_create(op, ffi, input, output); + if (!res) { + (*op)->rnpctx.clearsign = true; + } + return res; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_detached_create(rnp_op_sign_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t signature) +try { + rnp_result_t res = rnp_op_sign_create(op, ffi, input, signature); + if (!res) { + (*op)->rnpctx.detached = true; + } + return res; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_add_signature(rnp_op_sign_t op, rnp_key_handle_t key, rnp_op_sign_signature_t *sig) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_add_signature(op->ffi, op->signatures, key, op->rnpctx, sig); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_signature_set_hash(rnp_op_sign_signature_t sig, const char *hash) +try { + if (!sig || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_hash_alg(hash, &sig->signer.halg)) { + FFI_LOG(sig->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + sig->hash_set = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_signature_set_creation_time(rnp_op_sign_signature_t sig, uint32_t create) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + sig->signer.sigcreate = create; + sig->create_set = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_signature_set_expiration_time(rnp_op_sign_signature_t sig, uint32_t expires) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + sig->signer.sigexpire = expires; + sig->expiry_set = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_armor(rnp_op_sign_t op, bool armored) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_armor(op->rnpctx, armored); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_compression(rnp_op_sign_t op, const char *compression, int level) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_compression(op->ffi, op->rnpctx, compression, level); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_hash(rnp_op_sign_t op, const char *hash) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_hash(op->ffi, op->rnpctx, hash); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_creation_time(rnp_op_sign_t op, uint32_t create) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_creation_time(op->rnpctx, create); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_expiration_time(rnp_op_sign_t op, uint32_t expire) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_expiration_time(op->rnpctx, expire); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_file_name(rnp_op_sign_t op, const char *filename) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_name(op->rnpctx, filename); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_set_file_mtime(rnp_op_sign_t op, uint32_t mtime) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + return rnp_op_set_file_mtime(op->rnpctx, mtime); +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_execute(rnp_op_sign_t op) +try { + // checks + if (!op || !op->input || !op->output) { + return RNP_ERROR_NULL_POINTER; + } + + // set the default hash alg if none was specified + if (!op->rnpctx.halg) { + op->rnpctx.halg = DEFAULT_PGP_HASH_ALG; + } + pgp_write_handler_t handler = + pgp_write_handler(&op->ffi->pass_provider, &op->rnpctx, NULL, &op->ffi->key_provider); + + rnp_result_t ret; + if ((ret = rnp_op_add_signatures(op->signatures, op->rnpctx))) { + return ret; + } + ret = rnp_sign_src(&handler, &op->input->src, &op->output->dst); + + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + op->input = NULL; + op->output = NULL; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_sign_destroy(rnp_op_sign_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +static void +rnp_op_verify_on_signatures(const std::vector<pgp_signature_info_t> &sigs, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + + try { + /* in case we have multiple signed layers */ + delete[] op->signatures; + op->signatures = new rnp_op_verify_signature_st[sigs.size()]; + } catch (const std::exception &e) { + FFI_LOG(op->ffi, "%s", e.what()); + return; + } + op->signature_count = sigs.size(); + + size_t i = 0; + for (const auto &sinfo : sigs) { + rnp_op_verify_signature_t res = &op->signatures[i++]; + /* sinfo.sig may be NULL */ + if (sinfo.sig) { + try { + res->sig_pkt = *sinfo.sig; + } catch (const std::exception &e) { + FFI_LOG(op->ffi, "%s", e.what()); + } + } + + if (sinfo.unknown) { + res->verify_status = RNP_ERROR_SIGNATURE_UNKNOWN; + } else if (sinfo.valid) { + res->verify_status = sinfo.expired ? RNP_ERROR_SIGNATURE_EXPIRED : RNP_SUCCESS; + } else { + res->verify_status = + sinfo.no_signer ? RNP_ERROR_KEY_NOT_FOUND : RNP_ERROR_SIGNATURE_INVALID; + } + res->ffi = op->ffi; + } +} + +static bool +rnp_verify_src_provider(pgp_parse_handler_t *handler, pgp_source_t *src) +{ + /* this one is called only when input for detached signature is needed */ + rnp_op_verify_t op = (rnp_op_verify_t) handler->param; + if (!op->detached_input) { + return false; + } + *src = op->detached_input->src; + /* we should give ownership on src to caller */ + memset(&op->detached_input->src, 0, sizeof(op->detached_input->src)); + return true; +}; + +static bool +rnp_verify_dest_provider(pgp_parse_handler_t *handler, + pgp_dest_t ** dst, + bool * closedst, + const char * filename, + uint32_t mtime) +{ + rnp_op_verify_t op = (rnp_op_verify_t) handler->param; + if (!op->output) { + return false; + } + *dst = &(op->output->dst); + *closedst = false; + op->filename = filename ? strdup(filename) : NULL; + op->file_mtime = mtime; + return true; +} + +static void +recipient_handle_from_pk_sesskey(rnp_recipient_handle_t handle, + const pgp_pk_sesskey_t &sesskey) +{ + static_assert(sizeof(handle->keyid) == PGP_KEY_ID_SIZE, "Keyid size mismatch"); + memcpy(handle->keyid, sesskey.key_id.data(), PGP_KEY_ID_SIZE); + handle->palg = sesskey.alg; +} + +static void +symenc_handle_from_sk_sesskey(rnp_symenc_handle_t handle, const pgp_sk_sesskey_t &sesskey) +{ + handle->alg = sesskey.alg; + handle->halg = sesskey.s2k.hash_alg; + handle->s2k_type = sesskey.s2k.specifier; + if (sesskey.s2k.specifier == PGP_S2KS_ITERATED_AND_SALTED) { + handle->iterations = pgp_s2k_decode_iterations(sesskey.s2k.iterations); + } else { + handle->iterations = 1; + } + handle->aalg = sesskey.aalg; +} + +static void +rnp_verify_on_recipients(const std::vector<pgp_pk_sesskey_t> &recipients, + const std::vector<pgp_sk_sesskey_t> &passwords, + void * param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + /* store only top-level encrypted stream recipients info for now */ + if (op->encrypted_layers++) { + return; + } + if (!recipients.empty()) { + op->recipients = + (rnp_recipient_handle_t) calloc(recipients.size(), sizeof(*op->recipients)); + if (!op->recipients) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + for (size_t i = 0; i < recipients.size(); i++) { + recipient_handle_from_pk_sesskey(&op->recipients[i], recipients[i]); + } + } + op->recipient_count = recipients.size(); + if (!passwords.empty()) { + op->symencs = (rnp_symenc_handle_t) calloc(passwords.size(), sizeof(*op->symencs)); + if (!op->symencs) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + for (size_t i = 0; i < passwords.size(); i++) { + symenc_handle_from_sk_sesskey(&op->symencs[i], passwords[i]); + } + } + op->symenc_count = passwords.size(); +} + +static void +rnp_verify_on_decryption_start(pgp_pk_sesskey_t *pubenc, pgp_sk_sesskey_t *symenc, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + /* store only top-level encrypted stream info */ + if (op->encrypted_layers > 1) { + return; + } + if (pubenc) { + op->used_recipient = (rnp_recipient_handle_t) calloc(1, sizeof(*op->used_recipient)); + if (!op->used_recipient) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + recipient_handle_from_pk_sesskey(op->used_recipient, *pubenc); + return; + } + if (symenc) { + op->used_symenc = (rnp_symenc_handle_t) calloc(1, sizeof(*op->used_symenc)); + if (!op->used_symenc) { + FFI_LOG(op->ffi, "allocation failed"); + return; + } + symenc_handle_from_sk_sesskey(op->used_symenc, *symenc); + return; + } + FFI_LOG(op->ffi, "Warning! Both pubenc and symenc are NULL."); +} + +static void +rnp_verify_on_decryption_info(bool mdc, pgp_aead_alg_t aead, pgp_symm_alg_t salg, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + /* store only top-level encrypted stream info for now */ + if (op->encrypted_layers > 1) { + return; + } + op->mdc = mdc; + op->aead = aead; + op->salg = salg; + op->encrypted = true; +} + +static void +rnp_verify_on_decryption_done(bool validated, void *param) +{ + rnp_op_verify_t op = (rnp_op_verify_t) param; + if (op->encrypted_layers > 1) { + return; + } + op->validated = validated; +} + +rnp_result_t +rnp_op_verify_create(rnp_op_verify_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_output_t output) +try { + if (!op || !ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_verify_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->ffi = ffi; + (*op)->input = input; + (*op)->output = output; + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_detached_create(rnp_op_verify_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_input_t signature) +try { + if (!op || !ffi || !input || !signature) { + return RNP_ERROR_NULL_POINTER; + } + + *op = new rnp_op_verify_st(); + rnp_ctx_init_ffi((*op)->rnpctx, ffi); + (*op)->rnpctx.detached = true; + (*op)->ffi = ffi; + (*op)->input = signature; + (*op)->detached_input = input; + + return RNP_SUCCESS; +} +FFI_GUARD + +static pgp_key_t * +ffi_decrypt_key_provider(const pgp_key_request_ctx_t *ctx, void *userdata) +{ + rnp_decryption_kp_param_t *kparam = (rnp_decryption_kp_param_t *) userdata; + + auto ffi = kparam->op->ffi; + bool hidden = ctx->secret && (ctx->search.type == PGP_KEY_SEARCH_KEYID) && + (ctx->search.by.keyid == pgp_key_id_t({})); + /* default to the FFI key provider if not hidden keyid request */ + if (!hidden) { + return ffi->key_provider.callback(ctx, ffi->key_provider.userdata); + } + /* if we had hidden request and last key is NULL then key search was exhausted */ + if (!kparam->op->allow_hidden || (kparam->has_hidden && !kparam->last)) { + return NULL; + } + /* inform user about the hidden recipient before searching through the loaded keys */ + if (!kparam->has_hidden) { + call_key_callback(ffi, ctx->search, ctx->secret); + } + kparam->has_hidden = true; + kparam->last = find_key(ffi, ctx->search, true, true, kparam->last); + return kparam->last; +} + +rnp_result_t +rnp_op_verify_set_flags(rnp_op_verify_t op, uint32_t flags) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + /* Allow to decrypt without valid signatures */ + op->ignore_sigs = extract_flag(flags, RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT); + /* Strict mode: require all signatures to be valid */ + op->require_all_sigs = extract_flag(flags, RNP_VERIFY_REQUIRE_ALL_SIGS); + /* Allow hidden recipients if any */ + op->allow_hidden = extract_flag(flags, RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT); + + if (flags) { + FFI_LOG(op->ffi, "Unknown operation flags: %x", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_execute(rnp_op_verify_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_parse_handler_t handler; + + handler.password_provider = &op->ffi->pass_provider; + + rnp_decryption_kp_param_t kparam(op); + pgp_key_provider_t kprov = {ffi_decrypt_key_provider, &kparam}; + + handler.key_provider = &kprov; + handler.on_signatures = rnp_op_verify_on_signatures; + handler.src_provider = rnp_verify_src_provider; + handler.dest_provider = rnp_verify_dest_provider; + handler.on_recipients = rnp_verify_on_recipients; + handler.on_decryption_start = rnp_verify_on_decryption_start; + handler.on_decryption_info = rnp_verify_on_decryption_info; + handler.on_decryption_done = rnp_verify_on_decryption_done; + handler.param = op; + handler.ctx = &op->rnpctx; + + rnp_result_t ret = process_pgp_source(&handler, op->input->src); + /* Allow to decrypt data ignoring the signatures check if requested */ + if (op->ignore_sigs && op->validated && (ret == RNP_ERROR_SIGNATURE_INVALID)) { + ret = RNP_SUCCESS; + } + /* Allow to require all signatures be valid */ + if (op->require_all_sigs && !ret) { + for (size_t i = 0; i < op->signature_count; i++) { + if (op->signatures[i].verify_status) { + ret = RNP_ERROR_SIGNATURE_INVALID; + break; + } + } + } + if (op->output) { + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_signature_count(rnp_op_verify_t op, size_t *count) +try { + if (!op || !count) { + return RNP_ERROR_NULL_POINTER; + } + + *count = op->signature_count; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_signature_at(rnp_op_verify_t op, size_t idx, rnp_op_verify_signature_t *sig) +try { + if (!op || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= op->signature_count) { + FFI_LOG(op->ffi, "Invalid signature index: %zu", idx); + return RNP_ERROR_BAD_PARAMETERS; + } + *sig = &op->signatures[idx]; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_file_info(rnp_op_verify_t op, char **filename, uint32_t *mtime) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (mtime) { + *mtime = op->file_mtime; + } + if (filename) { + if (op->filename) { + *filename = strdup(op->filename); + } else { + *filename = NULL; + } + } + return RNP_SUCCESS; +} +FFI_GUARD + +static const char * +get_protection_mode(rnp_op_verify_t op) +{ + if (!op->encrypted) { + return "none"; + } + if (op->mdc) { + return "cfb-mdc"; + } + if (op->aead == PGP_AEAD_NONE) { + return "cfb"; + } + switch (op->aead) { + case PGP_AEAD_EAX: + return "aead-eax"; + case PGP_AEAD_OCB: + return "aead-ocb"; + default: + return "aead-unknown"; + } +} + +static const char * +get_protection_cipher(rnp_op_verify_t op) +{ + if (!op->encrypted) { + return "none"; + } + return id_str_pair::lookup(symm_alg_map, op->salg); +} + +rnp_result_t +rnp_op_verify_get_protection_info(rnp_op_verify_t op, char **mode, char **cipher, bool *valid) +try { + if (!op || (!mode && !cipher && !valid)) { + return RNP_ERROR_NULL_POINTER; + } + + if (mode) { + *mode = strdup(get_protection_mode(op)); + if (!*mode) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + if (cipher) { + *cipher = strdup(get_protection_cipher(op)); + if (!*cipher) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + if (valid) { + *valid = op->validated; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_recipient_count(rnp_op_verify_t op, size_t *count) +try { + if (!op || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = op->recipient_count; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_used_recipient(rnp_op_verify_t op, rnp_recipient_handle_t *recipient) +try { + if (!op || !recipient) { + return RNP_ERROR_NULL_POINTER; + } + *recipient = op->used_recipient; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_recipient_at(rnp_op_verify_t op, + size_t idx, + rnp_recipient_handle_t *recipient) +try { + if (!op || !recipient) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= op->recipient_count) { + return RNP_ERROR_BAD_PARAMETERS; + } + *recipient = &op->recipients[idx]; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_recipient_get_keyid(rnp_recipient_handle_t recipient, char **keyid) +try { + if (!recipient || !keyid) { + return RNP_ERROR_NULL_POINTER; + } + static_assert(sizeof(recipient->keyid) == PGP_KEY_ID_SIZE, + "rnp_recipient_handle_t.keyid size mismatch"); + return hex_encode_value(recipient->keyid, PGP_KEY_ID_SIZE, keyid); +} +FFI_GUARD + +rnp_result_t +rnp_recipient_get_alg(rnp_recipient_handle_t recipient, char **alg) +try { + if (!recipient || !alg) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(pubkey_alg_map, recipient->palg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_symenc_count(rnp_op_verify_t op, size_t *count) +try { + if (!op || !count) { + return RNP_ERROR_NULL_POINTER; + } + *count = op->symenc_count; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_used_symenc(rnp_op_verify_t op, rnp_symenc_handle_t *symenc) +try { + if (!op || !symenc) { + return RNP_ERROR_NULL_POINTER; + } + *symenc = op->used_symenc; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_get_symenc_at(rnp_op_verify_t op, size_t idx, rnp_symenc_handle_t *symenc) +try { + if (!op || !symenc) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= op->symenc_count) { + return RNP_ERROR_BAD_PARAMETERS; + } + *symenc = &op->symencs[idx]; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_cipher(rnp_symenc_handle_t symenc, char **cipher) +try { + if (!symenc || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(symm_alg_map, symenc->alg, cipher); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_aead_alg(rnp_symenc_handle_t symenc, char **alg) +try { + if (!symenc || !alg) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(aead_alg_map, symenc->aalg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_hash_alg(rnp_symenc_handle_t symenc, char **alg) +try { + if (!symenc || !alg) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(hash_alg_map, symenc->halg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_s2k_type(rnp_symenc_handle_t symenc, char **type) +try { + if (!symenc || !type) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(s2k_type_map, symenc->s2k_type, type); +} +FFI_GUARD + +rnp_result_t +rnp_symenc_get_s2k_iterations(rnp_symenc_handle_t symenc, uint32_t *iterations) +try { + if (!symenc || !iterations) { + return RNP_ERROR_NULL_POINTER; + } + *iterations = symenc->iterations; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_destroy(rnp_op_verify_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_op_verify_st::~rnp_op_verify_st() +{ + delete[] signatures; + free(filename); + free(recipients); + free(used_recipient); + free(symencs); + free(used_symenc); +} + +rnp_result_t +rnp_op_verify_signature_get_status(rnp_op_verify_signature_t sig) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + return sig->verify_status; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_handle(rnp_op_verify_signature_t sig, + rnp_signature_handle_t * handle) +try { + if (!sig || !handle) { + return RNP_ERROR_NULL_POINTER; + } + + *handle = (rnp_signature_handle_t) calloc(1, sizeof(**handle)); + if (!*handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + try { + (*handle)->sig = new pgp_subsig_t(sig->sig_pkt); + } catch (const std::exception &e) { + FFI_LOG(sig->ffi, "%s", e.what()); + free(*handle); + return RNP_ERROR_OUT_OF_MEMORY; + } + (*handle)->ffi = sig->ffi; + (*handle)->key = NULL; + (*handle)->own_sig = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_hash(rnp_op_verify_signature_t sig, char **hash) +try { + if (!sig || !hash) { + return RNP_ERROR_NULL_POINTER; + } + return get_map_value(hash_alg_map, sig->sig_pkt.halg, hash); +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_key(rnp_op_verify_signature_t sig, rnp_key_handle_t *key) +try { + if (!sig->sig_pkt.has_keyid()) { + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_ffi_t ffi = sig->ffi; + // create a search (since we'll use this later anyways) + pgp_key_search_t search(PGP_KEY_SEARCH_KEYID); + search.by.keyid = sig->sig_pkt.keyid(); + + // search the stores + pgp_key_t *pub = rnp_key_store_search(ffi->pubring, &search, NULL); + pgp_key_t *sec = rnp_key_store_search(ffi->secring, &search, NULL); + if (!pub && !sec) { + return RNP_ERROR_KEY_NOT_FOUND; + } + + struct rnp_key_handle_st *handle = (rnp_key_handle_st *) calloc(1, sizeof(*handle)); + if (!handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + handle->ffi = ffi; + handle->pub = pub; + handle->sec = sec; + handle->locator = search; + *key = handle; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_verify_signature_get_times(rnp_op_verify_signature_t sig, + uint32_t * create, + uint32_t * expires) +try { + if (create) { + *create = sig->sig_pkt.creation(); + } + if (expires) { + *expires = sig->sig_pkt.expiration(); + } + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_decrypt(rnp_ffi_t ffi, rnp_input_t input, rnp_output_t output) +try { + // checks + if (!ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + rnp_op_verify_t op = NULL; + rnp_result_t ret = rnp_op_verify_create(&op, ffi, input, output); + if (ret) { + return ret; + } + ret = rnp_op_verify_set_flags(op, RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT); + if (!ret) { + ret = rnp_op_verify_execute(op); + } + rnp_op_verify_destroy(op); + return ret; +} +FFI_GUARD + +static rnp_result_t +str_to_locator(rnp_ffi_t ffi, + pgp_key_search_t *locator, + const char * identifier_type, + const char * identifier) +{ + // parse the identifier type + locator->type = static_cast<pgp_key_search_type_t>( + id_str_pair::lookup(identifier_type_map, identifier_type, PGP_KEY_SEARCH_UNKNOWN)); + if (locator->type == PGP_KEY_SEARCH_UNKNOWN) { + FFI_LOG(ffi, "Invalid identifier type: %s", identifier_type); + return RNP_ERROR_BAD_PARAMETERS; + } + // see what type we have + switch (locator->type) { + case PGP_KEY_SEARCH_USERID: + if (snprintf(locator->by.userid, sizeof(locator->by.userid), "%s", identifier) >= + (int) sizeof(locator->by.userid)) { + FFI_LOG(ffi, "UserID too long"); + return RNP_ERROR_BAD_PARAMETERS; + } + break; + case PGP_KEY_SEARCH_KEYID: { + if (strlen(identifier) != (PGP_KEY_ID_SIZE * 2) || + !rnp::hex_decode(identifier, locator->by.keyid.data(), locator->by.keyid.size())) { + FFI_LOG(ffi, "Invalid keyid: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + } break; + case PGP_KEY_SEARCH_FINGERPRINT: { + // TODO: support v5 fingerprints + // Note: v2/v3 fingerprint are 16 bytes (32 chars) long. + if ((strlen(identifier) != (PGP_FINGERPRINT_SIZE * 2)) && (strlen(identifier) != 32)) { + FFI_LOG(ffi, "Invalid fingerprint: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + locator->by.fingerprint.length = rnp::hex_decode( + identifier, locator->by.fingerprint.fingerprint, PGP_FINGERPRINT_SIZE); + if (!locator->by.fingerprint.length) { + FFI_LOG(ffi, "Invalid fingerprint: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + } break; + case PGP_KEY_SEARCH_GRIP: { + if (strlen(identifier) != (PGP_KEY_GRIP_SIZE * 2) || + !rnp::hex_decode(identifier, locator->by.grip.data(), locator->by.grip.size())) { + FFI_LOG(ffi, "Invalid grip: %s", identifier); + return RNP_ERROR_BAD_PARAMETERS; + } + } break; + default: + // should never happen + assert(false); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} + +static bool +locator_to_str(const pgp_key_search_t &locator, + const char ** identifier_type, + char * identifier, + size_t identifier_size) +{ + // find the identifier type string with the map + *identifier_type = id_str_pair::lookup(identifier_type_map, locator.type, NULL); + if (!*identifier_type) { + return false; + } + // fill in the actual identifier + switch (locator.type) { + case PGP_KEY_SEARCH_USERID: + if (snprintf(identifier, identifier_size, "%s", locator.by.userid) >= + (int) identifier_size) { + return false; + } + break; + case PGP_KEY_SEARCH_KEYID: + if (!rnp::hex_encode( + locator.by.keyid.data(), locator.by.keyid.size(), identifier, identifier_size)) { + return false; + } + break; + case PGP_KEY_SEARCH_FINGERPRINT: + if (!rnp::hex_encode(locator.by.fingerprint.fingerprint, + locator.by.fingerprint.length, + identifier, + identifier_size)) { + return false; + } + break; + case PGP_KEY_SEARCH_GRIP: + if (!rnp::hex_encode( + locator.by.grip.data(), locator.by.grip.size(), identifier, identifier_size)) { + return false; + } + break; + default: + assert(false); + return false; + } + return true; +} + +static rnp_result_t +rnp_locate_key_int(rnp_ffi_t ffi, + const pgp_key_search_t &locator, + rnp_key_handle_t * handle, + bool require_secret = false) +{ + // search pubring + pgp_key_t *pub = rnp_key_store_search(ffi->pubring, &locator, NULL); + // search secring + pgp_key_t *sec = rnp_key_store_search(ffi->secring, &locator, NULL); + + if (require_secret && !sec) { + *handle = NULL; + return RNP_SUCCESS; + } + + if (pub || sec) { + *handle = (rnp_key_handle_t) malloc(sizeof(**handle)); + if (!*handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*handle)->ffi = ffi; + (*handle)->pub = pub; + (*handle)->sec = sec; + (*handle)->locator = locator; + } else { + *handle = NULL; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_locate_key(rnp_ffi_t ffi, + const char * identifier_type, + const char * identifier, + rnp_key_handle_t *handle) +try { + // checks + if (!ffi || !identifier_type || !identifier || !handle) { + return RNP_ERROR_NULL_POINTER; + } + + // figure out the identifier type + pgp_key_search_t locator; + rnp_result_t ret = str_to_locator(ffi, &locator, identifier_type, identifier); + if (ret) { + return ret; + } + + return rnp_locate_key_int(ffi, locator, handle); +} +FFI_GUARD + +rnp_result_t +rnp_key_export(rnp_key_handle_t handle, rnp_output_t output, uint32_t flags) +try { + pgp_dest_t *dst = NULL; + pgp_dest_t armordst = {}; + + // checks + if (!handle || !output) { + return RNP_ERROR_NULL_POINTER; + } + dst = &output->dst; + if ((flags & RNP_KEY_EXPORT_PUBLIC) && (flags & RNP_KEY_EXPORT_SECRET)) { + FFI_LOG(handle->ffi, "Invalid export flags, select only public or secret, not both."); + return RNP_ERROR_BAD_PARAMETERS; + } + + // handle flags + bool armored = extract_flag(flags, RNP_KEY_EXPORT_ARMORED); + pgp_key_t * key = NULL; + rnp_key_store_t *store = NULL; + if (flags & RNP_KEY_EXPORT_PUBLIC) { + extract_flag(flags, RNP_KEY_EXPORT_PUBLIC); + key = get_key_require_public(handle); + store = handle->ffi->pubring; + } else if (flags & RNP_KEY_EXPORT_SECRET) { + extract_flag(flags, RNP_KEY_EXPORT_SECRET); + key = get_key_require_secret(handle); + store = handle->ffi->secring; + } else { + FFI_LOG(handle->ffi, "must specify public or secret key for export"); + return RNP_ERROR_BAD_PARAMETERS; + } + bool export_subs = extract_flag(flags, RNP_KEY_EXPORT_SUBKEYS); + // check for any unrecognized flags + if (flags) { + FFI_LOG(handle->ffi, "unrecognized flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + // make sure we found our key + if (!key) { + FFI_LOG(handle->ffi, "no suitable key found"); + return RNP_ERROR_NO_SUITABLE_KEY; + } + // only PGP packets supported for now + if (key->format != PGP_KEY_STORE_GPG && key->format != PGP_KEY_STORE_KBX) { + return RNP_ERROR_NOT_IMPLEMENTED; + } + if (armored) { + auto msgtype = key->is_secret() ? PGP_ARMORED_SECRET_KEY : PGP_ARMORED_PUBLIC_KEY; + rnp_result_t res = init_armored_dst(&armordst, &output->dst, msgtype); + if (res) { + return res; + } + dst = &armordst; + } + // write + if (key->is_primary()) { + // primary key, write just the primary or primary and all subkeys + key->write_xfer(*dst, export_subs ? store : NULL); + if (dst->werr) { + return RNP_ERROR_WRITE; + } + } else { + // subkeys flag is only valid for primary + if (export_subs) { + FFI_LOG(handle->ffi, "export with subkeys requested but key is not primary"); + return RNP_ERROR_BAD_PARAMETERS; + } + // subkey, write the primary + this subkey only + pgp_key_t *primary = rnp_key_store_get_primary_key(store, key); + if (!primary) { + // shouldn't happen + return RNP_ERROR_GENERIC; + } + primary->write_xfer(*dst); + if (dst->werr) { + return RNP_ERROR_WRITE; + } + key->write_xfer(*dst); + if (dst->werr) { + return RNP_ERROR_WRITE; + } + } + if (armored) { + dst_finish(&armordst); + dst_close(&armordst, false); + } + output->keep = true; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_export_autocrypt(rnp_key_handle_t key, + rnp_key_handle_t subkey, + const char * uid, + rnp_output_t output, + uint32_t flags) +try { + if (!key || !output) { + return RNP_ERROR_NULL_POINTER; + } + bool base64 = extract_flag(flags, RNP_KEY_EXPORT_BASE64); + if (flags) { + FFI_LOG(key->ffi, "Unknown flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* Get the primary key */ + pgp_key_t *primary = get_key_prefer_public(key); + if (!primary || !primary->is_primary() || !primary->usable_for(PGP_OP_VERIFY)) { + FFI_LOG(key->ffi, "No valid signing primary key"); + return RNP_ERROR_BAD_PARAMETERS; + } + /* Get encrypting subkey */ + pgp_key_t *sub = + subkey ? get_key_prefer_public(subkey) : + find_suitable_key(PGP_OP_ENCRYPT, primary, &key->ffi->key_provider, true); + if (!sub || sub->is_primary() || !sub->usable_for(PGP_OP_ENCRYPT)) { + FFI_LOG(key->ffi, "No encrypting subkey"); + return RNP_ERROR_KEY_NOT_FOUND; + } + /* Get userid */ + size_t uididx = primary->uid_count(); + if (uid) { + for (size_t idx = 0; idx < primary->uid_count(); idx++) { + if (primary->get_uid(idx).str == uid) { + uididx = idx; + break; + } + } + } else { + if (primary->uid_count() > 1) { + FFI_LOG(key->ffi, "Ambiguous userid"); + return RNP_ERROR_BAD_PARAMETERS; + } + uididx = 0; + } + if (uididx >= primary->uid_count()) { + FFI_LOG(key->ffi, "Userid not found"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* Check whether base64 is requested */ + bool res = false; + if (base64) { + rnp::ArmoredDest armor(output->dst, PGP_ARMORED_BASE64); + res = primary->write_autocrypt(armor.dst(), *sub, uididx); + } else { + res = primary->write_autocrypt(output->dst, *sub, uididx); + } + return res ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; +} +FFI_GUARD + +static pgp_key_t * +rnp_key_get_revoker(rnp_key_handle_t key) +{ + pgp_key_t *exkey = get_key_prefer_public(key); + if (!exkey) { + return NULL; + } + if (exkey->is_subkey()) { + return rnp_key_store_get_primary_key(key->ffi->secring, exkey); + } + // TODO: search through revocation key subpackets as well + return get_key_require_secret(key); +} + +static rnp_result_t +rnp_key_get_revocation(rnp_ffi_t ffi, + pgp_key_t * key, + pgp_key_t * revoker, + const char * hash, + const char * code, + const char * reason, + pgp_signature_t &sig) +{ + if (!hash) { + hash = DEFAULT_HASH_ALG; + } + pgp_hash_alg_t halg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &halg)) { + FFI_LOG(ffi, "Unknown hash algorithm: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_revoke_t revinfo = {}; + if (code && !str_to_revocation_type(code, &revinfo.code)) { + FFI_LOG(ffi, "Wrong revocation code: %s", code); + return RNP_ERROR_BAD_PARAMETERS; + } + if (revinfo.code > PGP_REVOCATION_RETIRED) { + FFI_LOG(ffi, "Wrong key revocation code: %d", (int) revinfo.code); + return RNP_ERROR_BAD_PARAMETERS; + } + if (reason) { + try { + revinfo.reason = reason; + } catch (const std::exception &e) { + FFI_LOG(ffi, "%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + /* unlock the secret key if needed */ + rnp::KeyLocker revlock(*revoker); + if (revoker->is_locked() && !revoker->unlock(ffi->pass_provider)) { + FFI_LOG(ffi, "Failed to unlock secret key"); + return RNP_ERROR_BAD_PASSWORD; + } + try { + revoker->gen_revocation(revinfo, halg, key->pkt(), sig, ffi->context); + } catch (const std::exception &e) { + FFI_LOG(ffi, "Failed to generate revocation signature: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_export_revocation(rnp_key_handle_t key, + rnp_output_t output, + uint32_t flags, + const char * hash, + const char * code, + const char * reason) +try { + if (!key || !key->ffi || !output) { + return RNP_ERROR_NULL_POINTER; + } + bool need_armor = extract_flag(flags, RNP_KEY_EXPORT_ARMORED); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t *exkey = get_key_prefer_public(key); + if (!exkey || !exkey->is_primary()) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *revoker = rnp_key_get_revoker(key); + if (!revoker) { + FFI_LOG(key->ffi, "Revoker secret key not found"); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_signature_t sig; + rnp_result_t ret = + rnp_key_get_revocation(key->ffi, exkey, revoker, hash, code, reason, sig); + if (ret) { + return ret; + } + + if (need_armor) { + rnp::ArmoredDest armor(output->dst, PGP_ARMORED_PUBLIC_KEY); + sig.write(armor.dst()); + ret = armor.werr(); + dst_flush(&armor.dst()); + } else { + sig.write(output->dst); + ret = output->dst.werr; + dst_flush(&output->dst); + } + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_key_revoke( + rnp_key_handle_t key, uint32_t flags, const char *hash, const char *code, const char *reason) +try { + if (!key || !key->ffi) { + return RNP_ERROR_NULL_POINTER; + } + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t *exkey = get_key_prefer_public(key); + if (!exkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *revoker = rnp_key_get_revoker(key); + if (!revoker) { + FFI_LOG(key->ffi, "Revoker secret key not found"); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_signature_t sig; + rnp_result_t ret = + rnp_key_get_revocation(key->ffi, exkey, revoker, hash, code, reason, sig); + if (ret) { + return ret; + } + pgp_sig_import_status_t pub_status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY; + pgp_sig_import_status_t sec_status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY; + if (key->pub) { + pub_status = rnp_key_store_import_key_signature(key->ffi->pubring, key->pub, &sig); + } + if (key->sec) { + sec_status = rnp_key_store_import_key_signature(key->ffi->secring, key->sec, &sig); + } + + if ((pub_status == PGP_SIG_IMPORT_STATUS_UNKNOWN) || + (sec_status == PGP_SIG_IMPORT_STATUS_UNKNOWN)) { + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_25519_bits_tweaked(rnp_key_handle_t key, bool *result) +try { + if (!key || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *seckey = get_key_require_secret(key); + if (!seckey || seckey->is_locked() || (seckey->alg() != PGP_PKA_ECDH) || + (seckey->curve() != PGP_CURVE_25519)) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = x25519_bits_tweaked(seckey->material().ec); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_25519_bits_tweak(rnp_key_handle_t key) +try { + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *seckey = get_key_require_secret(key); + if (!seckey || seckey->is_protected() || (seckey->alg() != PGP_PKA_ECDH) || + (seckey->curve() != PGP_CURVE_25519)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!x25519_tweak_bits(seckey->pkt().material.ec)) { + FFI_LOG(key->ffi, "Failed to tweak 25519 key bits."); + return RNP_ERROR_BAD_STATE; + } + if (!seckey->write_sec_rawpkt(seckey->pkt(), "", key->ffi->context)) { + FFI_LOG(key->ffi, "Failed to update rawpkt."); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_remove(rnp_key_handle_t key, uint32_t flags) +try { + if (!key || !key->ffi) { + return RNP_ERROR_NULL_POINTER; + } + bool pub = extract_flag(flags, RNP_KEY_REMOVE_PUBLIC); + bool sec = extract_flag(flags, RNP_KEY_REMOVE_SECRET); + bool sub = extract_flag(flags, RNP_KEY_REMOVE_SUBKEYS); + if (flags) { + FFI_LOG(key->ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pub && !sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (sub && get_key_prefer_public(key)->is_subkey()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (pub) { + if (!key->ffi->pubring || !key->pub) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_remove_key(key->ffi->pubring, key->pub, sub)) { + return RNP_ERROR_KEY_NOT_FOUND; + } + key->pub = NULL; + } + if (sec) { + if (!key->ffi->secring || !key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_remove_key(key->ffi->secring, key->sec, sub)) { + return RNP_ERROR_KEY_NOT_FOUND; + } + key->sec = NULL; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static void +report_signature_removal(rnp_ffi_t ffi, + const pgp_key_t & key, + rnp_key_signatures_cb sigcb, + void * app_ctx, + pgp_subsig_t & keysig, + bool & remove) +{ + if (!sigcb) { + return; + } + rnp_signature_handle_t sig = (rnp_signature_handle_t) calloc(1, sizeof(*sig)); + if (!sig) { + FFI_LOG(ffi, "Signature handle allocation failed."); + return; + } + sig->ffi = ffi; + sig->key = &key; + sig->sig = &keysig; + sig->own_sig = false; + uint32_t action = remove ? RNP_KEY_SIGNATURE_REMOVE : RNP_KEY_SIGNATURE_KEEP; + sigcb(ffi, app_ctx, sig, &action); + switch (action) { + case RNP_KEY_SIGNATURE_REMOVE: + remove = true; + break; + case RNP_KEY_SIGNATURE_KEEP: + remove = false; + break; + default: + FFI_LOG(ffi, "Invalid signature removal action: %" PRIu32, action); + break; + } + rnp_signature_handle_destroy(sig); +} + +static bool +signature_needs_removal(rnp_ffi_t ffi, const pgp_key_t &key, pgp_subsig_t &sig, uint32_t flags) +{ + /* quick check for non-self signatures */ + bool nonself = flags & RNP_KEY_SIGNATURE_NON_SELF_SIG; + if (nonself && key.is_primary() && !key.is_signer(sig)) { + return true; + } + if (nonself && key.is_subkey()) { + pgp_key_t *primary = rnp_key_store_get_primary_key(ffi->pubring, &key); + if (primary && !primary->is_signer(sig)) { + return true; + } + } + /* unknown signer */ + pgp_key_t *signer = pgp_sig_get_signer(sig, ffi->pubring, &ffi->key_provider); + if (!signer && (flags & RNP_KEY_SIGNATURE_UNKNOWN_KEY)) { + return true; + } + /* validate signature if didn't */ + if (signer && !sig.validated()) { + signer->validate_sig(key, sig, ffi->context); + } + /* we cannot check for invalid/expired if sig was not validated */ + if (!sig.validated()) { + return false; + } + if ((flags & RNP_KEY_SIGNATURE_INVALID) && !sig.validity.valid) { + return true; + } + return false; +} + +static void +remove_key_signatures(rnp_ffi_t ffi, + pgp_key_t & pub, + pgp_key_t * sec, + uint32_t flags, + rnp_key_signatures_cb sigcb, + void * app_ctx) +{ + std::vector<pgp_sig_id_t> sigs; + + for (size_t idx = 0; idx < pub.sig_count(); idx++) { + pgp_subsig_t &sig = pub.get_sig(idx); + bool remove = signature_needs_removal(ffi, pub, sig, flags); + report_signature_removal(ffi, pub, sigcb, app_ctx, sig, remove); + if (remove) { + sigs.push_back(sig.sigid); + } + } + size_t deleted = pub.del_sigs(sigs); + if (deleted != sigs.size()) { + FFI_LOG(ffi, "Invalid deleted sigs count: %zu instead of %zu.", deleted, sigs.size()); + } + /* delete from the secret key if any */ + if (sec && (sec != &pub)) { + sec->del_sigs(sigs); + } +} + +rnp_result_t +rnp_key_remove_signatures(rnp_key_handle_t handle, + uint32_t flags, + rnp_key_signatures_cb sigcb, + void * app_ctx) +try { + if (!handle) { + return RNP_ERROR_NULL_POINTER; + } + if (!flags && !sigcb) { + return RNP_ERROR_BAD_PARAMETERS; + } + uint32_t origflags = flags; + extract_flag(flags, + RNP_KEY_SIGNATURE_INVALID | RNP_KEY_SIGNATURE_NON_SELF_SIG | + RNP_KEY_SIGNATURE_UNKNOWN_KEY); + if (flags) { + FFI_LOG(handle->ffi, "Invalid flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + flags = origflags; + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* process key itself */ + pgp_key_t *sec = get_key_require_secret(handle); + remove_key_signatures(handle->ffi, *key, sec, flags, sigcb, app_ctx); + + /* process subkeys */ + for (size_t idx = 0; key->is_primary() && (idx < key->subkey_count()); idx++) { + pgp_key_t *sub = pgp_key_get_subkey(key, handle->ffi->pubring, idx); + if (!sub) { + FFI_LOG(handle->ffi, "Failed to get subkey at idx %zu.", idx); + continue; + } + pgp_key_t *subsec = rnp_key_store_get_key_by_fpr(handle->ffi->secring, sub->fp()); + remove_key_signatures(handle->ffi, *sub, subsec, flags, sigcb, app_ctx); + } + /* revalidate key/subkey */ + key->revalidate(*handle->ffi->pubring); + if (sec) { + sec->revalidate(*handle->ffi->secring); + } + return RNP_SUCCESS; +} +FFI_GUARD + +static bool +pk_alg_allows_custom_curve(pgp_pubkey_alg_t pkalg) +{ + switch (pkalg) { + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_SM2: + return true; + default: + return false; + } +} + +static bool +parse_preferences(json_object *jso, pgp_user_prefs_t &prefs) +{ + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"hashes", json_type_array}, + {"ciphers", json_type_array}, + {"compression", json_type_array}, + {"key server", json_type_string}}; + + for (size_t iprop = 0; iprop < ARRAY_SIZE(properties); iprop++) { + json_object *value = NULL; + const char * key = properties[iprop].key; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + + if (!json_object_is_type(value, properties[iprop].type)) { + return false; + } + try { + if (rnp::str_case_eq(key, "hashes")) { + int length = json_object_array_length(value); + for (int i = 0; i < length; i++) { + json_object *item = json_object_array_get_idx(value, i); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(json_object_get_string(item), &hash_alg)) { + return false; + } + prefs.add_hash_alg(hash_alg); + } + } else if (rnp::str_case_eq(key, "ciphers")) { + int length = json_object_array_length(value); + for (int i = 0; i < length; i++) { + json_object *item = json_object_array_get_idx(value, i); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN; + if (!str_to_cipher(json_object_get_string(item), &symm_alg)) { + return false; + } + prefs.add_symm_alg(symm_alg); + } + } else if (rnp::str_case_eq(key, "compression")) { + int length = json_object_array_length(value); + for (int i = 0; i < length; i++) { + json_object *item = json_object_array_get_idx(value, i); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + pgp_compression_type_t z_alg = PGP_C_UNKNOWN; + if (!str_to_compression_alg(json_object_get_string(item), &z_alg)) { + return false; + } + prefs.add_z_alg(z_alg); + } + } else if (rnp::str_case_eq(key, "key server")) { + prefs.key_server = json_object_get_string(value); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return true; +} + +static bool +parse_keygen_crypto(json_object *jso, rnp_keygen_crypto_params_t &crypto) +{ + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"type", json_type_string}, + {"curve", json_type_string}, + {"length", json_type_int}, + {"hash", json_type_string}}; + + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i].key; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + + if (!json_object_is_type(value, properties[i].type)) { + return false; + } + // TODO: make sure there are no duplicate keys in the JSON + if (rnp::str_case_eq(key, "type")) { + if (!str_to_pubkey_alg(json_object_get_string(value), &crypto.key_alg)) { + return false; + } + } else if (rnp::str_case_eq(key, "length")) { + int length = json_object_get_int(value); + switch (crypto.key_alg) { + case PGP_PKA_RSA: + crypto.rsa.modulus_bit_len = length; + break; + case PGP_PKA_DSA: + crypto.dsa.p_bitlen = length; + break; + case PGP_PKA_ELGAMAL: + crypto.elgamal.key_bitlen = length; + break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "curve")) { + if (!pk_alg_allows_custom_curve(crypto.key_alg)) { + return false; + } + if (!curve_str_to_type(json_object_get_string(value), &crypto.ecc.curve)) { + return false; + } + } else if (rnp::str_case_eq(key, "hash")) { + if (!str_to_hash_alg(json_object_get_string(value), &crypto.hash_alg)) { + return false; + } + } else { + // shouldn't happen + return false; + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return true; +} + +static bool +parse_protection(json_object *jso, rnp_key_protection_params_t &protection) +{ + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"cipher", json_type_string}, + {"mode", json_type_string}, + {"iterations", json_type_int}, + {"hash", json_type_string}}; + + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i].key; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + + if (!json_object_is_type(value, properties[i].type)) { + return false; + } + // TODO: make sure there are no duplicate keys in the JSON + if (rnp::str_case_eq(key, "cipher")) { + if (!str_to_cipher(json_object_get_string(value), &protection.symm_alg)) { + return false; + } + } else if (rnp::str_case_eq(key, "mode")) { + if (!str_to_cipher_mode(json_object_get_string(value), &protection.cipher_mode)) { + return false; + } + } else if (rnp::str_case_eq(key, "iterations")) { + protection.iterations = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "hash")) { + if (!str_to_hash_alg(json_object_get_string(value), &protection.hash_alg)) { + return false; + } + } else { + // shouldn't happen + return false; + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return true; +} + +static bool +parse_keygen_primary(json_object * jso, + rnp_keygen_primary_desc_t & desc, + rnp_key_protection_params_t &prot) +{ + static const char *properties[] = { + "userid", "usage", "expiration", "preferences", "protection"}; + auto &cert = desc.cert; + + if (!parse_keygen_crypto(jso, desc.crypto)) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i]; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + if (rnp::str_case_eq(key, "userid")) { + if (!json_object_is_type(value, json_type_string)) { + return false; + } + auto uid = json_object_get_string(value); + if (strlen(uid) > MAX_ID_LENGTH) { + return false; + } + cert.userid = json_object_get_string(value); + } else if (rnp::str_case_eq(key, "usage")) { + switch (json_object_get_type(value)) { + case json_type_array: { + int length = json_object_array_length(value); + for (int j = 0; j < length; j++) { + json_object *item = json_object_array_get_idx(value, j); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + uint8_t flag = 0; + if (!str_to_key_flag(json_object_get_string(item), &flag)) { + return false; + } + // check for duplicate + if (cert.key_flags & flag) { + return false; + } + cert.key_flags |= flag; + } + } break; + case json_type_string: { + if (!str_to_key_flag(json_object_get_string(value), &cert.key_flags)) { + return false; + } + } break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "expiration")) { + if (!json_object_is_type(value, json_type_int)) { + return false; + } + cert.key_expiration = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "preferences")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } + if (!parse_preferences(value, cert.prefs)) { + return false; + } + if (json_object_object_length(value)) { + return false; + } + } else if (rnp::str_case_eq(key, "protection")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } + if (!parse_protection(value, prot)) { + return false; + } + if (json_object_object_length(value)) { + return false; + } + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return !json_object_object_length(jso); +} + +static bool +parse_keygen_sub(json_object * jso, + rnp_keygen_subkey_desc_t & desc, + rnp_key_protection_params_t &prot) +{ + static const char *properties[] = {"usage", "expiration", "protection"}; + auto & binding = desc.binding; + + if (!parse_keygen_crypto(jso, desc.crypto)) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i]; + + if (!json_object_object_get_ex(jso, key, &value)) { + continue; + } + if (rnp::str_case_eq(key, "usage")) { + switch (json_object_get_type(value)) { + case json_type_array: { + int length = json_object_array_length(value); + for (int j = 0; j < length; j++) { + json_object *item = json_object_array_get_idx(value, j); + if (!json_object_is_type(item, json_type_string)) { + return false; + } + uint8_t flag = 0; + if (!str_to_key_flag(json_object_get_string(item), &flag)) { + return false; + } + if (binding.key_flags & flag) { + return false; + } + binding.key_flags |= flag; + } + } break; + case json_type_string: { + if (!str_to_key_flag(json_object_get_string(value), &binding.key_flags)) { + return false; + } + } break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "expiration")) { + if (!json_object_is_type(value, json_type_int)) { + return false; + } + binding.key_expiration = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "protection")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } + if (!parse_protection(value, prot)) { + return false; + } + if (json_object_object_length(value)) { + return false; + } + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } + return !json_object_object_length(jso); +} + +static bool +gen_json_grips(char **result, const pgp_key_t *primary, const pgp_key_t *sub) +{ + if (!result) { + return true; + } + + json_object *jso = json_object_new_object(); + if (!jso) { + return false; + } + rnp::JSONObject jsowrap(jso); + + char grip[PGP_KEY_GRIP_SIZE * 2 + 1]; + if (primary) { + json_object *jsoprimary = json_object_new_object(); + if (!jsoprimary) { + return false; + } + json_object_object_add(jso, "primary", jsoprimary); + if (!rnp::hex_encode( + primary->grip().data(), primary->grip().size(), grip, sizeof(grip))) { + return false; + } + json_object *jsogrip = json_object_new_string(grip); + if (!jsogrip) { + return false; + } + json_object_object_add(jsoprimary, "grip", jsogrip); + } + if (sub) { + json_object *jsosub = json_object_new_object(); + if (!jsosub) { + return false; + } + json_object_object_add(jso, "sub", jsosub); + if (!rnp::hex_encode(sub->grip().data(), sub->grip().size(), grip, sizeof(grip))) { + return false; + } + json_object *jsogrip = json_object_new_string(grip); + if (!jsogrip) { + return false; + } + json_object_object_add(jsosub, "grip", jsogrip); + } + *result = strdup(json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY)); + return *result; +} + +static rnp_result_t +gen_json_primary_key(rnp_ffi_t ffi, + json_object * jsoparams, + rnp_key_protection_params_t &prot, + pgp_fingerprint_t & fp, + bool protect) +{ + rnp_keygen_primary_desc_t desc = {}; + // desc.crypto is a union + // so at least Clang 12 on Windows zero-initializes the first union member only + // keeping the "larger" member partially unintialized + desc.crypto.dsa.q_bitlen = 0; + + desc.cert.key_expiration = DEFAULT_KEY_EXPIRATION; + if (!parse_keygen_primary(jsoparams, desc, prot)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t pub; + pgp_key_t sec; + desc.crypto.ctx = &ffi->context; + if (!pgp_generate_primary_key(desc, true, sec, pub, ffi->secring->format)) { + return RNP_ERROR_GENERIC; + } + if (!rnp_key_store_add_key(ffi->pubring, &pub)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + /* encrypt secret key if specified */ + if (protect && prot.symm_alg && !sec.protect(prot, ffi->pass_provider, ffi->context)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_add_key(ffi->secring, &sec)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + fp = pub.fp(); + return RNP_SUCCESS; +} + +static rnp_result_t +gen_json_subkey(rnp_ffi_t ffi, + json_object * jsoparams, + pgp_key_t & prim_pub, + pgp_key_t & prim_sec, + pgp_fingerprint_t &fp) +{ + rnp_keygen_subkey_desc_t desc = {}; + rnp_key_protection_params_t prot = {}; + + desc.binding.key_expiration = DEFAULT_KEY_EXPIRATION; + if (!parse_keygen_sub(jsoparams, desc, prot)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!desc.binding.key_flags) { + /* Generate encrypt-only subkeys by default */ + desc.binding.key_flags = PGP_KF_ENCRYPT; + } + pgp_key_t pub; + pgp_key_t sec; + desc.crypto.ctx = &ffi->context; + if (!pgp_generate_subkey(desc, + true, + prim_sec, + prim_pub, + sec, + pub, + ffi->pass_provider, + ffi->secring->format)) { + return RNP_ERROR_GENERIC; + } + if (!rnp_key_store_add_key(ffi->pubring, &pub)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + /* encrypt subkey if specified */ + if (prot.symm_alg && !sec.protect(prot, ffi->pass_provider, ffi->context)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!rnp_key_store_add_key(ffi->secring, &sec)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + fp = pub.fp(); + return RNP_SUCCESS; +} + +rnp_result_t +rnp_generate_key_json(rnp_ffi_t ffi, const char *json, char **results) +try { + // checks + if (!ffi || !ffi->secring || !json) { + return RNP_ERROR_NULL_POINTER; + } + + // parse the JSON + json_tokener_error error; + json_object * jso = json_tokener_parse_verbose(json, &error); + if (!jso) { + // syntax error or some other issue + FFI_LOG(ffi, "Invalid JSON: %s", json_tokener_error_desc(error)); + return RNP_ERROR_BAD_FORMAT; + } + rnp::JSONObject jsowrap(jso); + + // locate the appropriate sections + rnp_result_t ret = RNP_ERROR_GENERIC; + json_object *jsoprimary = NULL; + json_object *jsosub = NULL; + { + json_object_object_foreach(jso, key, value) + { + json_object **dest = NULL; + + if (rnp::str_case_eq(key, "primary")) { + dest = &jsoprimary; + } else if (rnp::str_case_eq(key, "sub")) { + dest = &jsosub; + } else { + // unrecognized key in the object + FFI_LOG(ffi, "Unexpected key in JSON: %s", key); + return RNP_ERROR_BAD_PARAMETERS; + } + + // duplicate "primary"/"sub" + if (*dest) { + return RNP_ERROR_BAD_PARAMETERS; + } + *dest = value; + } + } + + if (!jsoprimary && !jsosub) { + return RNP_ERROR_BAD_PARAMETERS; + } + + // generate primary key + pgp_key_t * prim_pub = NULL; + pgp_key_t * prim_sec = NULL; + rnp_key_protection_params_t prim_prot = {}; + pgp_fingerprint_t fp; + if (jsoprimary) { + ret = gen_json_primary_key(ffi, jsoprimary, prim_prot, fp, !jsosub); + if (ret) { + return ret; + } + prim_pub = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); + if (!jsosub) { + if (!gen_json_grips(results, prim_pub, NULL)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; + } + prim_sec = rnp_key_store_get_key_by_fpr(ffi->secring, fp); + } else { + /* generate subkey only - find primary key via JSON params */ + json_object *jsoparent = NULL; + if (!json_object_object_get_ex(jsosub, "primary", &jsoparent) || + json_object_object_length(jsoparent) != 1) { + return RNP_ERROR_BAD_PARAMETERS; + } + const char *identifier_type = NULL; + const char *identifier = NULL; + json_object_object_foreach(jsoparent, key, value) + { + if (!json_object_is_type(value, json_type_string)) { + return RNP_ERROR_BAD_PARAMETERS; + } + identifier_type = key; + identifier = json_object_get_string(value); + } + if (!identifier_type || !identifier) { + return RNP_ERROR_BAD_STATE; + } + + pgp_key_search_t locator; + rnp_result_t tmpret = str_to_locator(ffi, &locator, identifier_type, identifier); + if (tmpret) { + return tmpret; + } + + prim_pub = rnp_key_store_search(ffi->pubring, &locator, NULL); + prim_sec = rnp_key_store_search(ffi->secring, &locator, NULL); + if (!prim_sec || !prim_pub) { + return RNP_ERROR_KEY_NOT_FOUND; + } + json_object_object_del(jsosub, "primary"); + } + + /* Generate subkey */ + ret = gen_json_subkey(ffi, jsosub, *prim_pub, *prim_sec, fp); + if (ret) { + if (jsoprimary) { + /* do not leave generated primary key in keyring */ + rnp_key_store_remove_key(ffi->pubring, prim_pub, false); + rnp_key_store_remove_key(ffi->secring, prim_sec, false); + } + return ret; + } + /* Protect the primary key now */ + if (prim_prot.symm_alg && + !prim_sec->protect(prim_prot, ffi->pass_provider, ffi->context)) { + rnp_key_store_remove_key(ffi->pubring, prim_pub, true); + rnp_key_store_remove_key(ffi->secring, prim_sec, true); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t *sub_pub = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); + bool res = gen_json_grips(results, jsoprimary ? prim_pub : NULL, sub_pub); + return res ? RNP_SUCCESS : RNP_ERROR_OUT_OF_MEMORY; +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_ex(rnp_ffi_t ffi, + const char * key_alg, + const char * sub_alg, + uint32_t key_bits, + uint32_t sub_bits, + const char * key_curve, + const char * sub_curve, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + rnp_op_generate_t op = NULL; + rnp_op_generate_t subop = NULL; + rnp_key_handle_t primary = NULL; + rnp_key_handle_t subkey = NULL; + rnp_result_t ret = RNP_ERROR_KEY_GENERATION; + + /* generate primary key */ + if ((ret = rnp_op_generate_create(&op, ffi, key_alg))) { + return ret; + } + if (key_bits && (ret = rnp_op_generate_set_bits(op, key_bits))) { + goto done; + } + if (key_curve && (ret = rnp_op_generate_set_curve(op, key_curve))) { + goto done; + } + if ((ret = rnp_op_generate_set_userid(op, userid))) { + goto done; + } + if ((ret = rnp_op_generate_add_usage(op, "sign"))) { + goto done; + } + if ((ret = rnp_op_generate_add_usage(op, "certify"))) { + goto done; + } + if ((ret = rnp_op_generate_execute(op))) { + goto done; + } + if ((ret = rnp_op_generate_get_key(op, &primary))) { + goto done; + } + /* generate subkey if requested */ + if (!sub_alg) { + goto done; + } + if ((ret = rnp_op_generate_subkey_create(&subop, ffi, primary, sub_alg))) { + goto done; + } + if (sub_bits && (ret = rnp_op_generate_set_bits(subop, sub_bits))) { + goto done; + } + if (sub_curve && (ret = rnp_op_generate_set_curve(subop, sub_curve))) { + goto done; + } + if (password && (ret = rnp_op_generate_set_protection_password(subop, password))) { + goto done; + } + if ((ret = rnp_op_generate_add_usage(subop, "encrypt"))) { + goto done; + } + if ((ret = rnp_op_generate_execute(subop))) { + goto done; + } + if ((ret = rnp_op_generate_get_key(subop, &subkey))) { + goto done; + } +done: + /* only now will protect the primary key - to not spend time on unlocking to sign + * subkey */ + if (!ret && password) { + ret = rnp_key_protect(primary, password, NULL, NULL, NULL, 0); + } + if (ret && primary) { + rnp_key_remove(primary, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET); + } + if (ret && subkey) { + rnp_key_remove(subkey, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET); + } + if (!ret && key) { + *key = primary; + } else { + rnp_key_handle_destroy(primary); + } + rnp_key_handle_destroy(subkey); + rnp_op_generate_destroy(op); + rnp_op_generate_destroy(subop); + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_rsa(rnp_ffi_t ffi, + uint32_t bits, + uint32_t subbits, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex(ffi, + RNP_ALGNAME_RSA, + subbits ? RNP_ALGNAME_RSA : NULL, + bits, + subbits, + NULL, + NULL, + userid, + password, + key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_dsa_eg(rnp_ffi_t ffi, + uint32_t bits, + uint32_t subbits, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex(ffi, + RNP_ALGNAME_DSA, + subbits ? RNP_ALGNAME_ELGAMAL : NULL, + bits, + subbits, + NULL, + NULL, + userid, + password, + key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_ec(rnp_ffi_t ffi, + const char * curve, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex( + ffi, RNP_ALGNAME_ECDSA, RNP_ALGNAME_ECDH, 0, 0, curve, curve, userid, password, key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_25519(rnp_ffi_t ffi, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex(ffi, + RNP_ALGNAME_EDDSA, + RNP_ALGNAME_ECDH, + 0, + 0, + NULL, + "Curve25519", + userid, + password, + key); +} +FFI_GUARD + +rnp_result_t +rnp_generate_key_sm2(rnp_ffi_t ffi, + const char * userid, + const char * password, + rnp_key_handle_t *key) +try { + return rnp_generate_key_ex( + ffi, RNP_ALGNAME_SM2, RNP_ALGNAME_SM2, 0, 0, NULL, NULL, userid, password, key); +} +FFI_GUARD + +static pgp_key_flags_t +default_key_flags(pgp_pubkey_alg_t alg, bool subkey) +{ + switch (alg) { + case PGP_PKA_RSA: + return subkey ? PGP_KF_ENCRYPT : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); + case PGP_PKA_DSA: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + return subkey ? PGP_KF_SIGN : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); + case PGP_PKA_SM2: + return subkey ? PGP_KF_ENCRYPT : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY); + case PGP_PKA_ECDH: + case PGP_PKA_ELGAMAL: + return PGP_KF_ENCRYPT; + default: + return PGP_KF_NONE; + } +} + +rnp_result_t +rnp_op_generate_create(rnp_op_generate_t *op, rnp_ffi_t ffi, const char *alg) +try { + pgp_pubkey_alg_t key_alg = PGP_PKA_NOTHING; + + if (!op || !ffi || !alg) { + return RNP_ERROR_NULL_POINTER; + } + + if (!ffi->pubring || !ffi->secring) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!str_to_pubkey_alg(alg, &key_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!(pgp_pk_alg_capabilities(key_alg) & PGP_KF_SIGN)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *op = new rnp_op_generate_st(); + (*op)->ffi = ffi; + (*op)->primary = true; + (*op)->crypto.key_alg = key_alg; + (*op)->crypto.ctx = &ffi->context; + (*op)->cert.key_flags = default_key_flags(key_alg, false); + (*op)->cert.key_expiration = DEFAULT_KEY_EXPIRATION; + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_subkey_create(rnp_op_generate_t *op, + rnp_ffi_t ffi, + rnp_key_handle_t primary, + const char * alg) +try { + if (!op || !ffi || !alg || !primary) { + return RNP_ERROR_NULL_POINTER; + } + + if (!ffi->pubring || !ffi->secring) { + return RNP_ERROR_BAD_PARAMETERS; + } + + /* TODO: should we do these checks here or may leave it up till generate call? */ + if (!primary->sec || !primary->sec->usable_for(PGP_OP_ADD_SUBKEY)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_pubkey_alg_t key_alg = PGP_PKA_NOTHING; + if (!str_to_pubkey_alg(alg, &key_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *op = new rnp_op_generate_st(); + (*op)->ffi = ffi; + (*op)->primary = false; + (*op)->crypto.key_alg = key_alg; + (*op)->crypto.ctx = &ffi->context; + (*op)->binding.key_flags = default_key_flags(key_alg, true); + (*op)->binding.key_expiration = DEFAULT_KEY_EXPIRATION; + (*op)->primary_sec = primary->sec; + (*op)->primary_pub = primary->pub; + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_bits(rnp_op_generate_t op, uint32_t bits) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + + switch (op->crypto.key_alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + op->crypto.rsa.modulus_bit_len = bits; + break; + case PGP_PKA_ELGAMAL: + op->crypto.elgamal.key_bitlen = bits; + break; + case PGP_PKA_DSA: + op->crypto.dsa.p_bitlen = bits; + break; + default: + return RNP_ERROR_BAD_PARAMETERS; + } + + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_hash(rnp_op_generate_t op, const char *hash) +try { + if (!op || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_hash_alg(hash, &op->crypto.hash_alg)) { + FFI_LOG(op->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_dsa_qbits(rnp_op_generate_t op, uint32_t qbits) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (op->crypto.key_alg != PGP_PKA_DSA) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->crypto.dsa.q_bitlen = qbits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_curve(rnp_op_generate_t op, const char *curve) +try { + if (!op || !curve) { + return RNP_ERROR_NULL_POINTER; + } + if (!pk_alg_allows_custom_curve(op->crypto.key_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!curve_str_to_type(curve, &op->crypto.ecc.curve)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_password(rnp_op_generate_t op, const char *password) +try { + if (!op || !password) { + return RNP_ERROR_NULL_POINTER; + } + op->password.assign(password, password + strlen(password) + 1); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_request_password(rnp_op_generate_t op, bool request) +try { + if (!op || !request) { + return RNP_ERROR_NULL_POINTER; + } + op->request_password = request; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_cipher(rnp_op_generate_t op, const char *cipher) +try { + if (!op || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_cipher(cipher, &op->protection.symm_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_hash(rnp_op_generate_t op, const char *hash) +try { + if (!op || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_hash_alg(hash, &op->protection.hash_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_mode(rnp_op_generate_t op, const char *mode) +try { + if (!op || !mode) { + return RNP_ERROR_NULL_POINTER; + } + if (!str_to_cipher_mode(mode, &op->protection.cipher_mode)) { + return RNP_ERROR_BAD_PARAMETERS; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_protection_iterations(rnp_op_generate_t op, uint32_t iterations) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + op->protection.iterations = iterations; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_usage(rnp_op_generate_t op, const char *usage) +try { + if (!op || !usage) { + return RNP_ERROR_NULL_POINTER; + } + uint8_t flag = 0; + if (!str_to_key_flag(usage, &flag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!(pgp_pk_alg_capabilities(op->crypto.key_alg) & flag)) { + return RNP_ERROR_NOT_SUPPORTED; + } + if (op->primary) { + op->cert.key_flags |= flag; + } else { + op->binding.key_flags |= flag; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_usage(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (op->primary) { + op->cert.key_flags = 0; + } else { + op->binding.key_flags = 0; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_userid(rnp_op_generate_t op, const char *userid) +try { + if (!op || !userid) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (strlen(userid) > MAX_ID_LENGTH) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.userid = userid; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_expiration(rnp_op_generate_t op, uint32_t expiration) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (op->primary) { + op->cert.key_expiration = expiration; + } else { + op->binding.key_expiration = expiration; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_pref_hashes(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.set_hash_algs({}); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_pref_hash(rnp_op_generate_t op, const char *hash) +try { + if (!op || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &hash_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.add_hash_alg(hash_alg); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_pref_compression(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.set_z_algs({}); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_pref_compression(rnp_op_generate_t op, const char *compression) +try { + if (!op || !compression) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_compression_type_t z_alg = PGP_C_UNKNOWN; + if (!str_to_compression_alg(compression, &z_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.add_z_alg(z_alg); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_clear_pref_ciphers(rnp_op_generate_t op) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.set_symm_algs({}); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_add_pref_cipher(rnp_op_generate_t op, const char *cipher) +try { + if (!op || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN; + if (!str_to_cipher(cipher, &symm_alg)) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.add_symm_alg(symm_alg); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_set_pref_keyserver(rnp_op_generate_t op, const char *keyserver) +try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } + op->cert.prefs.key_server = keyserver ? keyserver : ""; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_execute(rnp_op_generate_t op) +try { + if (!op || !op->ffi) { + return RNP_ERROR_NULL_POINTER; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + pgp_key_t pub; + pgp_key_t sec; + pgp_password_provider_t prov; + + if (op->primary) { + rnp_keygen_primary_desc_t keygen = {}; + keygen.crypto = op->crypto; + keygen.cert = op->cert; + op->cert.prefs = {}; /* generate call will free prefs */ + + if (!pgp_generate_primary_key(keygen, true, sec, pub, op->ffi->secring->format)) { + return RNP_ERROR_KEY_GENERATION; + } + } else { + /* subkey generation */ + rnp_keygen_subkey_desc_t keygen = {}; + keygen.crypto = op->crypto; + keygen.binding = op->binding; + if (!pgp_generate_subkey(keygen, + true, + *op->primary_sec, + *op->primary_pub, + sec, + pub, + op->ffi->pass_provider, + op->ffi->secring->format)) { + return RNP_ERROR_KEY_GENERATION; + } + } + + /* add public key part to the keyring */ + if (!(op->gen_pub = rnp_key_store_add_key(op->ffi->pubring, &pub))) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + /* encrypt secret key if requested */ + if (!op->password.empty()) { + prov = {rnp_password_provider_string, (void *) op->password.data()}; + } else if (op->request_password) { + prov = {rnp_password_cb_bounce, op->ffi}; + } + if (prov.callback && !sec.protect(op->protection, prov, op->ffi->context)) { + FFI_LOG(op->ffi, "failed to encrypt the key"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + + /* add secret key to the keyring */ + if (!(op->gen_sec = rnp_key_store_add_key(op->ffi->secring, &sec))) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + ret = RNP_SUCCESS; +done: + op->password.clear(); + if (ret && op->gen_pub) { + rnp_key_store_remove_key(op->ffi->pubring, op->gen_pub, false); + op->gen_pub = NULL; + } + if (ret && op->gen_sec) { + rnp_key_store_remove_key(op->ffi->secring, op->gen_sec, false); + op->gen_sec = NULL; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_get_key(rnp_op_generate_t op, rnp_key_handle_t *handle) +try { + if (!op || !handle) { + return RNP_ERROR_NULL_POINTER; + } + if (!op->gen_sec || !op->gen_pub) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *handle = (rnp_key_handle_t) malloc(sizeof(**handle)); + if (!*handle) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*handle)->ffi = op->ffi; + (*handle)->pub = op->gen_pub; + (*handle)->sec = op->gen_sec; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_op_generate_destroy(rnp_op_generate_t op) +try { + delete op; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_handle_destroy(rnp_key_handle_t key) +try { + // This does not free key->key which is owned by the keyring + free(key); + return RNP_SUCCESS; +} +FFI_GUARD + +void +rnp_buffer_destroy(void *ptr) +{ + free(ptr); +} + +void +rnp_buffer_clear(void *ptr, size_t size) +{ + if (ptr) { + secure_clear(ptr, size); + } +} + +static pgp_key_t * +get_key_require_public(rnp_key_handle_t handle) +{ + if (!handle->pub && handle->sec) { + pgp_key_request_ctx_t request; + request.secret = false; + + // try fingerprint + request.search.type = PGP_KEY_SEARCH_FINGERPRINT; + request.search.by.fingerprint = handle->sec->fp(); + handle->pub = pgp_request_key(&handle->ffi->key_provider, &request); + if (handle->pub) { + return handle->pub; + } + + // try keyid + request.search.type = PGP_KEY_SEARCH_KEYID; + request.search.by.keyid = handle->sec->keyid(); + handle->pub = pgp_request_key(&handle->ffi->key_provider, &request); + } + return handle->pub; +} + +static pgp_key_t * +get_key_prefer_public(rnp_key_handle_t handle) +{ + pgp_key_t *pub = get_key_require_public(handle); + return pub ? pub : get_key_require_secret(handle); +} + +static pgp_key_t * +get_key_require_secret(rnp_key_handle_t handle) +{ + if (!handle->sec && handle->pub) { + pgp_key_request_ctx_t request; + request.secret = true; + + // try fingerprint + request.search.type = PGP_KEY_SEARCH_FINGERPRINT; + request.search.by.fingerprint = handle->pub->fp(); + handle->sec = pgp_request_key(&handle->ffi->key_provider, &request); + if (handle->sec) { + return handle->sec; + } + + // try keyid + request.search.type = PGP_KEY_SEARCH_KEYID; + request.search.by.keyid = handle->pub->keyid(); + handle->sec = pgp_request_key(&handle->ffi->key_provider, &request); + } + return handle->sec; +} + +static rnp_result_t +key_get_uid_at(pgp_key_t *key, size_t idx, char **uid) +{ + if (!key || !uid) { + return RNP_ERROR_NULL_POINTER; + } + if (idx >= key->uid_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + *uid = strdup(key->get_uid(idx).str.c_str()); + if (!*uid) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_add_uid(rnp_key_handle_t handle, + const char * uid, + const char * hash, + uint32_t expiration, + uint8_t key_flags, + bool primary) +try { + if (!handle || !uid) { + return RNP_ERROR_NULL_POINTER; + } + /* setup parameters */ + if (!hash) { + hash = DEFAULT_HASH_ALG; + } + pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN; + if (!str_to_hash_alg(hash, &hash_alg)) { + FFI_LOG(handle->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (strlen(uid) > MAX_ID_LENGTH) { + FFI_LOG(handle->ffi, "UserID too long"); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_selfsig_cert_info_t info; + info.userid = uid; + info.key_flags = key_flags; + info.key_expiration = expiration; + info.primary = primary; + + /* obtain and unlok secret key */ + pgp_key_t *secret_key = get_key_require_secret(handle); + if (!secret_key || !secret_key->usable_for(PGP_OP_ADD_USERID)) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + pgp_key_t *public_key = get_key_prefer_public(handle); + if (!public_key && secret_key->format == PGP_KEY_STORE_G10) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + rnp::KeyLocker seclock(*secret_key); + if (secret_key->is_locked() && + !secret_key->unlock(handle->ffi->pass_provider, PGP_OP_ADD_USERID)) { + return RNP_ERROR_BAD_PASSWORD; + } + /* add and certify userid */ + secret_key->add_uid_cert(info, hash_alg, handle->ffi->context, public_key); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_primary_uid(rnp_key_handle_t handle, char **uid) +try { + if (!handle || !uid) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (key->has_primary_uid()) { + return key_get_uid_at(key, key->get_primary_uid(), uid); + } + for (size_t i = 0; i < key->uid_count(); i++) { + if (!key->get_uid(i).valid) { + continue; + } + return key_get_uid_at(key, i, uid); + } + return RNP_ERROR_BAD_PARAMETERS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_uid_count(rnp_key_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + + *count = get_key_prefer_public(handle)->uid_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_uid_at(rnp_key_handle_t handle, size_t idx, char **uid) +try { + if (handle == NULL || uid == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_prefer_public(handle); + return key_get_uid_at(key, idx, uid); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_uid_handle_at(rnp_key_handle_t key, size_t idx, rnp_uid_handle_t *uid) +try { + if (!key || !uid) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *akey = get_key_prefer_public(key); + if (!akey) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (idx >= akey->uid_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *uid = (rnp_uid_handle_t) malloc(sizeof(**uid)); + if (!*uid) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + (*uid)->ffi = key->ffi; + (*uid)->key = akey; + (*uid)->idx = idx; + return RNP_SUCCESS; +} +FFI_GUARD + +static pgp_userid_t * +rnp_uid_handle_get_uid(rnp_uid_handle_t uid) +{ + if (!uid || !uid->key) { + return NULL; + } + return &uid->key->get_uid(uid->idx); +} + +rnp_result_t +rnp_uid_get_type(rnp_uid_handle_t uid, uint32_t *type) +try { + if (!type) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + switch (id->pkt.tag) { + case PGP_PKT_USER_ID: + *type = RNP_USER_ID; + return RNP_SUCCESS; + case PGP_PKT_USER_ATTR: + *type = RNP_USER_ATTR; + return RNP_SUCCESS; + default: + return RNP_ERROR_BAD_STATE; + } +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_data(rnp_uid_handle_t uid, void **data, size_t *size) +try { + if (!data || !size) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + *data = malloc(id->pkt.uid_len); + if (id->pkt.uid_len && !*data) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*data, id->pkt.uid, id->pkt.uid_len); + *size = id->pkt.uid_len; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_is_primary(rnp_uid_handle_t uid, bool *primary) +try { + if (!primary) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + *primary = uid->key->has_primary_uid() && (uid->key->get_primary_uid() == uid->idx); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_is_valid(rnp_uid_handle_t uid, bool *valid) +try { + if (!valid) { + return RNP_ERROR_NULL_POINTER; + } + pgp_userid_t *id = rnp_uid_handle_get_uid(uid); + if (!id) { + return RNP_ERROR_NULL_POINTER; + } + *valid = id->valid; + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_key_return_signature(rnp_ffi_t ffi, + pgp_key_t * key, + pgp_subsig_t * subsig, + rnp_signature_handle_t *sig) +{ + *sig = (rnp_signature_handle_t) calloc(1, sizeof(**sig)); + if (!*sig) { + return RNP_ERROR_OUT_OF_MEMORY; + } + (*sig)->ffi = ffi; + (*sig)->key = key; + (*sig)->sig = subsig; + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_get_signature_count(rnp_key_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *count = key->keysig_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_signature_at(rnp_key_handle_t handle, size_t idx, rnp_signature_handle_t *sig) +try { + if (!handle || !sig) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key || (idx >= key->keysig_count())) { + return RNP_ERROR_BAD_PARAMETERS; + } + return rnp_key_return_signature(handle->ffi, key, &key->get_keysig(idx), sig); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_revocation_signature(rnp_key_handle_t handle, rnp_signature_handle_t *sig) +try { + if (!handle || !sig) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->revoked()) { + *sig = NULL; + return RNP_SUCCESS; + } + if (!key->has_sig(key->revocation().sigid)) { + return RNP_ERROR_BAD_STATE; + } + return rnp_key_return_signature( + handle->ffi, key, &key->get_sig(key->revocation().sigid), sig); +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_signature_count(rnp_uid_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *count = handle->key->get_uid(handle->idx).sig_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_signature_at(rnp_uid_handle_t handle, size_t idx, rnp_signature_handle_t *sig) +try { + if (!handle || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_userid_t &uid = handle->key->get_uid(handle->idx); + if (idx >= uid.sig_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + const pgp_sig_id_t &sigid = uid.get_sig(idx); + if (!handle->key->has_sig(sigid)) { + return RNP_ERROR_BAD_STATE; + } + return rnp_key_return_signature( + handle->ffi, handle->key, &handle->key->get_sig(sigid), sig); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_type(rnp_signature_handle_t handle, char **type) +try { + if (!handle || !type) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + auto sigtype = id_str_pair::lookup(sig_type_map, handle->sig->sig.type()); + return ret_str_value(sigtype, type); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_alg(rnp_signature_handle_t handle, char **alg) +try { + if (!handle || !alg) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + return get_map_value(pubkey_alg_map, handle->sig->sig.palg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_hash_alg(rnp_signature_handle_t handle, char **alg) +try { + if (!handle || !alg) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + return get_map_value(hash_alg_map, handle->sig->sig.halg, alg); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_creation(rnp_signature_handle_t handle, uint32_t *create) +try { + if (!handle || !create) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + *create = handle->sig->sig.creation(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_expiration(rnp_signature_handle_t handle, uint32_t *expires) +try { + if (!handle || !expires) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + *expires = handle->sig->sig.expiration(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_keyid(rnp_signature_handle_t handle, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!handle->sig->sig.has_keyid()) { + *result = NULL; + return RNP_SUCCESS; + } + pgp_key_id_t keyid = handle->sig->sig.keyid(); + return hex_encode_value(keyid.data(), keyid.size(), result); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_key_fprint(rnp_signature_handle_t handle, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + if (!handle->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!handle->sig->sig.has_keyfp()) { + *result = NULL; + return RNP_SUCCESS; + } + pgp_fingerprint_t keyfp = handle->sig->sig.keyfp(); + return hex_encode_value(keyfp.fingerprint, keyfp.length, result); +} +FFI_GUARD + +rnp_result_t +rnp_signature_get_signer(rnp_signature_handle_t sig, rnp_key_handle_t *key) +try { + if (!sig || !sig->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!sig->sig->sig.has_keyid()) { + *key = NULL; + return RNP_SUCCESS; + } + pgp_key_search_t locator(PGP_KEY_SEARCH_KEYID); + locator.by.keyid = sig->sig->sig.keyid(); + return rnp_locate_key_int(sig->ffi, locator, key); +} +FFI_GUARD + +rnp_result_t +rnp_signature_is_valid(rnp_signature_handle_t sig, uint32_t flags) +try { + if (!sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!sig->sig || sig->own_sig || flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!sig->sig->validity.validated) { + pgp_key_t *signer = + pgp_sig_get_signer(*sig->sig, sig->ffi->pubring, &sig->ffi->key_provider); + if (!signer) { + return RNP_ERROR_KEY_NOT_FOUND; + } + signer->validate_sig(*sig->key, *sig->sig, sig->ffi->context); + } + + if (!sig->sig->validity.validated) { + return RNP_ERROR_VERIFICATION_FAILED; + } + if (sig->sig->validity.expired) { + return RNP_ERROR_SIGNATURE_EXPIRED; + } + return sig->sig->valid() ? RNP_SUCCESS : RNP_ERROR_SIGNATURE_INVALID; +} +FFI_GUARD + +rnp_result_t +rnp_signature_packet_to_json(rnp_signature_handle_t sig, uint32_t flags, char **json) +try { + if (!sig || !json) { + return RNP_ERROR_NULL_POINTER; + } + + rnp::MemoryDest memdst; + sig->sig->sig.write(memdst.dst()); + auto vec = memdst.to_vector(); + rnp::MemorySource memsrc(vec); + return rnp_dump_src_to_json(&memsrc.src(), flags, json); +} +FFI_GUARD + +rnp_result_t +rnp_signature_remove(rnp_key_handle_t key, rnp_signature_handle_t sig) +try { + if (!key || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (sig->own_sig || !sig->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *pkey = get_key_require_public(key); + pgp_key_t *skey = get_key_require_secret(key); + if (!pkey && !skey) { + return RNP_ERROR_BAD_PARAMETERS; + } + const pgp_sig_id_t sigid = sig->sig->sigid; + bool ok = false; + if (pkey) { + ok = pkey->del_sig(sigid); + pkey->revalidate(*key->ffi->pubring); + } + if (skey) { + /* secret key may not have signature, but we still need to delete it at least once to + * succeed */ + ok = skey->del_sig(sigid) || ok; + skey->revalidate(*key->ffi->secring); + } + return ok ? RNP_SUCCESS : RNP_ERROR_NO_SIGNATURES_FOUND; +} +FFI_GUARD + +static rnp_result_t +write_signature(rnp_signature_handle_t sig, pgp_dest_t &dst) +{ + sig->sig->rawpkt.write(dst); + dst_flush(&dst); + return dst.werr; +} + +rnp_result_t +rnp_signature_export(rnp_signature_handle_t sig, rnp_output_t output, uint32_t flags) +try { + if (!sig || !sig->sig || !output) { + return RNP_ERROR_NULL_POINTER; + } + bool need_armor = extract_flag(flags, RNP_KEY_EXPORT_ARMORED); + if (flags) { + FFI_LOG(sig->ffi, "Invalid flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + rnp_result_t ret; + if (need_armor) { + rnp::ArmoredDest armor(output->dst, PGP_ARMORED_PUBLIC_KEY); + ret = write_signature(sig, armor.dst()); + } else { + ret = write_signature(sig, output->dst); + } + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_signature_handle_destroy(rnp_signature_handle_t sig) +try { + if (sig && sig->own_sig) { + delete sig->sig; + } + free(sig); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_is_revoked(rnp_uid_handle_t uid, bool *result) +try { + if (!uid || !result) { + return RNP_ERROR_NULL_POINTER; + } + + if (!uid->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *result = uid->key->get_uid(uid->idx).revoked; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_get_revocation_signature(rnp_uid_handle_t uid, rnp_signature_handle_t *sig) +try { + if (!uid || !sig) { + return RNP_ERROR_NULL_POINTER; + } + if (!uid->key) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (uid->idx >= uid->key->uid_count()) { + return RNP_ERROR_BAD_STATE; + } + const pgp_userid_t &userid = uid->key->get_uid(uid->idx); + if (!userid.revoked) { + *sig = NULL; + return RNP_SUCCESS; + } + if (!uid->key->has_sig(userid.revocation.sigid)) { + return RNP_ERROR_BAD_STATE; + } + return rnp_key_return_signature( + uid->ffi, uid->key, &uid->key->get_sig(userid.revocation.sigid), sig); +} +FFI_GUARD + +rnp_result_t +rnp_uid_remove(rnp_key_handle_t key, rnp_uid_handle_t uid) +try { + if (!key || !uid) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *pkey = get_key_require_public(key); + pgp_key_t *skey = get_key_require_secret(key); + if (!pkey && !skey) { + return RNP_ERROR_BAD_PARAMETERS; + } + if ((uid->key != pkey) && (uid->key != skey)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + bool ok = false; + if (pkey && (pkey->uid_count() > uid->idx)) { + pkey->del_uid(uid->idx); + pkey->revalidate(*key->ffi->pubring); + ok = true; + } + if (skey && (skey->uid_count() > uid->idx)) { + skey->del_uid(uid->idx); + skey->revalidate(*key->ffi->secring); + ok = true; + } + return ok ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; +} +FFI_GUARD + +rnp_result_t +rnp_uid_handle_destroy(rnp_uid_handle_t uid) +try { + free(uid); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_subkey_count(rnp_key_handle_t handle, size_t *count) +try { + if (!handle || !count) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + *count = key->subkey_count(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_subkey_at(rnp_key_handle_t handle, size_t idx, rnp_key_handle_t *subkey) +try { + if (!handle || !subkey) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (idx >= key->subkey_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_search_t locator(PGP_KEY_SEARCH_FINGERPRINT); + locator.by.fingerprint = key->get_subkey_fp(idx); + return rnp_locate_key_int(handle->ffi, locator, subkey); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_default_key(rnp_key_handle_t primary_key, + const char * usage, + uint32_t flags, + rnp_key_handle_t *default_key) +try { + if (!primary_key || !usage || !default_key) { + return RNP_ERROR_NULL_POINTER; + } + uint8_t keyflag = 0; + if (!str_to_key_flag(usage, &keyflag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + bool no_primary = extract_flag(flags, RNP_KEY_SUBKEYS_ONLY); + if (flags) { + FFI_LOG(primary_key->ffi, "Invalid flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_op_t op = PGP_OP_UNKNOWN; + bool secret = false; + switch (keyflag) { + case PGP_KF_SIGN: + op = PGP_OP_SIGN; + secret = true; + break; + case PGP_KF_CERTIFY: + op = PGP_OP_CERTIFY; + secret = true; + break; + case PGP_KF_ENCRYPT: + op = PGP_OP_ENCRYPT; + break; + default: + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *key = get_key_prefer_public(primary_key); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *defkey = + find_suitable_key(op, key, &primary_key->ffi->key_provider, no_primary); + if (!defkey) { + *default_key = NULL; + return RNP_ERROR_NO_SUITABLE_KEY; + } + + pgp_key_search_t search(PGP_KEY_SEARCH_FINGERPRINT); + search.by.fingerprint = defkey->fp(); + + rnp_result_t ret = rnp_locate_key_int(primary_key->ffi, search, default_key, secret); + + if (!*default_key && !ret) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_alg(rnp_key_handle_t handle, char **alg) +try { + if (!handle || !alg) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + return get_map_value(pubkey_alg_map, key->alg(), alg); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_bits(rnp_key_handle_t handle, uint32_t *bits) +try { + if (!handle || !bits) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + size_t _bits = key->material().bits(); + if (!_bits) { + return RNP_ERROR_BAD_PARAMETERS; + } + *bits = _bits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_dsa_qbits(rnp_key_handle_t handle, uint32_t *qbits) +try { + if (!handle || !qbits) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + size_t _qbits = key->material().qbits(); + if (!_qbits) { + return RNP_ERROR_BAD_PARAMETERS; + } + *qbits = _qbits; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_curve(rnp_key_handle_t handle, char **curve) +try { + if (!handle || !curve) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t * key = get_key_prefer_public(handle); + pgp_curve_t _curve = key->curve(); + if (_curve == PGP_CURVE_UNKNOWN) { + return RNP_ERROR_BAD_PARAMETERS; + } + const char *curvename = NULL; + if (!curve_type_to_str(_curve, &curvename)) { + return RNP_ERROR_BAD_PARAMETERS; + } + char *curvenamecp = strdup(curvename); + if (!curvenamecp) { + return RNP_ERROR_OUT_OF_MEMORY; + } + *curve = curvenamecp; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_fprint(rnp_key_handle_t handle, char **fprint) +try { + if (!handle || !fprint) { + return RNP_ERROR_NULL_POINTER; + } + + const pgp_fingerprint_t &fp = get_key_prefer_public(handle)->fp(); + return hex_encode_value(fp.fingerprint, fp.length, fprint); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_keyid(rnp_key_handle_t handle, char **keyid) +try { + if (!handle || !keyid) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + return hex_encode_value(key->keyid().data(), key->keyid().size(), keyid); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_grip(rnp_key_handle_t handle, char **grip) +try { + if (!handle || !grip) { + return RNP_ERROR_NULL_POINTER; + } + + const pgp_key_grip_t &kgrip = get_key_prefer_public(handle)->grip(); + return hex_encode_value(kgrip.data(), kgrip.size(), grip); +} +FFI_GUARD + +static const pgp_key_grip_t * +rnp_get_grip_by_fp(rnp_ffi_t ffi, const pgp_fingerprint_t &fp) +{ + pgp_key_t *key = NULL; + if (ffi->pubring) { + key = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); + } + if (!key && ffi->secring) { + key = rnp_key_store_get_key_by_fpr(ffi->secring, fp); + } + return key ? &key->grip() : NULL; +} + +rnp_result_t +rnp_key_get_primary_grip(rnp_key_handle_t handle, char **grip) +try { + if (!handle || !grip) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key->is_subkey()) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->has_primary_fp()) { + *grip = NULL; + return RNP_SUCCESS; + } + const pgp_key_grip_t *pgrip = rnp_get_grip_by_fp(handle->ffi, key->primary_fp()); + if (!pgrip) { + *grip = NULL; + return RNP_SUCCESS; + } + return hex_encode_value(pgrip->data(), pgrip->size(), grip); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_primary_fprint(rnp_key_handle_t handle, char **fprint) +try { + if (!handle || !fprint) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = get_key_prefer_public(handle); + if (!key->is_subkey()) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->has_primary_fp()) { + *fprint = NULL; + return RNP_SUCCESS; + } + const pgp_fingerprint_t &fp = key->primary_fp(); + return hex_encode_value(fp.fingerprint, fp.length, fprint); +} +FFI_GUARD + +rnp_result_t +rnp_key_allows_usage(rnp_key_handle_t handle, const char *usage, bool *result) +try { + if (!handle || !usage || !result) { + return RNP_ERROR_NULL_POINTER; + } + uint8_t flag = 0; + if (!str_to_key_flag(usage, &flag)) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->flags() & flag; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_creation(rnp_key_handle_t handle, uint32_t *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->creation(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_revoked(rnp_key_handle_t handle, bool *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->revoked(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_valid(rnp_key_handle_t handle, bool *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_require_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!key->validated()) { + key->validate(*handle->ffi->pubring); + } + if (!key->validated()) { + return RNP_ERROR_VERIFICATION_FAILED; + } + *result = key->valid(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_valid_till(rnp_key_handle_t handle, uint32_t *result) +try { + if (!result) { + return RNP_ERROR_NULL_POINTER; + } + uint64_t res = 0; + rnp_result_t ret = rnp_key_valid_till64(handle, &res); + if (ret) { + return ret; + } + if (res == UINT64_MAX) { + *result = UINT32_MAX; + } else if (res >= UINT32_MAX) { + *result = UINT32_MAX - 1; + } else { + *result = (uint32_t) res; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_valid_till64(rnp_key_handle_t handle, uint64_t *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_require_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (!key->validated()) { + key->validate(*handle->ffi->pubring); + } + if (!key->validated()) { + return RNP_ERROR_VERIFICATION_FAILED; + } + + if (key->is_subkey()) { + /* check validity time of the primary key as well */ + pgp_key_t *primary = rnp_key_store_get_primary_key(handle->ffi->pubring, key); + if (!primary) { + /* no primary key - subkey considered as never valid */ + *result = 0; + return RNP_SUCCESS; + } + if (!primary->validated()) { + primary->validate(*handle->ffi->pubring); + } + if (!primary->validated()) { + return RNP_ERROR_VERIFICATION_FAILED; + } + *result = key->valid_till(); + } else { + *result = key->valid_till(); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_expiration(rnp_key_handle_t handle, uint32_t *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->expiration(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_set_expiration(rnp_key_handle_t key, uint32_t expiry) +try { + if (!key) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *pkey = get_key_prefer_public(key); + if (!pkey) { + return RNP_ERROR_BAD_PARAMETERS; + } + pgp_key_t *skey = get_key_require_secret(key); + if (!skey) { + FFI_LOG(key->ffi, "Secret key required."); + return RNP_ERROR_BAD_PARAMETERS; + } + + if (pkey->is_primary()) { + if (!pgp_key_set_expiration( + pkey, skey, expiry, key->ffi->pass_provider, key->ffi->context)) { + return RNP_ERROR_GENERIC; + } + pkey->revalidate(*key->ffi->pubring); + if (pkey != skey) { + skey->revalidate(*key->ffi->secring); + } + return RNP_SUCCESS; + } + + /* for subkey we need primary key */ + if (!pkey->has_primary_fp()) { + FFI_LOG(key->ffi, "Primary key fp not available."); + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_search_t search(PGP_KEY_SEARCH_FINGERPRINT); + search.by.fingerprint = pkey->primary_fp(); + pgp_key_t *prim_sec = find_key(key->ffi, search, true, true); + if (!prim_sec) { + FFI_LOG(key->ffi, "Primary secret key not found."); + return RNP_ERROR_KEY_NOT_FOUND; + } + if (!pgp_subkey_set_expiration( + pkey, prim_sec, skey, expiry, key->ffi->pass_provider, key->ffi->context)) { + return RNP_ERROR_GENERIC; + } + prim_sec->revalidate(*key->ffi->secring); + pgp_key_t *prim_pub = find_key(key->ffi, search, false, true); + if (prim_pub) { + prim_pub->revalidate(*key->ffi->pubring); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_revocation_reason(rnp_key_handle_t handle, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key || !key->revoked()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *result = strdup(key->revocation().reason.c_str()); + if (!*result) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +rnp_key_is_revoked_with_code(rnp_key_handle_t handle, bool *result, int code) +{ + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key || !key->revoked()) { + return RNP_ERROR_BAD_PARAMETERS; + } + + *result = key->revocation().code == code; + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_is_superseded(rnp_key_handle_t handle, bool *result) +try { + return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_SUPERSEDED); +} +FFI_GUARD + +rnp_result_t +rnp_key_is_compromised(rnp_key_handle_t handle, bool *result) +try { + return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_COMPROMISED); +} +FFI_GUARD + +rnp_result_t +rnp_key_is_retired(rnp_key_handle_t handle, bool *result) +try { + return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_RETIRED); +} +FFI_GUARD + +rnp_result_t +rnp_key_is_expired(rnp_key_handle_t handle, bool *result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_prefer_public(handle); + if (!key) { + return RNP_ERROR_BAD_PARAMETERS; + } + *result = key->expired(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_type(rnp_key_handle_t key, char **type) +try { + if (!key || !type) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + + const pgp_s2k_t &s2k = key->sec->pkt().sec_protection.s2k; + const char * res = "Unknown"; + if (s2k.usage == PGP_S2KU_NONE) { + res = "None"; + } + if ((s2k.usage == PGP_S2KU_ENCRYPTED) && (s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + res = "Encrypted"; + } + if ((s2k.usage == PGP_S2KU_ENCRYPTED_AND_HASHED) && + (s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + res = "Encrypted-Hashed"; + } + if ((s2k.specifier == PGP_S2KS_EXPERIMENTAL) && + (s2k.gpg_ext_num == PGP_S2K_GPG_NO_SECRET)) { + res = "GPG-None"; + } + if ((s2k.specifier == PGP_S2KS_EXPERIMENTAL) && + (s2k.gpg_ext_num == PGP_S2K_GPG_SMARTCARD)) { + res = "GPG-Smartcard"; + } + + return ret_str_value(res, type); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_mode(rnp_key_handle_t key, char **mode) +try { + if (!key || !mode) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (key->sec->pkt().sec_protection.s2k.usage == PGP_S2KU_NONE) { + return ret_str_value("None", mode); + } + if (key->sec->pkt().sec_protection.s2k.specifier == PGP_S2KS_EXPERIMENTAL) { + return ret_str_value("Unknown", mode); + } + + return get_map_value(cipher_mode_map, key->sec->pkt().sec_protection.cipher_mode, mode); +} +FFI_GUARD + +static bool +pgp_key_has_encryption_info(const pgp_key_t *key) +{ + return (key->pkt().sec_protection.s2k.usage != PGP_S2KU_NONE) && + (key->pkt().sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL); +} + +rnp_result_t +rnp_key_get_protection_cipher(rnp_key_handle_t key, char **cipher) +try { + if (!key || !cipher) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pgp_key_has_encryption_info(key->sec)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + return get_map_value(symm_alg_map, key->sec->pkt().sec_protection.symm_alg, cipher); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_hash(rnp_key_handle_t key, char **hash) +try { + if (!key || !hash) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pgp_key_has_encryption_info(key->sec)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + return get_map_value(hash_alg_map, key->sec->pkt().sec_protection.s2k.hash_alg, hash); +} +FFI_GUARD + +rnp_result_t +rnp_key_get_protection_iterations(rnp_key_handle_t key, size_t *iterations) +try { + if (!key || !iterations) { + return RNP_ERROR_NULL_POINTER; + } + if (!key->sec) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!pgp_key_has_encryption_info(key->sec)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + if (key->sec->pkt().sec_protection.s2k.specifier == PGP_S2KS_ITERATED_AND_SALTED) { + *iterations = pgp_s2k_decode_iterations(key->sec->pkt().sec_protection.s2k.iterations); + } else { + *iterations = 1; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_locked(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_locked(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_lock(rnp_key_handle_t handle) +try { + if (handle == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + if (!key->lock()) { + return RNP_ERROR_GENERIC; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_unlock(rnp_key_handle_t handle, const char *password) +try { + if (!handle) { + return RNP_ERROR_NULL_POINTER; + } + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + bool ok = false; + if (password) { + pgp_password_provider_t prov(rnp_password_provider_string, + reinterpret_cast<void *>(const_cast<char *>(password))); + ok = key->unlock(prov); + } else { + ok = key->unlock(handle->ffi->pass_provider); + } + if (!ok) { + // likely a bad password + return RNP_ERROR_BAD_PASSWORD; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_protected(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_protected(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_protect(rnp_key_handle_t handle, + const char * password, + const char * cipher, + const char * cipher_mode, + const char * hash, + size_t iterations) +try { + rnp_key_protection_params_t protection = {}; + + // checks + if (!handle || !password) { + return RNP_ERROR_NULL_POINTER; + } + + if (cipher && !str_to_cipher(cipher, &protection.symm_alg)) { + FFI_LOG(handle->ffi, "Invalid cipher: %s", cipher); + return RNP_ERROR_BAD_PARAMETERS; + } + if (cipher_mode && !str_to_cipher_mode(cipher_mode, &protection.cipher_mode)) { + FFI_LOG(handle->ffi, "Invalid cipher mode: %s", cipher_mode); + return RNP_ERROR_BAD_PARAMETERS; + } + if (hash && !str_to_hash_alg(hash, &protection.hash_alg)) { + FFI_LOG(handle->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + protection.iterations = iterations; + + // get the key + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + pgp_key_pkt_t * decrypted_key = NULL; + const std::string pass = password; + if (key->encrypted()) { + pgp_password_ctx_t ctx(PGP_OP_PROTECT, key); + decrypted_key = pgp_decrypt_seckey(*key, handle->ffi->pass_provider, ctx); + if (!decrypted_key) { + return RNP_ERROR_GENERIC; + } + } + bool res = key->protect( + decrypted_key ? *decrypted_key : key->pkt(), protection, pass, handle->ffi->context); + delete decrypted_key; + return res ? RNP_SUCCESS : RNP_ERROR_GENERIC; +} +FFI_GUARD + +rnp_result_t +rnp_key_unprotect(rnp_key_handle_t handle, const char *password) +try { + // checks + if (!handle) { + return RNP_ERROR_NULL_POINTER; + } + + // get the key + pgp_key_t *key = get_key_require_secret(handle); + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + bool ok = false; + if (password) { + pgp_password_provider_t prov(rnp_password_provider_string, + reinterpret_cast<void *>(const_cast<char *>(password))); + ok = key->unprotect(prov, handle->ffi->context); + } else { + ok = key->unprotect(handle->ffi->pass_provider, handle->ffi->context); + } + if (!ok) { + // likely a bad password + return RNP_ERROR_BAD_PASSWORD; + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_primary(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_prefer_public(handle); + if (key->format == PGP_KEY_STORE_G10) { + // we can't currently determine this for a G10 secret key + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_primary(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_is_sub(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + pgp_key_t *key = get_key_prefer_public(handle); + if (key->format == PGP_KEY_STORE_G10) { + // we can't currently determine this for a G10 secret key + return RNP_ERROR_NO_SUITABLE_KEY; + } + *result = key->is_subkey(); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_have_secret(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + + *result = handle->sec != NULL; + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_key_have_public(rnp_key_handle_t handle, bool *result) +try { + if (handle == NULL || result == NULL) + return RNP_ERROR_NULL_POINTER; + *result = handle->pub != NULL; + return RNP_SUCCESS; +} +FFI_GUARD + +static rnp_result_t +key_to_bytes(pgp_key_t *key, uint8_t **buf, size_t *buf_len) +{ + auto vec = rnp_key_to_vec(*key); + *buf = (uint8_t *) calloc(1, vec.size()); + if (!*buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*buf, vec.data(), vec.size()); + *buf_len = vec.size(); + return RNP_SUCCESS; +} + +rnp_result_t +rnp_get_public_key_data(rnp_key_handle_t handle, uint8_t **buf, size_t *buf_len) +try { + // checks + if (!handle || !buf || !buf_len) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = handle->pub; + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + return key_to_bytes(key, buf, buf_len); +} +FFI_GUARD + +rnp_result_t +rnp_get_secret_key_data(rnp_key_handle_t handle, uint8_t **buf, size_t *buf_len) +try { + // checks + if (!handle || !buf || !buf_len) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = handle->sec; + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + return key_to_bytes(key, buf, buf_len); +} +FFI_GUARD + +static bool +add_json_string_field(json_object *jso, const char *key, const char *value) +{ + json_object *jsostr = json_object_new_string(value); + if (!jsostr) { + return false; + } + json_object_object_add(jso, key, jsostr); + return true; +} + +static bool +add_json_int_field(json_object *jso, const char *key, int32_t value) +{ + json_object *jsoval = json_object_new_int(value); + if (!jsoval) { + return false; + } + json_object_object_add(jso, key, jsoval); + return true; +} + +static bool +add_json_key_usage(json_object *jso, uint8_t key_flags) +{ + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(key_usage_map); i++) { + if (key_usage_map[i].id & key_flags) { + json_object *jsostr = json_object_new_string(key_usage_map[i].str); + if (!jsostr || json_object_array_add(jsoarr, jsostr)) { + json_object_put(jsoarr); + return false; + } + } + } + if (json_object_array_length(jsoarr)) { + json_object_object_add(jso, "usage", jsoarr); + } else { + json_object_put(jsoarr); + } + return true; +} + +static bool +add_json_key_flags(json_object *jso, uint8_t key_flags) +{ + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(key_flags_map); i++) { + if (key_flags_map[i].id & key_flags) { + json_object *jsostr = json_object_new_string(key_flags_map[i].str); + if (!jsostr || json_object_array_add(jsoarr, jsostr)) { + json_object_put(jsoarr); + return false; + } + } + } + if (json_object_array_length(jsoarr)) { + json_object_object_add(jso, "flags", jsoarr); + } else { + json_object_put(jsoarr); + } + return true; +} + +static rnp_result_t +add_json_mpis(json_object *jso, ...) +{ + va_list ap; + const char * name; + rnp_result_t ret = RNP_ERROR_GENERIC; + + va_start(ap, jso); + while ((name = va_arg(ap, const char *))) { + pgp_mpi_t *val = va_arg(ap, pgp_mpi_t *); + if (!val) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + char *hex = mpi2hex(val); + if (!hex) { + // this could probably be other things + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + json_object *jsostr = json_object_new_string(hex); + free(hex); + if (!jsostr) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + json_object_object_add(jso, name, jsostr); + } + ret = RNP_SUCCESS; + +done: + va_end(ap); + return ret; +} + +static rnp_result_t +add_json_public_mpis(json_object *jso, pgp_key_t *key) +{ + const pgp_key_material_t &km = key->material(); + switch (km.alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return add_json_mpis(jso, "n", &km.rsa.n, "e", &km.rsa.e, NULL); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return add_json_mpis(jso, "p", &km.eg.p, "g", &km.eg.g, "y", &km.eg.y, NULL); + case PGP_PKA_DSA: + return add_json_mpis( + jso, "p", &km.dsa.p, "q", &km.dsa.q, "g", &km.dsa.g, "y", &km.dsa.y, NULL); + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return add_json_mpis(jso, "point", &km.ec.p, NULL); + default: + return RNP_ERROR_NOT_SUPPORTED; + } + return RNP_SUCCESS; +} + +static rnp_result_t +add_json_secret_mpis(json_object *jso, pgp_key_t *key) +{ + const pgp_key_material_t &km = key->material(); + switch (key->alg()) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return add_json_mpis( + jso, "d", &km.rsa.d, "p", &km.rsa.p, "q", &km.rsa.q, "u", &km.rsa.u, NULL); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return add_json_mpis(jso, "x", &km.eg.x, NULL); + case PGP_PKA_DSA: + return add_json_mpis(jso, "x", &km.dsa.x, NULL); + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return add_json_mpis(jso, "x", &km.ec.x, NULL); + default: + return RNP_ERROR_NOT_SUPPORTED; + } + return RNP_SUCCESS; +} + +static rnp_result_t +add_json_sig_mpis(json_object *jso, const pgp_signature_t *sig) +{ + pgp_signature_material_t material = {}; + try { + if (!sig->parse_material(material)) { + return RNP_ERROR_BAD_PARAMETERS; + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_OUT_OF_MEMORY; + } + switch (sig->palg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_ENCRYPT_ONLY: + case PGP_PKA_RSA_SIGN_ONLY: + return add_json_mpis(jso, "sig", &material.rsa.s, NULL); + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: + return add_json_mpis(jso, "r", &material.eg.r, "s", &material.eg.s, NULL); + case PGP_PKA_DSA: + return add_json_mpis(jso, "r", &material.dsa.r, "s", &material.dsa.s, NULL); + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: + return add_json_mpis(jso, "r", &material.ecc.r, "s", &material.ecc.s, NULL); + default: + // TODO: we could use info->unknown and add a hex string of raw data here + return RNP_ERROR_NOT_SUPPORTED; + } + return RNP_SUCCESS; +} + +static bool +add_json_user_prefs(json_object *jso, const pgp_user_prefs_t &prefs) +{ + // TODO: instead of using a string "Unknown" as a fallback for these, + // we could add a string of hex/dec (or even an int) + if (!prefs.symm_algs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "ciphers", jsoarr); + for (auto alg : prefs.symm_algs) { + const char * name = id_str_pair::lookup(symm_alg_map, alg, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.hash_algs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "hashes", jsoarr); + for (auto alg : prefs.hash_algs) { + const char * name = id_str_pair::lookup(hash_alg_map, alg, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.z_algs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "compression", jsoarr); + for (auto alg : prefs.z_algs) { + const char * name = id_str_pair::lookup(compress_alg_map, alg, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.ks_prefs.empty()) { + json_object *jsoarr = json_object_new_array(); + if (!jsoarr) { + return false; + } + json_object_object_add(jso, "key server preferences", jsoarr); + for (auto flag : prefs.ks_prefs) { + const char * name = id_str_pair::lookup(key_server_prefs_map, flag, "Unknown"); + json_object *jsoname = json_object_new_string(name); + if (!jsoname || json_object_array_add(jsoarr, jsoname)) { + return false; + } + } + } + if (!prefs.key_server.empty()) { + if (!add_json_string_field(jso, "key server", prefs.key_server.c_str())) { + return false; + } + } + return true; +} + +static rnp_result_t +add_json_subsig(json_object *jso, bool is_sub, uint32_t flags, const pgp_subsig_t *subsig) +{ + // userid (if applicable) + if (!is_sub) { + json_object *jsouid = json_object_new_int(subsig->uid); + if (!jsouid) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "userid", jsouid); + } + // trust + json_object *jsotrust = json_object_new_object(); + if (!jsotrust) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "trust", jsotrust); + // trust (level) + json_object *jsotrust_level = json_object_new_int(subsig->trustlevel); + if (!jsotrust_level) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsotrust, "level", jsotrust_level); + // trust (amount) + json_object *jsotrust_amount = json_object_new_int(subsig->trustamount); + if (!jsotrust_amount) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsotrust, "amount", jsotrust_amount); + // key flags (usage) + if (!add_json_key_usage(jso, subsig->key_flags)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // key flags (other) + if (!add_json_key_flags(jso, subsig->key_flags)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // preferences + const pgp_user_prefs_t &prefs = subsig->prefs; + if (!prefs.symm_algs.empty() || !prefs.hash_algs.empty() || !prefs.z_algs.empty() || + !prefs.ks_prefs.empty() || !prefs.key_server.empty()) { + json_object *jsoprefs = json_object_new_object(); + if (!jsoprefs) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "preferences", jsoprefs); + if (!add_json_user_prefs(jsoprefs, prefs)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + const pgp_signature_t *sig = &subsig->sig; + // version + json_object *jsoversion = json_object_new_int(sig->version); + if (!jsoversion) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "version", jsoversion); + // signature type + auto type = id_str_pair::lookup(sig_type_map, sig->type()); + if (!add_json_string_field(jso, "type", type)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // signer key type + const char *key_type = id_str_pair::lookup(pubkey_alg_map, sig->palg); + if (!add_json_string_field(jso, "key type", key_type)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // hash + const char *hash = id_str_pair::lookup(hash_alg_map, sig->halg); + if (!add_json_string_field(jso, "hash", hash)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // creation time + json_object *jsocreation_time = json_object_new_int64(sig->creation()); + if (!jsocreation_time) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "creation time", jsocreation_time); + // expiration (seconds) + json_object *jsoexpiration = json_object_new_int64(sig->expiration()); + if (!jsoexpiration) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "expiration", jsoexpiration); + // signer + json_object *jsosigner = NULL; + // TODO: add signer fingerprint as well (no support internally yet) + if (sig->has_keyid()) { + jsosigner = json_object_new_object(); + if (!jsosigner) { + return RNP_ERROR_OUT_OF_MEMORY; + } + char keyid[PGP_KEY_ID_SIZE * 2 + 1]; + pgp_key_id_t signer = sig->keyid(); + if (!rnp::hex_encode(signer.data(), signer.size(), keyid, sizeof(keyid))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jsosigner, "keyid", keyid)) { + json_object_put(jsosigner); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + json_object_object_add(jso, "signer", jsosigner); + // mpis + json_object *jsompis = NULL; + if (flags & RNP_JSON_SIGNATURE_MPIS) { + jsompis = json_object_new_object(); + if (!jsompis) { + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t tmpret; + if ((tmpret = add_json_sig_mpis(jsompis, sig))) { + json_object_put(jsompis); + return tmpret; + } + } + json_object_object_add(jso, "mpis", jsompis); + return RNP_SUCCESS; +} + +static rnp_result_t +key_to_json(json_object *jso, rnp_key_handle_t handle, uint32_t flags) +{ + pgp_key_t *key = get_key_prefer_public(handle); + + // type + const char *str = id_str_pair::lookup(pubkey_alg_map, key->alg(), NULL); + if (!str) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!add_json_string_field(jso, "type", str)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // length + if (!add_json_int_field(jso, "length", key->material().bits())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // curve / alg-specific items + switch (key->alg()) { + case PGP_PKA_ECDH: { + const char *hash_name = + id_str_pair::lookup(hash_alg_map, key->material().ec.kdf_hash_alg, NULL); + if (!hash_name) { + return RNP_ERROR_BAD_PARAMETERS; + } + const char *cipher_name = + id_str_pair::lookup(symm_alg_map, key->material().ec.key_wrap_alg, NULL); + if (!cipher_name) { + return RNP_ERROR_BAD_PARAMETERS; + } + json_object *jsohash = json_object_new_string(hash_name); + if (!jsohash) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "kdf hash", jsohash); + json_object *jsocipher = json_object_new_string(cipher_name); + if (!jsocipher) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "key wrap cipher", jsocipher); + } + [[fallthrough]]; + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: { + const char *curve_name = NULL; + if (!curve_type_to_str(key->material().ec.curve, &curve_name)) { + return RNP_ERROR_BAD_PARAMETERS; + } + json_object *jsocurve = json_object_new_string(curve_name); + if (!jsocurve) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "curve", jsocurve); + } break; + default: + break; + } + + // keyid + char keyid[PGP_KEY_ID_SIZE * 2 + 1]; + if (!rnp::hex_encode(key->keyid().data(), key->keyid().size(), keyid, sizeof(keyid))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "keyid", keyid)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // fingerprint + char fpr[PGP_FINGERPRINT_SIZE * 2 + 1]; + if (!rnp::hex_encode(key->fp().fingerprint, key->fp().length, fpr, sizeof(fpr))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "fingerprint", fpr)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // grip + char grip[PGP_KEY_GRIP_SIZE * 2 + 1]; + if (!rnp::hex_encode(key->grip().data(), key->grip().size(), grip, sizeof(grip))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "grip", grip)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // revoked + json_object *jsorevoked = json_object_new_boolean(key->revoked() ? true : false); + if (!jsorevoked) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "revoked", jsorevoked); + // creation time + json_object *jsocreation_time = json_object_new_int64(key->creation()); + if (!jsocreation_time) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "creation time", jsocreation_time); + // expiration + json_object *jsoexpiration = json_object_new_int64(key->expiration()); + if (!jsoexpiration) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "expiration", jsoexpiration); + // key flags (usage) + if (!add_json_key_usage(jso, key->flags())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // key flags (other) + if (!add_json_key_flags(jso, key->flags())) { + return RNP_ERROR_OUT_OF_MEMORY; + } + // parent / subkeys + if (key->is_primary()) { + json_object *jsosubkeys_arr = json_object_new_array(); + if (!jsosubkeys_arr) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "subkey grips", jsosubkeys_arr); + for (auto &subfp : key->subkey_fps()) { + const pgp_key_grip_t *subgrip = rnp_get_grip_by_fp(handle->ffi, subfp); + if (!subgrip) { + continue; + } + if (!rnp::hex_encode(subgrip->data(), subgrip->size(), grip, sizeof(grip))) { + return RNP_ERROR_GENERIC; + } + json_object *jsostr = json_object_new_string(grip); + if (!jsostr || json_object_array_add(jsosubkeys_arr, jsostr)) { + json_object_put(jsostr); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + } else if (key->has_primary_fp()) { + auto pgrip = rnp_get_grip_by_fp(handle->ffi, key->primary_fp()); + if (pgrip) { + if (!rnp::hex_encode(pgrip->data(), pgrip->size(), grip, sizeof(grip))) { + return RNP_ERROR_GENERIC; + } + if (!add_json_string_field(jso, "primary key grip", grip)) { + return RNP_ERROR_OUT_OF_MEMORY; + } + } + } + // public + json_object *jsopublic = json_object_new_object(); + if (!jsopublic) { + return RNP_ERROR_OUT_OF_MEMORY; + } + bool have_sec = handle->sec != NULL; + bool have_pub = handle->pub != NULL; + json_object_object_add(jso, "public key", jsopublic); + json_object_object_add( + jsopublic, "present", json_object_new_boolean(have_pub ? true : false)); + if (flags & RNP_JSON_PUBLIC_MPIS) { + json_object *jsompis = json_object_new_object(); + if (!jsompis) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsopublic, "mpis", jsompis); + rnp_result_t tmpret; + if ((tmpret = add_json_public_mpis(jsompis, key))) { + return tmpret; + } + } + // secret + json_object *jsosecret = json_object_new_object(); + if (!jsosecret) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "secret key", jsosecret); + json_object_object_add( + jsosecret, "present", json_object_new_boolean(have_sec ? true : false)); + if (have_sec) { + bool locked = handle->sec->is_locked(); + if (flags & RNP_JSON_SECRET_MPIS) { + if (locked) { + json_object_object_add(jsosecret, "mpis", NULL); + } else { + json_object *jsompis = json_object_new_object(); + if (!jsompis) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsosecret, "mpis", jsompis); + rnp_result_t tmpret; + if ((tmpret = add_json_secret_mpis(jsompis, handle->sec))) { + return tmpret; + } + } + } + json_object *jsolocked = json_object_new_boolean(locked ? true : false); + if (!jsolocked) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsosecret, "locked", jsolocked); + json_object *jsoprotected = + json_object_new_boolean(handle->sec->is_protected() ? true : false); + if (!jsoprotected) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jsosecret, "protected", jsoprotected); + } + // userids + if (key->is_primary()) { + json_object *jsouids_arr = json_object_new_array(); + if (!jsouids_arr) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "userids", jsouids_arr); + for (size_t i = 0; i < key->uid_count(); i++) { + json_object *jsouid = json_object_new_string(key->get_uid(i).str.c_str()); + if (!jsouid || json_object_array_add(jsouids_arr, jsouid)) { + json_object_put(jsouid); + return RNP_ERROR_OUT_OF_MEMORY; + } + } + } + // signatures + if (flags & RNP_JSON_SIGNATURES) { + json_object *jsosigs_arr = json_object_new_array(); + if (!jsosigs_arr) { + return RNP_ERROR_OUT_OF_MEMORY; + } + json_object_object_add(jso, "signatures", jsosigs_arr); + for (size_t i = 0; i < key->sig_count(); i++) { + json_object *jsosig = json_object_new_object(); + if (!jsosig || json_object_array_add(jsosigs_arr, jsosig)) { + json_object_put(jsosig); + return RNP_ERROR_OUT_OF_MEMORY; + } + rnp_result_t tmpret; + if ((tmpret = + add_json_subsig(jsosig, key->is_subkey(), flags, &key->get_sig(i)))) { + return tmpret; + } + } + } + return RNP_SUCCESS; +} + +rnp_result_t +rnp_key_to_json(rnp_key_handle_t handle, uint32_t flags, char **result) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + json_object *jso = NULL; + + // checks + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + jso = json_object_new_object(); + if (!jso) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + if ((ret = key_to_json(jso, handle, flags))) { + goto done; + } + *result = (char *) json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY); + if (!*result) { + goto done; + } + *result = strdup(*result); + if (!*result) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + ret = RNP_SUCCESS; +done: + json_object_put(jso); + return ret; +} +FFI_GUARD + +static rnp_result_t +rnp_dump_src_to_json(pgp_source_t *src, uint32_t flags, char **result) +{ + rnp_dump_ctx_t dumpctx = {}; + + dumpctx.dump_mpi = extract_flag(flags, RNP_JSON_DUMP_MPI); + dumpctx.dump_packets = extract_flag(flags, RNP_JSON_DUMP_RAW); + dumpctx.dump_grips = extract_flag(flags, RNP_JSON_DUMP_GRIP); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + json_object *jso = NULL; + rnp_result_t ret = stream_dump_packets_json(&dumpctx, src, &jso); + if (ret) { + goto done; + } + + *result = (char *) json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY); + if (!*result) { + goto done; + } + *result = strdup(*result); + if (!*result) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + + ret = RNP_SUCCESS; +done: + json_object_put(jso); + return ret; +} + +rnp_result_t +rnp_key_packets_to_json(rnp_key_handle_t handle, bool secret, uint32_t flags, char **result) +try { + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_key_t *key = secret ? handle->sec : handle->pub; + if (!key || (key->format == PGP_KEY_STORE_G10)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + auto vec = rnp_key_to_vec(*key); + rnp::MemorySource mem(vec); + return rnp_dump_src_to_json(&mem.src(), flags, result); +} +FFI_GUARD + +rnp_result_t +rnp_dump_packets_to_json(rnp_input_t input, uint32_t flags, char **result) +try { + if (!input || !result) { + return RNP_ERROR_NULL_POINTER; + } + + return rnp_dump_src_to_json(&input->src, flags, result); +} +FFI_GUARD + +rnp_result_t +rnp_dump_packets_to_output(rnp_input_t input, rnp_output_t output, uint32_t flags) +try { + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + + rnp_dump_ctx_t dumpctx = {}; + dumpctx.dump_mpi = extract_flag(flags, RNP_DUMP_MPI); + dumpctx.dump_packets = extract_flag(flags, RNP_DUMP_RAW); + dumpctx.dump_grips = extract_flag(flags, RNP_DUMP_GRIP); + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = stream_dump_packets(&dumpctx, &input->src, &output->dst); + output->keep = true; + return ret; +} +FFI_GUARD + +// move to next key +static bool +key_iter_next_key(rnp_identifier_iterator_t it) +{ + // check if we not reached the end of the ring + *it->keyp = std::next(*it->keyp); + if (*it->keyp != it->store->keys.end()) { + it->uididx = 0; + return true; + } + // if we are currently on pubring, switch to secring (if not empty) + if (it->store == it->ffi->pubring && !it->ffi->secring->keys.empty()) { + it->store = it->ffi->secring; + *it->keyp = it->store->keys.begin(); + it->uididx = 0; + return true; + } + // we've gone through both rings + it->store = NULL; + return false; +} + +// move to next item (key or userid) +static bool +key_iter_next_item(rnp_identifier_iterator_t it) +{ + switch (it->type) { + case PGP_KEY_SEARCH_KEYID: + case PGP_KEY_SEARCH_FINGERPRINT: + case PGP_KEY_SEARCH_GRIP: + return key_iter_next_key(it); + case PGP_KEY_SEARCH_USERID: + it->uididx++; + while (it->uididx >= (*it->keyp)->uid_count()) { + if (!key_iter_next_key(it)) { + return false; + } + it->uididx = 0; + } + break; + default: + assert(false); + break; + } + return true; +} + +static bool +key_iter_first_key(rnp_identifier_iterator_t it) +{ + if (rnp_key_store_get_key_count(it->ffi->pubring)) { + it->store = it->ffi->pubring; + } else if (rnp_key_store_get_key_count(it->ffi->secring)) { + it->store = it->ffi->secring; + } else { + it->store = NULL; + return false; + } + *it->keyp = it->store->keys.begin(); + it->uididx = 0; + return true; +} + +static bool +key_iter_first_item(rnp_identifier_iterator_t it) +{ + switch (it->type) { + case PGP_KEY_SEARCH_KEYID: + case PGP_KEY_SEARCH_FINGERPRINT: + case PGP_KEY_SEARCH_GRIP: + return key_iter_first_key(it); + case PGP_KEY_SEARCH_USERID: + if (!key_iter_first_key(it)) { + return false; + } + it->uididx = 0; + while (it->uididx >= (*it->keyp)->uid_count()) { + if (!key_iter_next_key(it)) { + return false; + } + } + break; + default: + assert(false); + break; + } + return true; +} + +static bool +key_iter_get_item(const rnp_identifier_iterator_t it, char *buf, size_t buf_len) +{ + const pgp_key_t *key = &**it->keyp; + switch (it->type) { + case PGP_KEY_SEARCH_KEYID: { + if (!rnp::hex_encode(key->keyid().data(), key->keyid().size(), buf, buf_len)) { + return false; + } + break; + } + case PGP_KEY_SEARCH_FINGERPRINT: + if (!rnp::hex_encode(key->fp().fingerprint, key->fp().length, buf, buf_len)) { + return false; + } + break; + case PGP_KEY_SEARCH_GRIP: + if (!rnp::hex_encode(key->grip().data(), key->grip().size(), buf, buf_len)) { + return false; + } + break; + case PGP_KEY_SEARCH_USERID: { + if (it->uididx >= key->uid_count()) { + return false; + } + const pgp_userid_t &uid = key->get_uid(it->uididx); + if (uid.str.size() >= buf_len) { + return false; + } + memcpy(buf, uid.str.c_str(), uid.str.size() + 1); + } break; + default: + assert(false); + break; + } + return true; +} + +rnp_result_t +rnp_identifier_iterator_create(rnp_ffi_t ffi, + rnp_identifier_iterator_t *it, + const char * identifier_type) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + struct rnp_identifier_iterator_st *obj = NULL; + + // checks + if (!ffi || !it || !identifier_type) { + return RNP_ERROR_NULL_POINTER; + } + // create iterator + obj = (struct rnp_identifier_iterator_st *) calloc(1, sizeof(*obj)); + if (!obj) { + return RNP_ERROR_OUT_OF_MEMORY; + } + obj->ffi = ffi; + obj->keyp = new std::list<pgp_key_t>::iterator(); + obj->uididx = 0; + // parse identifier type + obj->type = static_cast<pgp_key_search_type_t>( + id_str_pair::lookup(identifier_type_map, identifier_type, PGP_KEY_SEARCH_UNKNOWN)); + if (obj->type == PGP_KEY_SEARCH_UNKNOWN) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + obj->tbl = json_object_new_object(); + if (!obj->tbl) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + // move to first item (if any) + key_iter_first_item(obj); + *it = obj; + + ret = RNP_SUCCESS; +done: + if (ret) { + rnp_identifier_iterator_destroy(obj); + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_identifier_iterator_next(rnp_identifier_iterator_t it, const char **identifier) +try { + rnp_result_t ret = RNP_ERROR_GENERIC; + + // checks + if (!it || !identifier) { + return RNP_ERROR_NULL_POINTER; + } + // initialize the result to NULL + *identifier = NULL; + // this means we reached the end of the rings + if (!it->store) { + return RNP_SUCCESS; + } + // get the item + if (!key_iter_get_item(it, it->buf, sizeof(it->buf))) { + return RNP_ERROR_GENERIC; + } + bool exists; + bool iterator_valid = true; + while ((exists = json_object_object_get_ex(it->tbl, it->buf, NULL))) { + if (!((iterator_valid = key_iter_next_item(it)))) { + break; + } + if (!key_iter_get_item(it, it->buf, sizeof(it->buf))) { + return RNP_ERROR_GENERIC; + } + } + // see if we actually found a new entry + if (!exists) { + // TODO: Newer json-c has a useful return value for json_object_object_add, + // which doesn't require the json_object_object_get_ex check below. + json_object_object_add(it->tbl, it->buf, NULL); + if (!json_object_object_get_ex(it->tbl, it->buf, NULL)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + *identifier = it->buf; + } + // prepare for the next one + if (iterator_valid) { + key_iter_next_item(it); + } + ret = RNP_SUCCESS; + +done: + if (ret) { + *identifier = NULL; + } + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_identifier_iterator_destroy(rnp_identifier_iterator_t it) +try { + if (it) { + json_object_put(it->tbl); + if (it->keyp) { + delete it->keyp; + } + free(it); + } + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_guess_contents(rnp_input_t input, char **contents) +try { + if (!input || !contents) { + return RNP_ERROR_NULL_POINTER; + } + + pgp_armored_msg_t msgtype = PGP_ARMORED_UNKNOWN; + if (is_armored_source(&input->src)) { + msgtype = rnp_armored_get_type(&input->src); + } else { + msgtype = rnp_armor_guess_type(&input->src); + } + const char *msg = id_str_pair::lookup(armor_type_map, msgtype); + size_t len = strlen(msg); + *contents = (char *) calloc(1, len + 1); + if (!*contents) { + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*contents, msg, len); + return RNP_SUCCESS; +} +FFI_GUARD + +rnp_result_t +rnp_enarmor(rnp_input_t input, rnp_output_t output, const char *type) +try { + pgp_armored_msg_t msgtype = PGP_ARMORED_UNKNOWN; + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + if (type) { + msgtype = static_cast<pgp_armored_msg_t>( + id_str_pair::lookup(armor_type_map, type, PGP_ARMORED_UNKNOWN)); + if (msgtype == PGP_ARMORED_UNKNOWN) { + RNP_LOG("Unsupported armor type: %s", type); + return RNP_ERROR_BAD_PARAMETERS; + } + } else { + msgtype = rnp_armor_guess_type(&input->src); + if (!msgtype) { + RNP_LOG("Unrecognized data to armor (try specifying a type)"); + return RNP_ERROR_BAD_PARAMETERS; + } + } + rnp_result_t ret = rnp_armor_source(&input->src, &output->dst, msgtype); + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_dearmor(rnp_input_t input, rnp_output_t output) +try { + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + rnp_result_t ret = rnp_dearmor_source(&input->src, &output->dst); + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_output_pipe(rnp_input_t input, rnp_output_t output) +try { + if (!input || !output) { + return RNP_ERROR_NULL_POINTER; + } + rnp_result_t ret = dst_write_src(&input->src, &output->dst); + output->keep = !ret; + return ret; +} +FFI_GUARD + +rnp_result_t +rnp_output_armor_set_line_length(rnp_output_t output, size_t llen) +try { + if (!output || !llen) { + return RNP_ERROR_BAD_PARAMETERS; + } + return armored_dst_set_line_length(&output->dst, llen); +} +FFI_GUARD + +const char * +rnp_backend_string() +{ + return rnp::backend_string(); +} + +const char * +rnp_backend_version() +{ + return rnp::backend_version(); +} |