Bug 1647671 - Update RNP to snapshot from 2020-06-19. r=rjl a=wsmwk
authorKai Engert <kaie@kuix.de>
Tue, 23 Jun 2020 12:32:22 +0200
changeset 39451 14075142ff5f4cf89f114c5761d1e95f07dd829a
parent 39450 2485cd508b607f5ab9c12f47766cc8b0a89b44d5
child 39452 e9d431c06249a0c60e48ed647c6aeedd24ada2fc
push id402
push userclokep@gmail.com
push dateMon, 29 Jun 2020 20:48:04 +0000
reviewersrjl, wsmwk
bugs1647671
Bug 1647671 - Update RNP to snapshot from 2020-06-19. r=rjl a=wsmwk Differential Revision: https://phabricator.services.mozilla.com/D80628
third_party/README.rnp
third_party/rnp/include/rekey/rnp_key_store.h
third_party/rnp/include/rnp/rnp.h
third_party/rnp/src/examples/sign.c
third_party/rnp/src/lib/CMakeLists.txt
third_party/rnp/src/lib/config.h.in
third_party/rnp/src/lib/ffi-priv-types.h
third_party/rnp/src/lib/pgp-key.cpp
third_party/rnp/src/lib/pgp-key.h
third_party/rnp/src/lib/rnp.cpp
third_party/rnp/src/lib/utils.h
third_party/rnp/src/lib/version.h
third_party/rnp/src/librekey/key_store_pgp.cpp
third_party/rnp/src/librekey/rnp_key_store.cpp
third_party/rnp/src/librepgp/stream-parse.cpp
third_party/rnp/src/librepgp/stream-parse.h
third_party/rnp/src/rnp/CMakeLists.txt
third_party/rnp/src/rnp/fficli.cpp
third_party/rnp/src/rnp/fficli.h
third_party/rnp/src/rnpkeys/CMakeLists.txt
third_party/rnp/src/rnpkeys/tui.cpp
third_party/rnp/src/tests/CMakeLists.txt
third_party/rnp/src/tests/data/test_messages/message.txt.enc-3key-2p
third_party/rnp/src/tests/data/test_messages/message.txt.enc-aead-eax
third_party/rnp/src/tests/data/test_messages/message.txt.enc-aead-eax-malf
third_party/rnp/src/tests/data/test_messages/message.txt.enc-aead-ocb
third_party/rnp/src/tests/data/test_messages/message.txt.enc-aead-ocb-malf
third_party/rnp/src/tests/data/test_messages/message.txt.enc-mdc
third_party/rnp/src/tests/data/test_messages/message.txt.enc-no-mdc
third_party/rnp/src/tests/ffi.cpp
third_party/rnp/src/tests/issues/1171.cpp
third_party/rnp/src/tests/rnp_tests.h
--- a/third_party/README.rnp
+++ b/third_party/README.rnp
@@ -1,12 +1,12 @@
 Directory ./rnp contains a copy of rnp which has been obtained from:
 https://github.com/rnpgp/rnp
 
-[commit b9335cee8cb346b567b8bfdb93e851618c6deb4a]
+[commit ac07057820ece92ddab52536f4a78d7d5bb71ba7]
 
 For licensing information, please refer to the included documentation.
 
 To update this copy, run "update_rnp.sh" in this directory from this directory
 within a complete build tree (including mozilla-central) as "mach python" is
 used.
 
 update_rnp.sh will generate rnp/src/lib/version.h from rnp/src/lib/version.h.in
--- a/third_party/rnp/include/rekey/rnp_key_store.h
+++ b/third_party/rnp/include/rekey/rnp_key_store.h
@@ -201,17 +201,17 @@ pgp_sig_import_status_t rnp_key_store_im
  * @param sig signature to import.
  * @param status signature import status will be put here, if not NULL.
  * @return pointer to the key to which this signature belongs (or NULL if key was not found)
  */
 pgp_key_t *rnp_key_store_import_signature(rnp_key_store_t *        keyring,
                                           const pgp_signature_t *  sig,
                                           pgp_sig_import_status_t *status);
 
-bool rnp_key_store_remove_key(rnp_key_store_t *, const pgp_key_t *);
+bool rnp_key_store_remove_key(rnp_key_store_t *, const pgp_key_t *, bool);
 
 pgp_key_t *rnp_key_store_get_key_by_id(rnp_key_store_t *, const unsigned char *, pgp_key_t *);
 
 bool rnp_key_store_get_key_grip(const pgp_key_material_t *, pgp_key_grip_t &grip);
 
 const pgp_key_t *rnp_key_store_get_key_by_grip(const rnp_key_store_t *,
                                                const pgp_key_grip_t &);
 pgp_key_t *      rnp_key_store_get_key_by_grip(rnp_key_store_t *, const pgp_key_grip_t &);
--- a/third_party/rnp/include/rnp/rnp.h
+++ b/third_party/rnp/include/rnp/rnp.h
@@ -41,16 +41,17 @@ typedef uint32_t rnp_result_t;
 
 #define RNP_KEY_EXPORT_ARMORED (1U << 0)
 #define RNP_KEY_EXPORT_PUBLIC (1U << 1)
 #define RNP_KEY_EXPORT_SECRET (1U << 2)
 #define RNP_KEY_EXPORT_SUBKEYS (1U << 3)
 
 #define RNP_KEY_REMOVE_PUBLIC (1U << 0)
 #define RNP_KEY_REMOVE_SECRET (1U << 1)
+#define RNP_KEY_REMOVE_SUBKEYS (1U << 2)
 
 #define RNP_KEY_UNLOAD_PUBLIC (1U << 0)
 #define RNP_KEY_UNLOAD_SECRET (1U << 1)
 
 /**
  * Flags for optional details to include in JSON.
  */
 #define RNP_JSON_PUBLIC_MPIS (1U << 0)
@@ -165,16 +166,18 @@ typedef struct rnp_op_generate_st *     
 typedef struct rnp_op_sign_st *            rnp_op_sign_t;
 typedef struct rnp_op_sign_signature_st *  rnp_op_sign_signature_t;
 typedef struct rnp_op_verify_st *          rnp_op_verify_t;
 typedef struct rnp_op_verify_signature_st *rnp_op_verify_signature_t;
 typedef struct rnp_op_encrypt_st *         rnp_op_encrypt_t;
 typedef struct rnp_identifier_iterator_st *rnp_identifier_iterator_t;
 typedef struct rnp_uid_handle_st *         rnp_uid_handle_t;
 typedef struct rnp_signature_handle_st *   rnp_signature_handle_t;
+typedef struct rnp_recipient_handle_st *   rnp_recipient_handle_t;
+typedef struct rnp_symenc_handle_st *      rnp_symenc_handle_t;
 
 /* Callbacks */
 /**
  * @brief Callback, used to read data from the source.
  *
  * @param app_ctx custom parameter, passed back to the function.
  * @param buf on successfull call data should be put here. Cannot be NULL,
  *            and must be capable to store at least len bytes.
@@ -879,17 +882,18 @@ rnp_result_t rnp_key_revoke(rnp_key_hand
                             const char *     hash,
                             const char *     code,
                             const char *     reason);
 
 /** remove a key from keyring(s)
  *  Note: you need to call rnp_save_keys() to write updated keyring(s) out.
  *        Other handles of the same key should not be used after this call.
  * @param key pointer to the key handle.
- * @param flags see RNP_KEY_REMOVE_* constants.
+ * @param flags see RNP_KEY_REMOVE_* constants. Flag RNP_REMOVE_SUBKEYS will work only for
+ *              primary key, and remove all of it's subkeys as well.
  * @return RNP_SUCCESS or error code if failed.
  */
 rnp_result_t rnp_key_remove(rnp_key_handle_t key, uint32_t flags);
 
 /** guess contents of the OpenPGP data stream.
  *
  * @param input stream with data. Must be opened and cannot be NULL.
  * @param contents string with guessed data format will be stored here.
@@ -1635,16 +1639,183 @@ rnp_result_t rnp_op_verify_get_signature
  *  @param filename pointer to the filename. On success caller is responsible for freeing it
  *                  via the rnp_buffer_free function call. May be NULL if this information is
  *                  not needed.
  *  @param mtime file modification time will be stored here on success. May be NULL.
  *  @return RNP_SUCCESS if call succeeded.
  */
 rnp_result_t rnp_op_verify_get_file_info(rnp_op_verify_t op, char **filename, uint32_t *mtime);
 
+/**
+ * @brief Get data protection (encryption) mode, used in processed message.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param mode on success string with mode will be stored here. Caller is responsible for
+ *             freeing it using the rnp_buffer_free() call. May be NULL if information is not
+ * needed. Currently defined values are as following:
+ *             - none : message was not protected/encrypted
+ *             - cfb : message was encrypted in CFB mode without the MDC
+ *             - cfb-mdc : message was encrypted in CFB mode and protected with MDC
+ *             - aead-ocb : message was encrypted in AEAD-OCB mode
+ *             - aead-eax : message was encrypted in AEAD-EAX mode
+ * @param cipher symmetric cipher, used for data encryption. May be NULL if information is not
+ *               needed. Must be freed by rnp_buffer_free() call.
+ * @param valid true if message integrity protection was used (i.e. MDC or AEAD), and it was
+ *              validated successfully. Otherwise (even for raw cfb mode) will be false. May be
+ *              NULL if information is not needed.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_op_verify_get_protection_info(rnp_op_verify_t op,
+                                               char **         mode,
+                                               char **         cipher,
+                                               bool *          valid);
+
+/**
+ * @brief Get number of public keys (recipients) to whom message was encrypted to.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param count on success number of keys will be stored here. Cannot be NULL.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_op_verify_get_recipient_count(rnp_op_verify_t op, size_t *count);
+
+/**
+ * @brief Get the recipient's handle, used to decrypt message.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param recipient pointer to the opaque handle context. Cannot be NULL. If recipient's key
+ *                  was used to decrypt a message then handle will be stored here, otherwise
+ *                  it will be set to NULL.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_op_verify_get_used_recipient(rnp_op_verify_t         op,
+                                              rnp_recipient_handle_t *recipient);
+
+/**
+ * @brief Get the recipient's handle by index.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param idx zero-based index in array.
+ * @param recipient pointer to the opaque handle context. Cannot be NULL. On success handle
+ *                  will be stored here.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_op_verify_get_recipient_at(rnp_op_verify_t         op,
+                                            size_t                  idx,
+                                            rnp_recipient_handle_t *recipient);
+
+/**
+ * @brief Get recipient's keyid.
+ *
+ * @param recipient recipient's handle, obtained via rnp_op_verify_get_used_recipient() or
+ *                  rnp_op_verify_get_recipient_at() function call. Cannot be NULL.
+ * @param keyid on success pointer to NULL-terminated string with hex-encoded keyid will be
+ *              stored here. Cannot be NULL. Must be freed using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_recipient_get_keyid(rnp_recipient_handle_t recipient, char **keyid);
+
+/**
+ * @brief Get recipient's key algorithm.
+ *
+ * @param recipient recipient's handle, obtained via rnp_op_verify_get_used_recipient() or
+ *                  rnp_op_verify_get_recipient_at() function call. Cannot be NULL.
+ * @param alg on success pointer to NULL-terminated string with algorithm will be stored here.
+ *            Cannot be NULL. Must be freed using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_recipient_get_alg(rnp_recipient_handle_t recipient, char **alg);
+
+/**
+ * @brief Get number of symenc entries (i.e. passwords), to which message was encrypted.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param count on success number of keys will be stored here. Cannot be NULL.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_op_verify_get_symenc_count(rnp_op_verify_t op, size_t *count);
+
+/**
+ * @brief Get the symenc handle, used to decrypt a message.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param symenc pointer to the opaque symenc context. Cannot be NULL. If password was used to
+ *               decrypt a message then handle will be stored here, otherwise it will be set to
+ *               NULL.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_op_verify_get_used_symenc(rnp_op_verify_t op, rnp_symenc_handle_t *symenc);
+
+/**
+ * @brief Get the symenc handle by index.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param idx zero-based index in array.
+ * @param symenc pointer to the opaque handle context. Cannot be NULL. On success handle
+ *               will be stored here.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_op_verify_get_symenc_at(rnp_op_verify_t      op,
+                                         size_t               idx,
+                                         rnp_symenc_handle_t *symenc);
+
+/**
+ * @brief Get the symmetric cipher, used to encrypt data encryption key.
+ *        Note: if message is encrypted with only one passphrase and without public keys, then
+ *        key, derived from password, may be used to encrypt the whole message.
+ * @param symenc opaque handle, cannot be NULL.
+ * @param cipher NULL-terminated string with cipher's name will be stored here. Cannot be NULL.
+ *               Must be freed using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_symenc_get_cipher(rnp_symenc_handle_t symenc, char **cipher);
+
+/**
+ * @brief Get AEAD algorithm if it was used to encrypt data encryption key.
+ *
+ * @param symenc opaque handle, cannot be NULL.
+ * @param alg NULL-terminated string with AEAD algorithm name will be stored here. If AEAD was
+ *            not used then it will contain string 'None'. Must be freed using the
+ *            rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_symenc_get_aead_alg(rnp_symenc_handle_t symenc, char **alg);
+
+/**
+ * @brief Get hash algorithm, used to derive key from the passphrase.
+ *
+ * @param symenc opaque handle, cannot be NULL.
+ * @param alg NULL-terminated string with hash algorithm name will be stored here. Cannot be
+ *            NULL. Must be freed using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_symenc_get_hash_alg(rnp_symenc_handle_t symenc, char **alg);
+
+/**
+ * @brief Get string-to-key type, used to derive password.
+ *
+ * @param symenc opaque handle, cannot be NULL.
+ * @param type NULL-terminated string with s2k type will be stored here. Currently following
+ *             types are available: 'Simple', 'Salted', 'Iterated and salted'. Please note that
+ *             first two are considered weak and should not be used. Must be freed using the
+ *             rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_symenc_get_s2k_type(rnp_symenc_handle_t symenc, char **type);
+
+/**
+ * @brief Get number of iterations in iterated-and-salted S2K, if it was used.
+ *
+ * @param symenc opaque handle, cannot be NULL.
+ * @param iterations on success number of iterations will be stored here. Cannot be NULL.
+ *                   If non-iterated s2k was used then will be set to 0.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+rnp_result_t rnp_symenc_get_s2k_iterations(rnp_symenc_handle_t symenc, uint32_t *iterations);
+
 /** @brief Free resources allocated in verification context.
  *  @param op opaque verification context. Must be initialized.
  *  @return RNP_SUCCESS if call succeeded.
  */
 rnp_result_t rnp_op_verify_destroy(rnp_op_verify_t op);
 
 /** @brief Get signature verification status.
  *  @param sig opaque signature context obtained via rnp_op_verify_get_signature_at call.
@@ -2096,18 +2267,16 @@ rnp_result_t rnp_identifier_iterator_nex
  *  @param it the iterator object
  *  @return RNP_SUCCESS on success, or any other value on error
  */
 rnp_result_t rnp_identifier_iterator_destroy(rnp_identifier_iterator_t it);
 
 #if defined(__cplusplus)
 }
 
-#include "utils.h"
-
 #endif
 
 /** Algorithm Strings
  */
 #ifndef RNP_ALGNAME_PLAINTEXT
 
 #define RNP_ALGNAME_PLAINTEXT "PLAINTEXT"
 #define RNP_ALGNAME_RSA "RSA"
--- a/third_party/rnp/src/examples/sign.c
+++ b/third_party/rnp/src/examples/sign.c
@@ -20,17 +20,16 @@
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include <rnp/rnp.h>
-#include <repgp/repgp_def.h>
 #include <string.h>
 #include <time.h>
 
 #define RNP_SUCCESS 0
 
 /* sample pass provider implementation, which always return 'password' */
 static bool
 example_pass_provider(rnp_ffi_t        ffi,
@@ -161,9 +160,9 @@ finish:
     rnp_ffi_destroy(ffi);
     return result;
 }
 
 int
 main(int argc, char **argv)
 {
     return ffi_sign();
-}
\ No newline at end of file
+}
--- a/third_party/rnp/src/lib/CMakeLists.txt
+++ b/third_party/rnp/src/lib/CMakeLists.txt
@@ -201,27 +201,27 @@ endif()
 
 # install headers
 install(
   FILES
     "${PROJECT_SOURCE_DIR}/include/rnp/rnp.h"
   COMPONENT
     development
   DESTINATION
-    "${CMAKE_INSTALL_INCLUDEDIR}/${LIBRNP_INCLUDEDIR}"
+    "${CMAKE_INSTALL_INCLUDEDIR}/${LIBRNP_INCLUDEDIR}/rnp"
   RENAME
     rnp.h
 )
 install(
   FILES
     "${PROJECT_SOURCE_DIR}/include/rnp/rnp_err.h"
   COMPONENT
     development
   DESTINATION
-    "${CMAKE_INSTALL_INCLUDEDIR}/${LIBRNP_INCLUDEDIR}"
+    "${CMAKE_INSTALL_INCLUDEDIR}/${LIBRNP_INCLUDEDIR}/rnp"
   RENAME
     rnp_err.h
 )
 
 # .cmake installs
 set(INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/rnp")
 
 install(EXPORT rnp-targets
@@ -244,19 +244,64 @@ write_basic_package_version_file(rnp-con
 install (
   FILES
     "${CMAKE_CURRENT_BINARY_DIR}/rnp-config.cmake"
     "${CMAKE_CURRENT_BINARY_DIR}/rnp-config-version.cmake"
   DESTINATION "${INSTALL_CMAKEDIR}"
   COMPONENT development
 )
 
+function(get_linked_libs libsvar dirsvar tgt)
+  get_target_property(imported ${tgt} IMPORTED)
+  list(APPEND visited_targets ${tgt})
+  if (imported)
+    get_target_property(linkedlibs ${tgt} INTERFACE_LINK_LIBRARIES)
+  else()
+    get_target_property(linkedlibs ${tgt} LINK_LIBRARIES)
+  endif()
+  set(libs)
+  foreach (lib ${linkedlibs})
+    if (TARGET ${lib})
+      list(FIND visited_targets ${lib} visited)
+      if (${visited} EQUAL -1)
+        # library
+        get_target_property(liblocation ${lib} LOCATION)
+        get_filename_component(linkedlib ${liblocation} NAME_WE)
+        string(REGEX REPLACE "^${CMAKE_SHARED_LIBRARY_PREFIX}" "" linkedlib ${linkedlib})
+        get_linked_libs(linkedlibs libdirs ${lib})
+        list(APPEND libs ${linkedlib} ${linkedlibs})
+        # directory
+        get_filename_component(libdir ${liblocation} DIRECTORY)
+        list(FIND ${dirsvar} ${libdir} seendir)
+        if (${seendir} EQUAL -1)
+          list(APPEND ${dirsvar} ${libdir} ${libdirs})
+        endif()
+      endif()
+    endif()
+  endforeach()
+  set(visited_targets ${visited_targets} PARENT_SCOPE)
+  set(${libsvar} ${libs} PARENT_SCOPE)
+  set(${dirsvar} ${${dirsvar}} PARENT_SCOPE)
+endfunction()
+
+get_linked_libs(libs dirs librnp)
+set(linkercmd)
+foreach (dir ${dirs})
+  string(APPEND linkercmd "-L${dir} ")
+endforeach()
+foreach (lib ${libs})
+  string(APPEND linkercmd "-l${lib} ")
+endforeach()
+string(STRIP "${linkercmd}" linkercmd)
+set(LIBRNP_PRIVATE_LIBS ${linkercmd})
+
 # create a pkgconfig .pc too
 find_package(PkgConfig)
 if (PKG_CONFIG_FOUND)
+  get_target_property(LIBRNP_OUTPUT_NAME librnp OUTPUT_NAME)
   configure_file(
     "${PROJECT_SOURCE_DIR}/cmake/librnp.pc.in"
     "${PROJECT_BINARY_DIR}/librnp-${PROJECT_VERSION_MAJOR}.pc"
     @ONLY
   )
   install(
     FILES "${PROJECT_BINARY_DIR}/librnp-${PROJECT_VERSION_MAJOR}.pc"
     DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
--- a/third_party/rnp/src/lib/config.h.in
+++ b/third_party/rnp/src/lib/config.h.in
@@ -19,17 +19,17 @@
  * 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.
  */
 
-#define PACKAGE_STRING    "rnp 0.13.1+git20200609.b9335cee.MZLA"
+#define PACKAGE_STRING    "rnp 0.13.1+git20200619.ac070578.MZLA"
 #define PACKAGE_BUGREPORT "https://bugzilla.mozilla.org/enter_bug.cgi?product=Thunderbird"
 
 #undef HAVE_BZLIB_H
 #undef HAVE_ZLIB_H
 
 #undef HAVE_FCNTL_H
 #undef HAVE_INTTYPES_H
 #undef HAVE_LIMITS_H
@@ -43,8 +43,15 @@
 #undef HAVE_UNISTD_H
 #undef HAVE_SYS_WAIT_H
 #undef HAVE_MKDTEMP
 #undef HAVE_REALPATH
 #undef HAVE_O_BINARY
 #undef HAVE__O_BINARY
 #undef HAVE__TEMPNAM
 
+/* Macro _GLIBCXX_USE_CXX11_ABI was first introduced with GCC 5.0, which
+ * we assume to be bundled with a sane implementation of std::regex. */
+#if !defined(__GNUC__) || defined(_GLIBCXX_USE_CXX11_ABI)
+#define RNP_USE_STD_REGEX 1
+#endif
+
+
--- a/third_party/rnp/src/lib/ffi-priv-types.h
+++ b/third_party/rnp/src/lib/ffi-priv-types.h
@@ -44,16 +44,31 @@ struct rnp_uid_handle_st {
 
 struct rnp_signature_handle_st {
     rnp_ffi_t     ffi;
     pgp_key_t *   key;
     pgp_subsig_t *sig;
     bool          own_sig;
 };
 
+struct rnp_recipient_handle_st {
+    rnp_ffi_t        ffi;
+    uint8_t          keyid[PGP_KEY_ID_SIZE];
+    pgp_pubkey_alg_t palg;
+};
+
+struct rnp_symenc_handle_st {
+    rnp_ffi_t           ffi;
+    pgp_symm_alg_t      alg;
+    pgp_hash_alg_t      halg;
+    pgp_s2k_specifier_t s2k_type;
+    uint32_t            iterations;
+    pgp_aead_alg_t      aalg;
+};
+
 struct rnp_ffi_st {
     FILE *                  errs;
     rnp_key_store_t *       pubring;
     rnp_key_store_t *       secring;
     rnp_get_key_cb          getkeycb;
     void *                  getkeycb_ctx;
     rnp_password_cb         getpasscb;
     void *                  getpasscb_ctx;
@@ -127,16 +142,29 @@ struct rnp_op_verify_st {
     rnp_input_t  detached_input; /* for detached signature will be source file/data */
     rnp_output_t output;
     rnp_ctx_t    rnpctx;
     /* these fields are filled after operation execution */
     rnp_op_verify_signature_t signatures;
     size_t                    signature_count;
     char *                    filename;
     uint32_t                  file_mtime;
+    /* encryption information */
+    bool           encrypted;
+    bool           mdc;
+    bool           validated;
+    pgp_aead_alg_t aead;
+    pgp_symm_alg_t salg;
+    /* recipient/symenc information */
+    rnp_recipient_handle_t recipients;
+    size_t                 recipient_count;
+    rnp_recipient_handle_t used_recipient;
+    rnp_symenc_handle_t    symencs;
+    size_t                 symenc_count;
+    rnp_symenc_handle_t    used_symenc;
 };
 
 struct rnp_op_encrypt_st {
     rnp_ffi_t    ffi;
     rnp_input_t  input;
     rnp_output_t output;
     rnp_ctx_t    rnpctx;
     list         signatures;
--- a/third_party/rnp/src/lib/pgp-key.cpp
+++ b/third_party/rnp/src/lib/pgp-key.cpp
@@ -1305,16 +1305,25 @@ pgp_key_add_subkey_grip(pgp_key_t *key, 
         key->subkey_grips.push_back(grip);
         return true;
     } catch (const std::exception &e) {
         RNP_LOG("%s", e.what());
         return false;
     }
 }
 
+void
+pgp_key_remove_subkey_grip(pgp_key_t *key, const pgp_key_grip_t &grip)
+{
+    auto it = std::find(key->subkey_grips.begin(), key->subkey_grips.end(), grip);
+    if (it != key->subkey_grips.end()) {
+        key->subkey_grips.erase(it);
+    }
+}
+
 const pgp_key_grip_t &
 pgp_key_get_subkey_grip(const pgp_key_t *key, size_t idx)
 {
     return key->subkey_grips[idx];
 }
 
 pgp_key_t *
 pgp_key_get_subkey(const pgp_key_t *key, rnp_key_store_t *store, size_t idx)
--- a/third_party/rnp/src/lib/pgp-key.h
+++ b/third_party/rnp/src/lib/pgp-key.h
@@ -323,16 +323,24 @@ size_t pgp_key_get_subkey_count(const pg
  *
  * @param key key pointer to the primary key
  * @param grip subkey's grip.
  * @return true if succeeded (grip already exists in list or added), or false otherwise.
  */
 bool pgp_key_add_subkey_grip(pgp_key_t *key, const pgp_key_grip_t &grip);
 
 /**
+ * @brief Remove subkey grip from key's list.
+ *
+ * @param key key pointer to the primary key
+ * @param grip subkey's grip.
+ */
+void pgp_key_remove_subkey_grip(pgp_key_t *key, const pgp_key_grip_t &grip);
+
+/**
  * @brief Get the pgp key's subkey grip
  *
  * @param key key pointer to the primary key
  * @param idx index of the subkey
  * @return grip or throws std::out_of_range exception
  */
 const pgp_key_grip_t &pgp_key_get_subkey_grip(const pgp_key_t *key, size_t idx);
 
--- a/third_party/rnp/src/lib/rnp.cpp
+++ b/third_party/rnp/src/lib/rnp.cpp
@@ -192,16 +192,21 @@ static const pgp_map_t hash_alg_map[] = 
                                          {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},
                                          {PGP_HASH_CRC24, RNP_ALGNAME_CRC24}};
 
+static const pgp_map_t s2k_type_map[] = {
+  {PGP_S2KS_SIMPLE, "Simple"},
+  {PGP_S2KS_SALTED, "Salted"},
+  {PGP_S2KS_ITERATED_AND_SALTED, "Iterated and salted"}};
+
 static const pgp_bit_map_t key_usage_map[] = {{PGP_KF_SIGN, "sign"},
                                               {PGP_KF_CERTIFY, "certify"},
                                               {PGP_KF_ENCRYPT, "encrypt"},
                                               {PGP_KF_AUTH, "authenticate"}};
 
 static const pgp_bit_map_t key_flags_map[] = {{PGP_KF_SPLIT, "split"},
                                               {PGP_KF_SHARED, "shared"}};
 
@@ -366,16 +371,53 @@ parse_ks_format(pgp_key_store_format_t *
     } 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)
+{
+    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 pgp_map_t *map, size_t msize, int val, char **res)
+{
+    const char *str = NULL;
+    for (size_t i = 0; i < msize; i++) {
+        if (map[i].type == val) {
+            str = map[i].string;
+            break;
+        }
+    }
+    if (!str) {
+        return RNP_ERROR_BAD_PARAMETERS;
+    }
+    char *strcp = strdup(str);
+    if (!strcp) {
+        return RNP_ERROR_OUT_OF_MEMORY;
+    }
+    *res = strcp;
+    return RNP_SUCCESS;
+}
+
 rnp_result_t
 rnp_ffi_create(rnp_ffi_t *ffi, const char *pub_format, const char *sec_format)
 {
     struct rnp_ffi_st *ob = NULL;
     rnp_result_t       ret = RNP_ERROR_GENERIC;
 
     // checks
     if (!ffi || !pub_format || !sec_format) {
@@ -2660,16 +2702,112 @@ rnp_verify_dest_provider(pgp_parse_handl
     rnp_op_verify_t op = (rnp_op_verify_t) handler->param;
     *dst = &(op->output->dst);
     *closedst = false;
     op->filename = filename ? strdup(filename) : NULL;
 
     return true;
 }
 
+static void
+recipient_handle_from_pk_sesskey(rnp_recipient_handle_t  handle,
+                                 const pgp_pk_sesskey_t &sesskey)
+{
+    memcpy(handle->keyid, sesskey.key_id, 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;
+    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;
+    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;
+    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;
+    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)
 {
     if (!op || !ffi || !input) {
         return RNP_ERROR_NULL_POINTER;
@@ -2716,16 +2854,20 @@ rnp_op_verify_execute(rnp_op_verify_t op
 {
     pgp_parse_handler_t handler;
 
     handler.password_provider = &op->ffi->pass_provider;
     handler.key_provider = &op->ffi->key_provider;
     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);
     if (op->output) {
         dst_flush(&op->output->dst);
         op->output->keep = ret == RNP_SUCCESS;
     }
@@ -2768,26 +2910,220 @@ rnp_op_verify_get_file_info(rnp_op_verif
             *filename = strdup(op->filename);
         } else {
             *filename = NULL;
         }
     }
     return RNP_SUCCESS;
 }
 
+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";
+    }
+    const char *str = "unknown";
+    ARRAY_LOOKUP_BY_ID(symm_alg_map, type, string, op->salg, str);
+    return str;
+}
+
+rnp_result_t
+rnp_op_verify_get_protection_info(rnp_op_verify_t op, char **mode, char **cipher, bool *valid)
+{
+    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;
+}
+
+rnp_result_t
+rnp_op_verify_get_recipient_count(rnp_op_verify_t op, size_t *count)
+{
+    if (!op || !count) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    *count = op->recipient_count;
+    return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_op_verify_get_used_recipient(rnp_op_verify_t op, rnp_recipient_handle_t *recipient)
+{
+    if (!op || !recipient) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    *recipient = op->used_recipient;
+    return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_op_verify_get_recipient_at(rnp_op_verify_t         op,
+                               size_t                  idx,
+                               rnp_recipient_handle_t *recipient)
+{
+    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;
+}
+
+rnp_result_t
+rnp_recipient_get_keyid(rnp_recipient_handle_t recipient, char **keyid)
+{
+    if (!recipient || !keyid) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    return hex_encode_value(recipient->keyid, PGP_KEY_ID_SIZE, keyid, RNP_HEX_UPPERCASE);
+}
+
+rnp_result_t
+rnp_recipient_get_alg(rnp_recipient_handle_t recipient, char **alg)
+{
+    if (!recipient || !alg) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    return get_map_value(pubkey_alg_map, ARRAY_SIZE(pubkey_alg_map), recipient->palg, alg);
+}
+
+rnp_result_t
+rnp_op_verify_get_symenc_count(rnp_op_verify_t op, size_t *count)
+{
+    if (!op || !count) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    *count = op->symenc_count;
+    return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_op_verify_get_used_symenc(rnp_op_verify_t op, rnp_symenc_handle_t *symenc)
+{
+    if (!op || !symenc) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    *symenc = op->used_symenc;
+    return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_op_verify_get_symenc_at(rnp_op_verify_t op, size_t idx, rnp_symenc_handle_t *symenc)
+{
+    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;
+}
+
+rnp_result_t
+rnp_symenc_get_cipher(rnp_symenc_handle_t symenc, char **cipher)
+{
+    if (!symenc || !cipher) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    return get_map_value(symm_alg_map, ARRAY_SIZE(symm_alg_map), symenc->alg, cipher);
+}
+
+rnp_result_t
+rnp_symenc_get_aead_alg(rnp_symenc_handle_t symenc, char **alg)
+{
+    if (!symenc || !alg) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    return get_map_value(aead_alg_map, ARRAY_SIZE(aead_alg_map), symenc->aalg, alg);
+}
+
+rnp_result_t
+rnp_symenc_get_hash_alg(rnp_symenc_handle_t symenc, char **alg)
+{
+    if (!symenc || !alg) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    return get_map_value(hash_alg_map, ARRAY_SIZE(hash_alg_map), symenc->halg, alg);
+}
+
+rnp_result_t
+rnp_symenc_get_s2k_type(rnp_symenc_handle_t symenc, char **type)
+{
+    if (!symenc || !type) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    return get_map_value(s2k_type_map, ARRAY_SIZE(s2k_type_map), symenc->s2k_type, type);
+}
+
+rnp_result_t
+rnp_symenc_get_s2k_iterations(rnp_symenc_handle_t symenc, uint32_t *iterations)
+{
+    if (!symenc || !iterations) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+    *iterations = symenc->iterations;
+    return RNP_SUCCESS;
+}
+
 rnp_result_t
 rnp_op_verify_destroy(rnp_op_verify_t op)
 {
     if (op) {
         rnp_ctx_free(&op->rnpctx);
         for (size_t i = 0; i < op->signature_count; i++) {
             free_signature(&op->signatures[i].sig_pkt);
         }
         free(op->signatures);
         free(op->filename);
+        free(op->recipients);
+        free(op->used_recipient);
+        free(op->symencs);
+        free(op->used_symenc);
         free(op);
     }
     return RNP_SUCCESS;
 }
 
 rnp_result_t
 rnp_op_verify_signature_get_status(rnp_op_verify_signature_t sig)
 {
@@ -2832,24 +3168,17 @@ rnp_op_verify_signature_get_handle(rnp_o
 }
 
 rnp_result_t
 rnp_op_verify_signature_get_hash(rnp_op_verify_signature_t sig, char **hash)
 {
     if (!sig || !hash) {
         return RNP_ERROR_NULL_POINTER;
     }
-
-    const char *hname = NULL;
-    ARRAY_LOOKUP_BY_ID(hash_alg_map, type, string, sig->sig_pkt.halg, hname);
-    if (hname) {
-        *hash = strdup(hname);
-        return RNP_SUCCESS;
-    }
-    return RNP_ERROR_BAD_STATE;
+    return get_map_value(hash_alg_map, ARRAY_SIZE(hash_alg_map), sig->sig_pkt.halg, hash);
 }
 
 rnp_result_t
 rnp_op_verify_signature_get_key(rnp_op_verify_signature_t sig, rnp_key_handle_t *key)
 {
     rnp_ffi_t        ffi = sig->ffi;
     pgp_key_search_t search;
 
@@ -3330,38 +3659,60 @@ rnp_key_revoke(
 }
 
 rnp_result_t
 rnp_key_remove(rnp_key_handle_t key, uint32_t flags)
 {
     if (!key || !key->ffi) {
         return RNP_ERROR_NULL_POINTER;
     }
-    if (flags == 0) {
+    bool pub = false;
+    if (flags & RNP_KEY_REMOVE_PUBLIC) {
+        pub = true;
+        flags &= ~RNP_KEY_REMOVE_PUBLIC;
+    }
+    bool sec = false;
+    if (flags & RNP_KEY_REMOVE_SECRET) {
+        sec = true;
+        flags &= ~RNP_KEY_REMOVE_SECRET;
+    }
+    bool sub = false;
+    if (flags & RNP_KEY_REMOVE_SUBKEYS) {
+        sub = true;
+        flags &= ~RNP_KEY_REMOVE_SUBKEYS;
+    }
+    if (flags) {
+        FFI_LOG(key->ffi, "Unknown flags: %" PRIu32, flags);
         return RNP_ERROR_BAD_PARAMETERS;
     }
-    if (flags & RNP_KEY_REMOVE_PUBLIC) {
+    if (!pub && !sec) {
+        return RNP_ERROR_BAD_PARAMETERS;
+    }
+    if (sub && pgp_key_is_subkey(get_key_prefer_public(key))) {
+        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)) {
+        if (!rnp_key_store_remove_key(key->ffi->pubring, key->pub, sub)) {
             return RNP_ERROR_KEY_NOT_FOUND;
         }
         key->pub = NULL;
     }
-    if (flags & RNP_KEY_REMOVE_SECRET) {
+    if (sec) {
         if (!key->ffi->secring || !key->sec) {
             return RNP_ERROR_BAD_PARAMETERS;
         }
-        if (!rnp_key_store_remove_key(key->ffi->secring, key->sec)) {
+        if (!rnp_key_store_remove_key(key->ffi->secring, key->sec, sub)) {
             return RNP_ERROR_KEY_NOT_FOUND;
         }
         key->sec = NULL;
     }
-
     return RNP_SUCCESS;
 }
 
 static bool
 pk_alg_allows_custom_curve(pgp_pubkey_alg_t pkalg)
 {
     switch (pkalg) {
     case PGP_PKA_ECDH:
@@ -4683,21 +5034,21 @@ rnp_op_generate_execute(rnp_op_generate_
     ret = RNP_SUCCESS;
 done:
     if (op->password) {
         pgp_forget(op->password, strlen(op->password) + 1);
         free(op->password);
         op->password = NULL;
     }
     if (ret && op->gen_pub) {
-        rnp_key_store_remove_key(op->ffi->pubring, 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);
+        rnp_key_store_remove_key(op->ffi->secring, op->gen_sec, false);
         op->gen_sec = NULL;
     }
     return ret;
 }
 
 rnp_result_t
 rnp_op_generate_get_key(rnp_op_generate_t op, rnp_key_handle_t *handle)
 {
@@ -5043,51 +5394,30 @@ rnp_result_t
 rnp_signature_get_alg(rnp_signature_handle_t handle, char **alg)
 {
     if (!handle || !alg) {
         return RNP_ERROR_NULL_POINTER;
     }
     if (!handle->sig) {
         return RNP_ERROR_BAD_PARAMETERS;
     }
-    const char *str = NULL;
-    ARRAY_LOOKUP_BY_ID(pubkey_alg_map, type, string, handle->sig->sig.palg, str);
-    if (!str) {
-        return RNP_ERROR_BAD_PARAMETERS;
-    }
-    char *algcp = strdup(str);
-    if (!algcp) {
-        return RNP_ERROR_OUT_OF_MEMORY;
-    }
-
-    *alg = algcp;
-    return RNP_SUCCESS;
+    return get_map_value(
+      pubkey_alg_map, ARRAY_SIZE(pubkey_alg_map), handle->sig->sig.palg, alg);
 }
 
 rnp_result_t
 rnp_signature_get_hash_alg(rnp_signature_handle_t handle, char **alg)
 {
     if (!handle || !alg) {
         return RNP_ERROR_NULL_POINTER;
     }
     if (!handle->sig) {
         return RNP_ERROR_BAD_PARAMETERS;
     }
-    const char *str = NULL;
-    ARRAY_LOOKUP_BY_ID(hash_alg_map, type, string, handle->sig->sig.halg, str);
-    if (!str) {
-        return RNP_ERROR_BAD_PARAMETERS;
-    }
-    char *algcp = strdup(str);
-    if (!algcp) {
-        return RNP_ERROR_OUT_OF_MEMORY;
-    }
-
-    *alg = algcp;
-    return RNP_SUCCESS;
+    return get_map_value(hash_alg_map, ARRAY_SIZE(hash_alg_map), handle->sig->sig.halg, alg);
 }
 
 rnp_result_t
 rnp_signature_get_creation(rnp_signature_handle_t handle, uint32_t *create)
 {
     if (!handle || !create) {
         return RNP_ERROR_NULL_POINTER;
     }
@@ -5108,28 +5438,17 @@ rnp_signature_get_keyid(rnp_signature_ha
         return RNP_ERROR_BAD_PARAMETERS;
     }
     uint8_t keyid[PGP_KEY_ID_SIZE] = {0};
     if (!signature_get_keyid(&handle->sig->sig, keyid)) {
         *result = NULL;
         return RNP_SUCCESS;
     }
 
-    size_t hex_len = PGP_KEY_ID_SIZE * 2 + 1;
-    *result = (char *) malloc(hex_len);
-    if (!*result) {
-        return RNP_ERROR_OUT_OF_MEMORY;
-    }
-
-    if (!rnp_hex_encode(keyid, PGP_KEY_ID_SIZE, *result, hex_len, RNP_HEX_UPPERCASE)) {
-        free(*result);
-        *result = NULL;
-        return RNP_ERROR_GENERIC;
-    }
-    return RNP_SUCCESS;
+    return hex_encode_value(keyid, PGP_KEY_ID_SIZE, result, RNP_HEX_UPPERCASE);
 }
 
 rnp_result_t
 rnp_signature_get_signer(rnp_signature_handle_t sig, rnp_key_handle_t *key)
 {
     char *       keyid = NULL;
     rnp_result_t ret = rnp_signature_get_keyid(sig, &keyid);
     if (ret) {
@@ -5238,31 +5557,19 @@ rnp_key_get_subkey_at(rnp_key_handle_t h
 }
 
 rnp_result_t
 rnp_key_get_alg(rnp_key_handle_t handle, char **alg)
 {
     if (!handle || !alg) {
         return RNP_ERROR_NULL_POINTER;
     }
-    pgp_key_t * key = get_key_prefer_public(handle);
-    const char *str = NULL;
-
-    ARRAY_LOOKUP_BY_ID(pubkey_alg_map, type, string, pgp_key_get_alg(key), str);
-    if (!str) {
-        return RNP_ERROR_BAD_PARAMETERS;
-    }
-
-    char *algcp = strdup(str);
-    if (!algcp) {
-        return RNP_ERROR_OUT_OF_MEMORY;
-    }
-
-    *alg = algcp;
-    return RNP_SUCCESS;
+    pgp_key_t *key = get_key_prefer_public(handle);
+    return get_map_value(
+      pubkey_alg_map, ARRAY_SIZE(pubkey_alg_map), pgp_key_get_alg(key), alg);
 }
 
 rnp_result_t
 rnp_key_get_bits(rnp_key_handle_t handle, uint32_t *bits)
 {
     if (!handle || !bits) {
         return RNP_ERROR_NULL_POINTER;
     }
@@ -5311,105 +5618,63 @@ rnp_key_get_curve(rnp_key_handle_t handl
     }
     *curve = curvenamecp;
     return RNP_SUCCESS;
 }
 
 rnp_result_t
 rnp_key_get_fprint(rnp_key_handle_t handle, char **fprint)
 {
-    if (handle == NULL || fprint == NULL)
-        return RNP_ERROR_NULL_POINTER;
-
-    size_t hex_len = PGP_FINGERPRINT_HEX_SIZE + 1;
-    *fprint = (char *) malloc(hex_len);
-    if (*fprint == NULL)
-        return RNP_ERROR_OUT_OF_MEMORY;
-
-    pgp_key_t *key = get_key_prefer_public(handle);
-    if (!rnp_hex_encode(pgp_key_get_fp(key)->fingerprint,
-                        pgp_key_get_fp(key)->length,
-                        *fprint,
-                        hex_len,
-                        RNP_HEX_UPPERCASE)) {
-        return RNP_ERROR_GENERIC;
-    }
-    return RNP_SUCCESS;
+    if (!handle || !fprint) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+
+    const pgp_fingerprint_t *fp = pgp_key_get_fp(get_key_prefer_public(handle));
+    return hex_encode_value(fp->fingerprint, fp->length, fprint, RNP_HEX_UPPERCASE);
 }
 
 rnp_result_t
 rnp_key_get_keyid(rnp_key_handle_t handle, char **keyid)
 {
-    if (handle == NULL || keyid == NULL)
-        return RNP_ERROR_NULL_POINTER;
-
-    size_t hex_len = PGP_KEY_ID_SIZE * 2 + 1;
-    *keyid = (char *) malloc(hex_len);
-    if (*keyid == NULL)
-        return RNP_ERROR_OUT_OF_MEMORY;
+    if (!handle || !keyid) {
+        return RNP_ERROR_NULL_POINTER;
+    }
 
     pgp_key_t *key = get_key_prefer_public(handle);
-    if (!rnp_hex_encode(
-          pgp_key_get_keyid(key), PGP_KEY_ID_SIZE, *keyid, hex_len, RNP_HEX_UPPERCASE)) {
-        return RNP_ERROR_GENERIC;
-    }
-    return RNP_SUCCESS;
+    return hex_encode_value(pgp_key_get_keyid(key), PGP_KEY_ID_SIZE, keyid, RNP_HEX_UPPERCASE);
 }
 
 rnp_result_t
 rnp_key_get_grip(rnp_key_handle_t handle, char **grip)
 {
-    if (handle == NULL || grip == NULL)
-        return RNP_ERROR_NULL_POINTER;
-
-    size_t hex_len = PGP_KEY_GRIP_SIZE * 2 + 1;
-    *grip = (char *) malloc(hex_len);
-    if (*grip == NULL)
-        return RNP_ERROR_OUT_OF_MEMORY;
-
-    pgp_key_t *key = get_key_prefer_public(handle);
-    if (!rnp_hex_encode(pgp_key_get_grip(key).data(),
-                        pgp_key_get_grip(key).size(),
-                        *grip,
-                        hex_len,
-                        RNP_HEX_UPPERCASE)) {
-        return RNP_ERROR_GENERIC;
-    }
-    return RNP_SUCCESS;
+    if (!handle || !grip) {
+        return RNP_ERROR_NULL_POINTER;
+    }
+
+    const pgp_key_grip_t &kgrip = pgp_key_get_grip(get_key_prefer_public(handle));
+    return hex_encode_value(kgrip.data(), kgrip.size(), grip, RNP_HEX_UPPERCASE);
 }
 
 rnp_result_t
 rnp_key_get_primary_grip(rnp_key_handle_t handle, char **grip)
 {
-    if (handle == NULL || grip == NULL) {
+    if (!handle || !grip) {
         return RNP_ERROR_NULL_POINTER;
     }
 
     pgp_key_t *key = get_key_prefer_public(handle);
     if (!pgp_key_is_subkey(key)) {
         return RNP_ERROR_BAD_PARAMETERS;
     }
     if (!pgp_key_has_primary_grip(key)) {
         *grip = NULL;
         return RNP_SUCCESS;
     }
-    size_t hex_len = PGP_KEY_GRIP_SIZE * 2 + 1;
-    *grip = (char *) malloc(hex_len);
-    if (*grip == NULL) {
-        return RNP_ERROR_OUT_OF_MEMORY;
-    }
-    if (!rnp_hex_encode(pgp_key_get_primary_grip(key).data(),
-                        pgp_key_get_primary_grip(key).size(),
-                        *grip,
-                        hex_len,
-                        RNP_HEX_UPPERCASE)) {
-        free(*grip);
-        return RNP_ERROR_GENERIC;
-    }
-    return RNP_SUCCESS;
+    const pgp_key_grip_t &pgrip = pgp_key_get_primary_grip(key);
+    return hex_encode_value(pgrip.data(), pgrip.size(), grip, RNP_HEX_UPPERCASE);
 }
 
 rnp_result_t
 rnp_key_allows_usage(rnp_key_handle_t handle, const char *usage, bool *result)
 {
     if (!handle || !usage || !result) {
         return RNP_ERROR_NULL_POINTER;
     }
--- a/third_party/rnp/src/lib/utils.h
+++ b/third_party/rnp/src/lib/utils.h
@@ -202,22 +202,16 @@ STORE64BE(uint8_t x[8], uint64_t y)
     x[6] = (uint8_t)(y >> 8) & 0xff;
     x[7] = (uint8_t)(y >> 0) & 0xff;
 }
 
 #ifndef MAX
 #define MAX(a, b) (((a) > (b)) ? (a) : (b))
 #endif
 
-/* Macro _GLIBCXX_USE_CXX11_ABI was first introduced with GCC 5.0, which
- * we assume to be bundled with a sane implementation of std::regex. */
-#if !defined(__GNUC__) || defined(_GLIBCXX_USE_CXX11_ABI)
-#define RNP_USE_STD_REGEX 1
-#endif
-
 inline char *
 getenv_logname(void)
 {
     char *name = getenv("LOGNAME");
     if (!name) {
         name = getenv("USER");
     }
     return name;
--- a/third_party/rnp/src/lib/version.h
+++ b/third_party/rnp/src/lib/version.h
@@ -23,19 +23,19 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
 #define RNP_VERSION_MAJOR 0
 #define RNP_VERSION_MINOR 13
 #define RNP_VERSION_PATCH 1
 
 #define RNP_VERSION_STRING "0.13.1"
-#define RNP_VERSION_STRING_FULL "0.13.1+git20200609.b9335cee.MZLA"
+#define RNP_VERSION_STRING_FULL "0.13.1+git20200619.ac070578.MZLA"
 
-#define RNP_VERSION_COMMIT_TIMESTAMP 1591722904
+#define RNP_VERSION_COMMIT_TIMESTAMP 1592576776
 
 // using a 32-bit version with 10 bits per component
 #define RNP_VERSION_COMPONENT_MASK 0x3ff
 #define RNP_VERSION_MAJOR_SHIFT 20
 #define RNP_VERSION_MINOR_SHIFT 10
 #define RNP_VERSION_PATCH_SHIFT 0
 #define RNP_VERSION_CODE_FOR(major, minor, patch)                        \
     (((major & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_MAJOR_SHIFT) | \
--- a/third_party/rnp/src/librekey/key_store_pgp.cpp
+++ b/third_party/rnp/src/librekey/key_store_pgp.cpp
@@ -183,17 +183,17 @@ rnp_key_store_add_transferable_key(rnp_k
     }
 
     /* now validate/refresh the whole key with subkeys */
     keyring->disable_validation = false;
     pgp_key_revalidate_updated(addkey, keyring);
     return true;
 error:
     /* during key addition all fields are copied so will be cleaned below */
-    rnp_key_store_remove_key(keyring, addkey);
+    rnp_key_store_remove_key(keyring, addkey, false);
     return false;
 }
 
 bool
 rnp_key_from_transferable_key(pgp_key_t *key, pgp_transferable_key_t *tkey)
 {
     *key = {};
     /* create key */
--- a/third_party/rnp/src/librekey/rnp_key_store.cpp
+++ b/third_party/rnp/src/librekey/rnp_key_store.cpp
@@ -35,36 +35,30 @@
 #include <assert.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <dirent.h>
 #include <errno.h>
 #include <algorithm>
+#include <stdexcept>
 
 #include <rnp/rnp_sdk.h>
 #include <rekey/rnp_key_store.h>
 #include <librepgp/stream-packet.h>
 
 #include "key_store_pgp.h"
 #include "key_store_kbx.h"
 #include "key_store_g10.h"
 
 #include "pgp-key.h"
 #include "fingerprint.h"
 #include "crypto/hash.h"
 
-// must be placed after include "utils.h"
-#ifndef RNP_USE_STD_REGEX
-#include <regex.h>
-#else
-#include <regex>
-#endif
-
 bool
 rnp_key_store_load_from_path(rnp_key_store_t *         key_store,
                              const pgp_key_provider_t *key_provider)
 {
     DIR *          dir;
     bool           rc;
     pgp_source_t   src = {};
     struct dirent *ent;
@@ -671,25 +665,50 @@ rnp_key_store_import_signature(rnp_key_s
         *status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY;
         return NULL;
     }
     *status = rnp_key_store_import_key_signature(keyring, res_key, sig);
     return res_key;
 }
 
 bool
-rnp_key_store_remove_key(rnp_key_store_t *keyring, const pgp_key_t *key)
+rnp_key_store_remove_key(rnp_key_store_t *keyring, const pgp_key_t *key, bool subkeys)
 {
-    keyring->keybygrip.erase(pgp_key_get_grip(key));
-    size_t oldsize = keyring->keys.size();
-    keyring->keys.erase(std::remove_if(keyring->keys.begin(),
-                                       keyring->keys.end(),
-                                       [key](pgp_key_t &_key) { return key == &_key; }));
+    auto it = keyring->keybygrip.find(pgp_key_get_grip(key));
+    if (it == keyring->keybygrip.end()) {
+        return false;
+    }
 
-    return oldsize != keyring->keys.size();
+    /* cleanup primary_grip (or subkey)/subkey_grips */
+    if (pgp_key_is_primary_key(key) && pgp_key_get_subkey_count(key)) {
+        for (size_t i = 0; i < pgp_key_get_subkey_count(key); i++) {
+            auto it = keyring->keybygrip.find(pgp_key_get_subkey_grip(key, i));
+            if (it == keyring->keybygrip.end()) {
+                continue;
+            }
+            /* if subkeys are deleted then no need to update grips */
+            if (subkeys) {
+                keyring->keys.erase(it->second);
+                keyring->keybygrip.erase(it);
+                continue;
+            }
+            it->second->primary_grip = {};
+            it->second->primary_grip_set = false;
+        }
+    }
+    if (pgp_key_is_subkey(key) && pgp_key_has_primary_grip(key)) {
+        pgp_key_t *primary = rnp_key_store_get_primary_key(keyring, key);
+        if (primary) {
+            pgp_key_remove_subkey_grip(primary, pgp_key_get_grip(key));
+        }
+    }
+
+    keyring->keys.erase(it->second);
+    keyring->keybygrip.erase(it);
+    return true;
 }
 
 /**
    \ingroup HighLevel_KeyringFind
 
    \brief Finds key in keyring from its Key ID
 
    \param keyring Keyring to be searched
@@ -726,22 +745,21 @@ rnp_key_store_get_key_by_id(rnp_key_stor
                  pgp_key_get_keyid(&key) + PGP_KEY_ID_SIZE / 2, keyid, PGP_KEY_ID_SIZE / 2);
     });
     return (it == keyring->keys.end()) ? NULL : &(*it);
 }
 
 const pgp_key_t *
 rnp_key_store_get_key_by_grip(const rnp_key_store_t *keyring, const pgp_key_grip_t &grip)
 {
-    try {
-        return &*keyring->keybygrip.at(grip);
-    } catch (const std::exception &e) {
-        RNP_LOG("%s", e.what());
+    auto it = keyring->keybygrip.find(grip);
+    if (it == keyring->keybygrip.end()) {
         return NULL;
     }
+    return &*it->second;
 }
 
 pgp_key_t *
 rnp_key_store_get_key_by_grip(rnp_key_store_t *keyring, const pgp_key_grip_t &grip)
 {
     auto it = keyring->keybygrip.find(grip);
     if (it == keyring->keybygrip.end()) {
         return NULL;
--- a/third_party/rnp/src/librepgp/stream-parse.cpp
+++ b/third_party/rnp/src/librepgp/stream-parse.cpp
@@ -77,51 +77,53 @@ typedef struct pgp_source_packet_param_t
     bool          partial;                  /* partial length packet */
     bool          indeterminate;            /* indeterminate length packet */
     uint8_t       hdr[PGP_MAX_HEADER_SIZE]; /* PGP packet header, needed for AEAD */
     size_t        hdrlen;                   /* length of the header */
     size_t        len; /* packet body length if non-partial and non-indeterminate */
 } pgp_source_packet_param_t;
 
 typedef struct pgp_source_encrypted_param_t {
-    pgp_source_packet_param_t pkt;            /* underlying packet-related params */
-    list                      symencs;        /* array of sym-encrypted session keys */
-    list                      pubencs;        /* array of pk-encrypted session keys */
-    bool                      has_mdc;        /* encrypted with mdc, i.e. tag 18 */
-    bool                      mdc_validated;  /* mdc was validated already */
-    bool                      aead;           /* AEAD encrypted data packet, tag 20 */
-    bool                      aead_validated; /* we read and validated last chunk */
-    pgp_crypt_t               decrypt;        /* decrypting crypto */
-    pgp_hash_t                mdc;            /* mdc SHA1 hash */
-    size_t                    chunklen;       /* size of AEAD chunk in bytes */
-    size_t                    chunkin;        /* number of bytes read from the current chunk */
-    size_t                    chunkidx;       /* index of the current chunk */
-    uint8_t                   cache[PGP_AEAD_CACHE_LEN]; /* read cache */
-    size_t                    cachelen;                  /* number of bytes in the cache */
-    size_t                    cachepos; /* index of first unread byte in the cache */
-    pgp_aead_hdr_t            aead_hdr; /* AEAD encryption parameters */
-    uint8_t                   aead_ad[PGP_AEAD_MAX_AD_LEN]; /* additional data */
-    size_t                    aead_adlen;                   /* length of the additional data */
+    pgp_source_packet_param_t     pkt;            /* underlying packet-related params */
+    std::vector<pgp_sk_sesskey_t> symencs;        /* array of sym-encrypted session keys */
+    std::vector<pgp_pk_sesskey_t> pubencs;        /* array of pk-encrypted session keys */
+    bool                          has_mdc;        /* encrypted with mdc, i.e. tag 18 */
+    bool                          mdc_validated;  /* mdc was validated already */
+    bool                          aead;           /* AEAD encrypted data packet, tag 20 */
+    bool                          aead_validated; /* we read and validated last chunk */
+    pgp_crypt_t                   decrypt;        /* decrypting crypto */
+    pgp_hash_t                    mdc;            /* mdc SHA1 hash */
+    size_t                        chunklen;       /* size of AEAD chunk in bytes */
+    size_t                        chunkin;  /* number of bytes read from the current chunk */
+    size_t                        chunkidx; /* index of the current chunk */
+    uint8_t                       cache[PGP_AEAD_CACHE_LEN]; /* read cache */
+    size_t                        cachelen;                  /* number of bytes in the cache */
+    size_t                        cachepos; /* index of first unread byte in the cache */
+    pgp_aead_hdr_t                aead_hdr; /* AEAD encryption parameters */
+    uint8_t                       aead_ad[PGP_AEAD_MAX_AD_LEN]; /* additional data */
+    size_t                        aead_adlen; /* length of the additional data */
+    pgp_symm_alg_t                salg;       /* data encryption algorithm */
+    pgp_parse_handler_t *         handler;    /* parsing handler with callbacks */
 } pgp_source_encrypted_param_t;
 
 typedef struct pgp_source_signed_param_t {
-    pgp_processing_ctx_t *ctx;             /* processing context */
-    pgp_source_t *        readsrc;         /* source to read from */
-    bool                  detached;        /* detached signature */
-    bool                  cleartext;       /* source is cleartext signed */
-    bool                  clr_eod;         /* cleartext data is over */
-    bool                  clr_fline;       /* first line of the cleartext */
-    bool                  clr_mline;       /* in the middle of the very long line */
-    uint8_t               out[CT_BUF_LEN]; /* cleartext output cache for easier parsing */
-    size_t                outlen;          /* total bytes in out */
-    size_t                outpos;          /* offset of first available byte in out */
-    list                  onepasses;       /* list of one-pass singatures */
-    list                  sigs;            /* list of signatures */
-    list                  hashes;          /* hash contexts */
-    list                  siginfos;        /* signature validation info */
+    pgp_parse_handler_t *handler;         /* parsing handler with callbacks */
+    pgp_source_t *       readsrc;         /* source to read from */
+    bool                 detached;        /* detached signature */
+    bool                 cleartext;       /* source is cleartext signed */
+    bool                 clr_eod;         /* cleartext data is over */
+    bool                 clr_fline;       /* first line of the cleartext */
+    bool                 clr_mline;       /* in the middle of the very long line */
+    uint8_t              out[CT_BUF_LEN]; /* cleartext output cache for easier parsing */
+    size_t               outlen;          /* total bytes in out */
+    size_t               outpos;          /* offset of first available byte in out */
+    list                 onepasses;       /* list of one-pass singatures */
+    list                 sigs;            /* list of signatures */
+    list                 hashes;          /* hash contexts */
+    list                 siginfos;        /* signature validation info */
 } pgp_source_signed_param_t;
 
 typedef struct pgp_source_compressed_param_t {
     pgp_source_packet_param_t pkt; /* underlying packet-related params */
     pgp_compression_type_t    alg;
     union {
         z_stream  z;
         bz_stream bz;
@@ -681,16 +683,23 @@ encrypted_src_read_cfb(pgp_source_t *src
     return true;
 }
 
 static rnp_result_t
 encrypted_src_finish(pgp_source_t *src)
 {
     pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param;
 
+    /* report to the handler that decryption is finished */
+    if (param->handler->on_decryption_done) {
+        bool validated =
+          (param->has_mdc && param->mdc_validated) || (param->aead && param->aead_validated);
+        param->handler->on_decryption_done(validated, param->handler->param);
+    }
+
     if (param->aead) {
         if (!param->aead_validated) {
             RNP_LOG("aead last chunk was not validated");
             return RNP_ERROR_BAD_STATE;
         }
         return RNP_SUCCESS;
     }
 
@@ -705,18 +714,19 @@ encrypted_src_finish(pgp_source_t *src)
 static void
 encrypted_src_close(pgp_source_t *src)
 {
     pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param;
     if (!param) {
         return;
     }
 
-    list_destroy(&param->symencs);
-    list_destroy(&param->pubencs);
+    /* to be removed once pgp_source_t is migrated to C++ */
+    param->symencs.~vector();
+    param->pubencs.~vector();
 
     if (param->pkt.partial) {
         src_close(param->pkt.readsrc);
         free(param->pkt.readsrc);
         param->pkt.readsrc = NULL;
     }
 
     if (param->aead) {
@@ -925,50 +935,50 @@ signed_src_finish(pgp_source_t *src)
         /* Get the key id */
         if (!signature_get_keyid(sinfo->sig, keyctx.search.by.keyid)) {
             RNP_LOG("cannot get signer's key id from signature");
             sinfo->unknown = true;
             continue;
         }
 
         /* Get the public key */
-        if (!(key = pgp_request_key(param->ctx->handler.key_provider, &keyctx))) {
+        if (!(key = pgp_request_key(param->handler->key_provider, &keyctx))) {
             // fallback to secret key
             keyctx.secret = true;
-            if (!(key = pgp_request_key(param->ctx->handler.key_provider, &keyctx))) {
+            if (!(key = pgp_request_key(param->handler->key_provider, &keyctx))) {
                 RNP_LOG("signer's key not found");
                 sinfo->no_signer = true;
                 continue;
             }
         }
         sinfo->signer = key;
         /* validate signature */
         signed_validate_signature(param, sinfo);
     }
 
     /* checking the validation results */
     ret = RNP_SUCCESS;
     for (list_item *si = list_front(param->siginfos); si; si = list_next(si)) {
         sinfo = (pgp_signature_info_t *) si;
         sinfos[sinfoc++] = *sinfo;
 
-        if (sinfo->no_signer && param->ctx->handler.ctx->discard) {
+        if (sinfo->no_signer && param->handler->ctx->discard) {
             /* if output is discarded then we interested in verification */
             ret = RNP_ERROR_SIGNATURE_INVALID;
             continue;
         }
         if (!sinfo->no_signer && (!sinfo->valid || (sinfo->expired))) {
             /* do not report error if signer not found */
             ret = RNP_ERROR_SIGNATURE_INVALID;
         }
     }
 
     /* call the callback with signature infos */
-    if (param->ctx->handler.on_signatures) {
-        param->ctx->handler.on_signatures(sinfos, sinfoc, param->ctx->handler.param);
+    if (param->handler->on_signatures) {
+        param->handler->on_signatures(sinfos, sinfoc, param->handler->param);
     }
 
     free(sinfos);
     return ret;
 }
 
 /*
  * str is a string to tokenize.
@@ -1360,17 +1370,19 @@ encrypted_try_key(pgp_source_encrypted_p
 
     if (!param->aead) {
         /* Decrypt header */
         res = encrypted_decrypt_cfb_header(param, salg, &decbuf[1]);
     } else {
         /* Start AEAD decrypting, assuming we have correct key */
         res = encrypted_start_aead(param, salg, &decbuf[1]);
     }
-
+    if (res) {
+        param->salg = salg;
+    }
 finish:
     pgp_forget(&checksum, sizeof(checksum));
     pgp_forget(decbuf, sizeof(decbuf));
 
     return res;
 }
 
 static bool
@@ -1395,83 +1407,81 @@ encrypted_try_password(pgp_source_encryp
     pgp_symm_alg_t alg;
     uint8_t        keybuf[PGP_MAX_KEY_SIZE + 1];
     uint8_t        nonce[PGP_AEAD_MAX_NONCE_LEN];
     size_t         keysize;
     bool           keyavail = false; /* tried password at least once */
     bool           decres;
     int            res;
 
-    for (list_item *se = list_front(param->symencs); se; se = list_next(se)) {
+    for (auto &skey : param->symencs) {
         /* deriving symmetric key from password */
-        pgp_sk_sesskey_t *skey = (pgp_sk_sesskey_t *) se;
-
-        keysize = pgp_key_size(skey->alg);
-        if (!keysize || !pgp_s2k_derive_key(&skey->s2k, password, keybuf, keysize)) {
+        keysize = pgp_key_size(skey.alg);
+        if (!keysize || !pgp_s2k_derive_key(&skey.s2k, password, keybuf, keysize)) {
             continue;
         }
         RNP_DHEX("derived key: ", keybuf, keysize);
 
-        if (skey->version == PGP_SKSK_V4) {
+        if (skey.version == PGP_SKSK_V4) {
             /* v4 symmetrically-encrypted session key */
-            if (skey->enckeylen > 0) {
+            if (skey.enckeylen > 0) {
                 /* decrypting session key */
-                if (!pgp_cipher_cfb_start(&crypt, skey->alg, keybuf, NULL)) {
+                if (!pgp_cipher_cfb_start(&crypt, skey.alg, keybuf, NULL)) {
                     continue;
                 }
 
-                pgp_cipher_cfb_decrypt(&crypt, keybuf, skey->enckey, skey->enckeylen);
+                pgp_cipher_cfb_decrypt(&crypt, keybuf, skey.enckey, skey.enckeylen);
                 pgp_cipher_cfb_finish(&crypt);
 
                 alg = (pgp_symm_alg_t) keybuf[0];
                 keysize = pgp_key_size(alg);
-                if (!keysize || (keysize + 1 != skey->enckeylen)) {
+                if (!keysize || (keysize + 1 != skey.enckeylen)) {
                     continue;
                 }
                 memmove(keybuf, keybuf + 1, keysize);
             } else {
-                alg = (pgp_symm_alg_t) skey->alg;
+                alg = (pgp_symm_alg_t) skey.alg;
             }
 
             if (!pgp_block_size(alg)) {
                 continue;
             }
 
             keyavail = true;
-        } else if (skey->version == PGP_SKSK_V5) {
+        } else if (skey.version == PGP_SKSK_V5) {
             /* v5 AEAD-encrypted session key */
-            size_t taglen = pgp_cipher_aead_tag_len(skey->aalg);
+            size_t taglen = pgp_cipher_aead_tag_len(skey.aalg);
             size_t noncelen;
 
-            if (!taglen || (keysize != skey->enckeylen - taglen)) {
+            if (!taglen || (keysize != skey.enckeylen - taglen)) {
                 continue;
             }
-            alg = skey->alg;
+            alg = skey.alg;
 
             /* initialize cipher */
-            if (!pgp_cipher_aead_init(&crypt, skey->alg, skey->aalg, keybuf, true)) {
+            if (!pgp_cipher_aead_init(&crypt, skey.alg, skey.aalg, keybuf, true)) {
                 continue;
             }
 
             /* set additional data */
-            if (!encrypted_sesk_set_ad(&crypt, skey)) {
+            if (!encrypted_sesk_set_ad(&crypt, &skey)) {
                 RNP_LOG("failed to set ad");
                 continue;
             }
 
             /* calculate nonce */
-            noncelen = pgp_cipher_aead_nonce(skey->aalg, skey->iv, nonce, 0);
+            noncelen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0);
 
             RNP_DHEX("nonce: ", nonce, noncelen);
-            RNP_DHEX("encrypted key: ", skey->enckey, skey->enckeylen);
+            RNP_DHEX("encrypted key: ", skey.enckey, skey.enckeylen);
 
             /* start cipher, decrypt key and verify tag */
             keyavail = pgp_cipher_aead_start(&crypt, nonce, noncelen);
-            decres = keyavail &&
-                     pgp_cipher_aead_finish(&crypt, keybuf, skey->enckey, skey->enckeylen);
+            decres =
+              keyavail && pgp_cipher_aead_finish(&crypt, keybuf, skey.enckey, skey.enckeylen);
 
             if (decres) {
                 RNP_DHEX("decrypted key: ", keybuf, pgp_key_size(param->aead_hdr.ealg));
             }
 
             pgp_cipher_aead_destroy(&crypt);
 
             /* we have decrypted key so let's start decryption */
@@ -1485,17 +1495,22 @@ encrypted_try_password(pgp_source_encryp
         /* Decrypt header for CFB */
         if (!param->aead && !encrypted_decrypt_cfb_header(param, alg, keybuf)) {
             continue;
         }
         if (param->aead && !encrypted_start_aead(param, param->aead_hdr.ealg, keybuf)) {
             continue;
         }
 
+        param->salg = param->aead ? param->aead_hdr.ealg : alg;
         res = 1;
+        /* inform handler that we used this symenc */
+        if (param->handler->on_decryption_start) {
+            param->handler->on_decryption_start(NULL, &skey, param->handler->param);
+        }
         goto finish;
     }
 
     if (!keyavail) {
         RNP_LOG("no supported sk available");
         res = -1;
         goto finish;
     }
@@ -1769,26 +1784,30 @@ encrypted_read_packet_data(pgp_source_en
             return RNP_ERROR_READ;
         }
         ptype = get_packet_type(ptag);
 
         if (ptype == PGP_PKT_SK_SESSION_KEY) {
             if ((errcode = stream_parse_sk_sesskey(param->pkt.readsrc, &skey))) {
                 return errcode;
             }
-
-            if (!list_append(&param->symencs, &skey, sizeof(skey))) {
+            try {
+                param->symencs.push_back(skey);
+            } catch (const std::exception &e) {
+                RNP_LOG("%s", e.what());
                 return RNP_ERROR_OUT_OF_MEMORY;
             }
         } else if (ptype == PGP_PKT_PK_SESSION_KEY) {
             if ((errcode = stream_parse_pk_sesskey(param->pkt.readsrc, &pkey))) {
                 return errcode;
             }
-
-            if (!list_append(&param->pubencs, &pkey, sizeof(pkey))) {
+            try {
+                param->pubencs.push_back(pkey);
+            } catch (const std::exception &e) {
+                RNP_LOG("%s", e.what());
                 return RNP_ERROR_OUT_OF_MEMORY;
             }
         } else if ((ptype == PGP_PKT_SE_DATA) || (ptype == PGP_PKT_SE_IP_DATA) ||
                    (ptype == PGP_PKT_AEAD_ENCRYPTED)) {
             break;
         } else {
             RNP_LOG("unknown packet type: %d", ptype);
             return RNP_ERROR_BAD_FORMAT;
@@ -1844,114 +1863,120 @@ encrypted_read_packet_data(pgp_source_en
         param->has_mdc = true;
         param->mdc_validated = false;
     }
 
     return RNP_SUCCESS;
 }
 
 static rnp_result_t
-init_encrypted_src(pgp_processing_ctx_t *ctx, pgp_source_t *src, pgp_source_t *readsrc)
+init_encrypted_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc)
 {
     rnp_result_t                  errcode = RNP_ERROR_GENERIC;
     pgp_source_encrypted_param_t *param;
     pgp_key_t *                   seckey = NULL;
-    pgp_key_request_ctx_t         keyctx;
     pgp_key_pkt_t *               decrypted_seckey = NULL;
     char                          password[MAX_PASSWORD_LENGTH] = {0};
     int                           intres;
     bool                          have_key = false;
 
     if (!init_src_common(src, sizeof(*param))) {
         return RNP_ERROR_OUT_OF_MEMORY;
     }
     param = (pgp_source_encrypted_param_t *) src->param;
     param->pkt.readsrc = readsrc;
+    param->handler = handler;
 
     src->close = encrypted_src_close;
     src->finish = encrypted_src_finish;
     src->type = PGP_STREAM_ENCRYPTED;
 
     /* Read the packet-related information */
     errcode = encrypted_read_packet_data(param);
     if (errcode != RNP_SUCCESS) {
         goto finish;
     }
 
     src->read = param->aead ? encrypted_src_read_aead : encrypted_src_read_cfb;
 
     /* Obtaining the symmetric key */
     have_key = false;
 
-    if (!ctx->handler.password_provider) {
+    if (!handler->password_provider) {
         RNP_LOG("no password provider");
         errcode = RNP_ERROR_BAD_PARAMETERS;
         goto finish;
     }
 
+    /* informing handler about the available pubencs/symencs */
+    if (handler->on_recipients) {
+        handler->on_recipients(param->pubencs, param->symencs, handler->param);
+    }
+
     /* Trying public-key decryption */
-    if (list_length(param->pubencs) > 0) {
-        if (!ctx->handler.key_provider) {
+    if (!param->pubencs.empty()) {
+        if (!handler->key_provider) {
             RNP_LOG("no key provider");
             errcode = RNP_ERROR_BAD_PARAMETERS;
             goto finish;
         }
 
+        pgp_key_request_ctx_t keyctx = {};
         keyctx.op = PGP_OP_DECRYPT_SYM;
         keyctx.secret = true;
         keyctx.search.type = PGP_KEY_SEARCH_KEYID;
 
-        for (list_item *pe = list_front(param->pubencs); pe; pe = list_next(pe)) {
-            memcpy(keyctx.search.by.keyid,
-                   ((pgp_pk_sesskey_t *) pe)->key_id,
-                   sizeof(keyctx.search.by.keyid));
+        for (auto &pubenc : param->pubencs) {
+            memcpy(keyctx.search.by.keyid, pubenc.key_id, sizeof(keyctx.search.by.keyid));
             /* Get the key if any */
-            if (!(seckey = pgp_request_key(ctx->handler.key_provider, &keyctx))) {
+            if (!(seckey = pgp_request_key(handler->key_provider, &keyctx))) {
                 errcode = RNP_ERROR_NO_SUITABLE_KEY;
                 continue;
             }
             /* Decrypt key */
             if (pgp_key_is_encrypted(seckey)) {
                 pgp_password_ctx_t pass_ctx{.op = PGP_OP_DECRYPT, .key = seckey};
                 decrypted_seckey =
-                  pgp_decrypt_seckey(seckey, ctx->handler.password_provider, &pass_ctx);
+                  pgp_decrypt_seckey(seckey, handler->password_provider, &pass_ctx);
                 if (!decrypted_seckey) {
                     errcode = RNP_ERROR_BAD_PASSWORD;
                     continue;
                 }
             } else {
                 decrypted_seckey = &(seckey->pkt);
             }
 
             /* Try to initialize the decryption */
-            if (encrypted_try_key(param,
-                                  (pgp_pk_sesskey_t *) pe,
-                                  decrypted_seckey,
-                                  rnp_ctx_rng_handle(ctx->handler.ctx))) {
+            if (encrypted_try_key(
+                  param, &pubenc, decrypted_seckey, rnp_ctx_rng_handle(handler->ctx))) {
                 have_key = true;
+                /* inform handler that we used this pubenc */
+                if (handler->on_decryption_start) {
+                    handler->on_decryption_start(&pubenc, NULL, handler->param);
+                }
             }
 
             /* Destroy decrypted key */
             if (pgp_key_is_encrypted(seckey)) {
                 free_key_pkt(decrypted_seckey);
                 free(decrypted_seckey);
                 decrypted_seckey = NULL;
             }
 
             if (have_key) {
                 break;
             }
         }
     }
 
     /* Trying password-based decryption */
-    if (!have_key && (list_length(param->symencs) > 0)) {
+    if (!have_key && !param->symencs.empty()) {
         pgp_password_ctx_t pass_ctx{.op = PGP_OP_DECRYPT_SYM, .key = NULL};
         if (!pgp_request_password(
-              ctx->handler.password_provider, &pass_ctx, password, sizeof(password))) {
+              handler->password_provider, &pass_ctx, password, sizeof(password))) {
             errcode = RNP_ERROR_BAD_PASSWORD;
             goto finish;
         }
 
         intres = encrypted_try_password(param, password);
         if (intres > 0) {
             have_key = true;
         } else if (intres < 0) {
@@ -1964,23 +1989,27 @@ init_encrypted_src(pgp_processing_ctx_t 
     if (!have_key) {
         RNP_LOG("failed to obtain decrypting key or password");
         if (!errcode) {
             errcode = RNP_ERROR_NO_SUITABLE_KEY;
         }
         goto finish;
     }
 
+    /* report decryption start to the handler */
+    if (handler->on_decryption_info) {
+        handler->on_decryption_info(
+          param->has_mdc, param->aead_hdr.aalg, param->salg, handler->param);
+    }
     errcode = RNP_SUCCESS;
 finish:
     if (errcode != RNP_SUCCESS) {
         src_close(src);
     }
     pgp_forget(password, sizeof(password));
-
     return errcode;
 }
 
 static rnp_result_t
 init_cleartext_signed_src(pgp_source_t *src)
 {
     char                       buf[64];
     size_t                     hdrlen = strlen(ST_CLEAR_BEGIN);
@@ -2009,17 +2038,17 @@ init_cleartext_signed_src(pgp_source_t *
     }
 
     /* now we are good to go */
     param->clr_fline = true;
     return RNP_SUCCESS;
 }
 
 static rnp_result_t
-init_signed_src(pgp_processing_ctx_t *ctx, pgp_source_t *src, pgp_source_t *readsrc)
+init_signed_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc)
 {
     rnp_result_t               errcode = RNP_ERROR_GENERIC;
     pgp_source_signed_param_t *param;
     uint8_t                    ptag;
     int                        ptype;
     pgp_one_pass_sig_t         onepass = {0};
     pgp_signature_t *          sig = NULL;
     bool                       cleartext;
@@ -2027,25 +2056,25 @@ init_signed_src(pgp_processing_ctx_t *ct
     if (!init_src_common(src, sizeof(*param))) {
         return RNP_ERROR_OUT_OF_MEMORY;
     }
 
     cleartext = is_cleartext_source(readsrc);
 
     param = (pgp_source_signed_param_t *) src->param;
     param->readsrc = readsrc;
-    param->ctx = ctx;
+    param->handler = handler;
     param->cleartext = cleartext;
     src->read = cleartext ? cleartext_src_read : signed_src_read;
     src->close = signed_src_close;
     src->finish = signed_src_finish;
     src->type = cleartext ? PGP_STREAM_CLEARTEXT : PGP_STREAM_SIGNED;
 
     /* we need key provider to validate signatures */
-    if (!ctx->handler.key_provider) {
+    if (!handler->key_provider) {
         RNP_LOG("no key provider");
         errcode = RNP_ERROR_BAD_PARAMETERS;
         goto finish;
     }
 
     if (cleartext) {
         errcode = init_cleartext_signed_src(src);
         goto finish;
@@ -2158,21 +2187,21 @@ init_packet_sequence(pgp_processing_ctx_
             return RNP_ERROR_BAD_FORMAT;
         }
 
         memset(&psrc, 0, sizeof(psrc));
 
         switch (type) {
         case PGP_PKT_PK_SESSION_KEY:
         case PGP_PKT_SK_SESSION_KEY:
-            ret = init_encrypted_src(ctx, &psrc, lsrc);
+            ret = init_encrypted_src(&ctx->handler, &psrc, lsrc);
             break;
         case PGP_PKT_ONE_PASS_SIG:
         case PGP_PKT_SIGNATURE:
-            ret = init_signed_src(ctx, &psrc, lsrc);
+            ret = init_signed_src(&ctx->handler, &psrc, lsrc);
             break;
         case PGP_PKT_COMPRESSED:
             ret = init_compressed_src(&psrc, lsrc);
             break;
         case PGP_PKT_LITDATA:
             if ((lsrc->type != PGP_STREAM_ENCRYPTED) && (lsrc->type != PGP_STREAM_SIGNED) &&
                 (lsrc->type != PGP_STREAM_COMPRESSED)) {
                 RNP_LOG("unexpected literal pkt");
@@ -2212,17 +2241,17 @@ init_packet_sequence(pgp_processing_ctx_
 }
 
 static rnp_result_t
 init_cleartext_sequence(pgp_processing_ctx_t *ctx, pgp_source_t *src)
 {
     pgp_source_t clrsrc = {0};
     rnp_result_t res;
 
-    if ((res = init_signed_src(ctx, &clrsrc, src))) {
+    if ((res = init_signed_src(&ctx->handler, &clrsrc, src))) {
         return res;
     }
 
     if (!list_append(&ctx->sources, &clrsrc, sizeof(clrsrc))) {
         RNP_LOG("allocation failed");
         return RNP_ERROR_OUT_OF_MEMORY;
     }
 
--- a/third_party/rnp/src/librepgp/stream-parse.h
+++ b/third_party/rnp/src/librepgp/stream-parse.h
@@ -38,26 +38,42 @@ typedef struct pgp_parse_handler_t  pgp_
 typedef struct pgp_signature_info_t pgp_signature_info_t;
 typedef bool                        pgp_destination_func_t(pgp_parse_handler_t *handler,
                                                            pgp_dest_t **        dst,
                                                            bool *               closedst,
                                                            const char *         filename);
 typedef bool pgp_source_func_t(pgp_parse_handler_t *handler, pgp_source_t *src);
 typedef void pgp_signatures_func_t(pgp_signature_info_t *sigs, int count, void *param);
 
+typedef void pgp_on_recipients_func_t(const std::vector<pgp_pk_sesskey_t> &recipients,
+                                      const std::vector<pgp_sk_sesskey_t> &passwords,
+                                      void *                               param);
+typedef void pgp_decryption_start_func_t(pgp_pk_sesskey_t *pubenc,
+                                         pgp_sk_sesskey_t *symenc,
+                                         void *            param);
+typedef void pgp_decryption_info_func_t(bool           mdc,
+                                        pgp_aead_alg_t aead,
+                                        pgp_symm_alg_t salg,
+                                        void *         param);
+typedef void pgp_decryption_done_func_t(bool validated, void *param);
+
 /* handler used to return needed information during pgp source processing */
 typedef struct pgp_parse_handler_t {
     pgp_password_provider_t *password_provider; /* if NULL then default will be used */
     pgp_key_provider_t *     key_provider; /* must be set when key is required, i.e. during
                                               signing/verification/public key encryption and
                                               deryption */
     pgp_destination_func_t *dest_provider; /* called when destination stream is required */
     pgp_source_func_t *     src_provider;  /* required to provider source during the detached
                                               signature verification */
-    pgp_signatures_func_t *on_signatures;  /* for signature verification results */
+    pgp_on_recipients_func_t *   on_recipients;       /* called before decryption start */
+    pgp_decryption_start_func_t *on_decryption_start; /* called when decryption key obtained */
+    pgp_decryption_info_func_t * on_decryption_info;  /* called when decryption is started */
+    pgp_decryption_done_func_t * on_decryption_done;  /* called when decryption is finished */
+    pgp_signatures_func_t *      on_signatures;       /* for signature verification results */
 
     rnp_ctx_t *ctx;   /* operation context */
     void *     param; /* additional parameters */
 } pgp_parse_handler_t;
 
 /* @brief Process the OpenPGP source: file, memory, stdin
  * Function will parse input data, provided by any source conforming to pgp_source_t,
  * autodetecting whether it is armored, cleartext or binary.
--- a/third_party/rnp/src/rnp/CMakeLists.txt
+++ b/third_party/rnp/src/rnp/CMakeLists.txt
@@ -35,15 +35,19 @@ target_include_directories(rnp
 )
 
 target_link_libraries(rnp
   PRIVATE
     librnp
     JSON-C::JSON-C
 )
 
+if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+  target_link_libraries(rnp PRIVATE regex)
+endif()
+
 include(GNUInstallDirs)
 install(TARGETS rnp
   RUNTIME
     DESTINATION "${CMAKE_INSTALL_BINDIR}"
     COMPONENT cli
 )
 
--- a/third_party/rnp/src/rnp/fficli.cpp
+++ b/third_party/rnp/src/rnp/fficli.cpp
@@ -41,16 +41,17 @@
 #ifndef _WIN32
 #include <termios.h>
 #include <sys/resource.h>
 #endif
 
 #include <time.h>
 #include "config.h"
 #include "fficli.h"
+#include "utils.h"
 
 // must be placed after include "utils.h"
 #ifndef RNP_USE_STD_REGEX
 #include <regex.h>
 #else
 #include <regex>
 #endif
 
--- a/third_party/rnp/src/rnp/fficli.h
+++ b/third_party/rnp/src/rnp/fficli.h
@@ -147,11 +147,9 @@ bool        rnp_casecmp(const std::strin
         (void) fprintf((stderr), "\n");        \
     } while (0)
 
 #define EXT_ASC (".asc")
 #define EXT_SIG (".sig")
 #define EXT_PGP (".pgp")
 #define EXT_GPG (".gpg")
 
-char *rnp_strip_eol(char *s);
-
 #endif
--- a/third_party/rnp/src/rnpkeys/CMakeLists.txt
+++ b/third_party/rnp/src/rnpkeys/CMakeLists.txt
@@ -41,14 +41,18 @@ target_include_directories(rnpkeys
 )
 
 target_link_libraries(rnpkeys
   PRIVATE
     librnp
     JSON-C::JSON-C
 )
 
+if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+  target_link_libraries(rnpkeys PRIVATE regex)
+endif()
+
 include(GNUInstallDirs)
 install(TARGETS rnpkeys
   RUNTIME
     DESTINATION "${CMAKE_INSTALL_BINDIR}"
     COMPONENT cli
 )
--- a/third_party/rnp/src/rnpkeys/tui.cpp
+++ b/third_party/rnp/src/rnpkeys/tui.cpp
@@ -1,13 +1,14 @@
 #include <unistd.h>
 #include <errno.h>
 #include "rnp/rnpcfg.h"
 #include "rnpkeys.h"
 #include "defaults.h"
+#include "utils.h"
 
 /* -----------------------------------------------------------------------------
  * @brief   Reads input from file pointer and converts it securelly to ints
  *          Partially based on ERR34-C from SEI CERT C Coding Standarad
  *
  * @param   fp          pointer to opened pipe
  * @param   result[out] result read from file pointer and converted to int
  *
--- a/third_party/rnp/src/tests/CMakeLists.txt
+++ b/third_party/rnp/src/tests/CMakeLists.txt
@@ -89,31 +89,34 @@ add_executable(rnp_tests
   streams.cpp
   support.cpp
   user-prefs.cpp
   utils-hex2bin.cpp
   utils-list.cpp
   utils-rnpcfg.cpp
   issues/1030.cpp
   issues/1115.cpp
+  issues/1171.cpp
 )
 
 target_include_directories(rnp_tests
   PRIVATE
     "${PROJECT_SOURCE_DIR}/src"
     "${PROJECT_SOURCE_DIR}/src/lib"
 )
 
 target_link_libraries(rnp_tests
   PRIVATE
     librnp
     JSON-C::JSON-C
     gtest_main
 )
-
+if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+  target_link_libraries(rnp_tests PRIVATE regex)
+endif()
 target_compile_definitions(rnp_tests
   PRIVATE
     RNP_RUN_TESTS
 )
 
 if (WIN32)
   add_custom_command(TARGET rnp_tests POST_BUILD
     COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:librnp>" "$<TARGET_FILE_DIR:rnp_tests>")
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f3d3b1bd416079356d87d8e133044e2233d9750c
GIT binary patch
literal 826
zc$@(`1I7Hoi~}ClKILz%!%YDL|9U<q$wT1YqH-_^(btK0Oz#cxW#7T{%OQjsGRuB<
zXu%;T8e#|Ca`D=JJv<iXQ_jL{re+Jq-R;6*{Z*#M3p{G)wzbgVnkEDv0qcu^5V+kT
zS)u=C%;hfsw>|f=dA-Wzz#d<cqlqNmX(B!aUlTtsOrq60qgp|aR3-xZg29XfiUqiz
zty<pE0R#VZQ*{+rI!|CTpI$DbpcyBUPWV7$<v9kg!AORk?Rr=D)I?j?WgJxP_nviF
zwqe>1Mf8wy$o`hN+0tG1VGlE(sZ=GsVDQpY%l!>NqZf^tyy8$AeWzdvpy_?z(Rb{z
zsBxPP=0(C%xv!q3C3y2vX*!H4UFk0oYiVBTc@Fo%z)k~HP+F5}N|z201OSa*OH?O9
zFT#wlZH_iLC4EwbT*y0mEi;=B^fT&BpX}R<Q^u@hLk<&=^Hq=<v7!$vDDAeD=kODW
zLzZ*Zi(;+EBdq!%UX8a#s;?>A+ps!%+q)l{hb<2EB9nH~4RIU1LrXHKV`}EZOwDGi
z+iiJ-EU9N9E56{CQ43|dn(qVtCCEup%Ff35sDocS^hUO=18#|`V2=o<CEJE>ppXVv
zR6S&wl~NLa&z^NAq0IZZUf#-eOQu$O8a$;K-+NmGwAiGNHki*b34!KgzQr?Pc#;0H
zmleb;(_4!OWI-y~gVTjh@9<|E_S2ul6uRQrUdwKkEZHwYm^=#UDpS51!!85~0|>uD
z7C&CT6j{ErZ<G_5A#D`m64pLur#f;#&Wqt(!y$HDS-33rl|bHR!!85~0|<s$Fzjf0
zI!(FbnCI{d<<8x2134{FgjrDr3UYuMTx%F)0VE`M7qQPH(yIaFr<fFj43X$Zpo{1%
z9In5h6a&77M+@EzRZ(zde-MXXE#f08;y<RePY1GBI>5}rYAH?Z(W|x_lOA9KVr(Va
z_<5`u+`LXjZ=V66iHF_0qkOUfo<ZB*0q{pKq+SNL)Tz-g0BV<EQ*weex>IPc5i=Az
z{EtAjb0v`ztZGv*jLIz7LA2*1a$K7~8t&A4K@jlAe7)Dg`{ae3xxpt3S(rm<&L3Em
E^;Jxa*#H0l
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9935fa93efb3b631cd4ab05dfacc2e39a3b0a058
GIT binary patch
literal 468
zc$@*$0W1E&i~}ClKILz%!%YDM0I|9s?v0q5_O<^7>TA1hTqewCS(eRlthc#7nD!MI
ztj89y4d9PTDVh-7O@{vDuk{UXWv?4pu_-OCD5v0^F=y5m9AX7e1R$aMU5lK~f6d@a
zgW}zV*cLw=7#2Di|NIeoYvjwMD@KAO*JIJEAzJl7TR9}OCxG&`_jF{|OT$hD2>}BL
z;6nw5B#D5ez7{P;j-Ri&n)_en<8yx4zO%Qzw<Z_i@$mom#)_<oA<q4>QUOEAjG5sq
zeXzxAcAe3NrqnSxR$^9As*?qD#?-(w0SN&BG!O(o+`zQ9!hQd;;PTa2bOc=_;WJE7
z8MSWtaYyw*%U0T*3*F4VT)gU^)?HyKlgKw5VGKSV_<<I<zklf&wVq9d79ukOvIKq2
z;!;A)Eu(pE!gFD%>UvpO^7ivOzGf?Tn|hOjjz8aC>}40oi-`x(>T;F5IkKffXcE?E
zWpCQJGhkzom`+|h5w_{ZET!>>qt&7OZNP=r5hNz0G>y?~{C@-QM28n%K<vW~hGLc^
zc(&D9581$|9Xy~xy%vOxomPHPX|-ChM%g62A$@~wc6*3p8=woM2um7`G7jIkw~8Ud
KgRrRSk1*tB6zF6C
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..132d15f726aa615947452fb58e908db55b9ec099
GIT binary patch
literal 468
zc$@*$0W1E&i~}ClKILz%!%YDM0I|9s?v0q5_O<^7>TA1hTqewCS(eRlthc#7nD!MI
ztj89y4d9PTDVh-7O@{vDuk{UXWv?4pu_-OCD5v0^F=y5m9AX7e1R$aMU5lK~f6d@a
zgW}zV*cLw=7#2Di|NIeoYvjwMD@KAO*JIJEAzJl7TR9}OCxG&`_jF{|OT$hD2>}BL
z;6nw5B#D5ez7{P;j-Ri&n)_en<8yx4zO%Qzw<Z_i@$mom#)_<oA<q4>QUOEAjG5sq
zeXzxAcAe3NrqnSxR$^9As*?qD#?-(w0SN&BG!O(o+`zQ9!hQd;;PTa2bOc=_;WJE7
z8MSWtaYyw*%U0T*3*F4VT)gU^)?HyKlgKw5VGKSV_<<I<zklf&wVq9d79ukOvIKq2
z;!;A)Eu(pE!gFD%>UvpO^7ivOzGf?Tn|hOjjz8aC>}40oi-`x(>T;F5IkKffXcE?E
zWpCQJGhkzom`+|h5w_{ZET!>>qt&7OZNP=r5hNz0G>y?~{C@-QM28n%K<vW~hGLc^
zc(&D9581$|9Xy~xy%vOxomPHPX|-ChM%g62A$@~wc6*3p8=woM2um7`G7jIkw~8Xe
KgRrRSk1*tB9q45M
new file mode 100644
--- /dev/null
+++ b/third_party/rnp/src/tests/data/test_messages/message.txt.enc-aead-ocb
@@ -0,0 +1,1 @@
+ET#\Y{Ǥ!tBgŶ7%GZ
,"R9\>']ȠBV?A]qb8jdsu~fy Aw)JZΛ:] -acq
s1Q3kw:yHH]4[UK[rכ>*U}>IK^8,V
}I /b-/ʀ^M/3J9A5-$2*e9r@-;u
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/third_party/rnp/src/tests/data/test_messages/message.txt.enc-aead-ocb-malf
@@ -0,0 +1,1 @@
+ET#\Y{Ǥ!tBgŶ7%GZ
,"R9\>']ȠBV?A]qb8jdsu~fy Aw)JZΛ:] -acq
s1Q3kw:yHH]4[UK[rכ>*U}>IK^8,V
}I /b-/ʀ^M/3J9A5-$2*e9r@-;u
\ No newline at end of file
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c9461ea7cab7aceb05ba36dd921e33b21a2c78ec
GIT binary patch
literal 320
zc$@)10l)r)j01`VxSy?B-q8UB0L0H`?!Bfc;Yp#L)~1xzRcId5>J@~W1Uxmxs+rw(
zubZA=AC)Jga=j1^<_P;ofC`_5ZWvX!$a$LXol@lew4+ifCkKj(iSun1<KRB&UIa-=
zaLK&pF`iHYv(0p6Fiz_2S9JyRtGqZTtyf7CSH;=niNS!KU_KE*zNqnG%+jy{;Ub-x
z)R+H8Zc2~n==C0nLfh$58(ipwtIHEPKD2YhhPZc1`&kCyJWJlvET(42r{kX`(V`<X
zsD5Jo`o2fT)@LUyDqPhlh!X*PBunW`9>{_U=Z>AvX+7d#0MO2N8ukfz6B?!PGyu9<
zIOg0B+ltx@>I3iwvE9Bmu#WU*Vbv52+Rw;t(WudMCm+Gx)l={-x5v#(ct>w)^dIpK
SzNo4<$`UlA4P8J)??`UtWukBZ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2eeeb17dcbc5dab1d8c2d45e2c6d2004287e6064
GIT binary patch
literal 296
zc$@(!0oVS7j01`VxSy?B-q8UB0FV~Qo=!7wvw+js-xrjpY3b)bJ8qL34^-o=NB$tz
zziRRE`40yasZT+JhNQXwPAp91UW4bhEDOzjv7%ngS^QX!Rym_wPFBQcH;ml|RodZm
zu-yTYaqXX#R||^oS=m8P8YY+`6cIwc^{vlV>oC1cDzfbFAGfQoc{~<&dC8dmruhO6
z2d-&2{EDVT1wOW0+#))2s1M=OhX_Sv04&5kw8-HKmcJb|L$}j0v^0}KN{t)~D*sPS
zrH)f?`8usKPRYJ_*6LE26A+#FAsq@gwJNX&b2#{6=Z%gfh*q0VJ4(v$;3H3M;@>vI
udsX#k9k%X|tDc7WhqyP*ZE}+tB_b3VG?djze|TmHlMzF_2lLg}jKk8Q#E(e;
--- a/third_party/rnp/src/tests/ffi.cpp
+++ b/third_party/rnp/src/tests/ffi.cpp
@@ -6828,16 +6828,549 @@ TEST_F(rnp_tests, test_ffi_op_verify_sig
     assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
     assert_int_equal(sigcount, 0);
     rnp_op_verify_destroy(verify);
     rnp_input_destroy(input);
     rnp_output_destroy(output);
     rnp_ffi_destroy(ffi);
 }
 
+TEST_F(rnp_tests, test_ffi_op_verify_get_protection_info)
+{
+    rnp_ffi_t    ffi = NULL;
+    rnp_input_t  input = NULL;
+    rnp_output_t output = NULL;
+
+    // init ffi
+    test_ffi_init(&ffi);
+    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb, (void *) "password"));
+
+    /* message just signed */
+    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    rnp_op_verify_t verify = NULL;
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    char *mode = NULL;
+    char *cipher = NULL;
+    bool  valid = true;
+    assert_rnp_failure(rnp_op_verify_get_protection_info(NULL, &mode, &cipher, &valid));
+    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, NULL, NULL));
+    assert_int_equal(strcmp(mode, "none"), 0);
+    rnp_buffer_destroy(mode);
+    assert_rnp_success(rnp_op_verify_get_protection_info(verify, NULL, &cipher, NULL));
+    assert_int_equal(strcmp(cipher, "none"), 0);
+    rnp_buffer_destroy(cipher);
+    valid = true;
+    assert_rnp_success(rnp_op_verify_get_protection_info(verify, NULL, NULL, &valid));
+    assert_false(valid);
+    assert_rnp_failure(rnp_op_verify_get_protection_info(verify, NULL, NULL, NULL));
+    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+    assert_int_equal(strcmp(mode, "none"), 0);
+    assert_int_equal(strcmp(cipher, "none"), 0);
+    assert_false(valid);
+    rnp_buffer_destroy(mode);
+    rnp_buffer_destroy(cipher);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* message without MDC */
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-no-mdc"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    mode = NULL;
+    cipher = NULL;
+    valid = true;
+    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+    assert_int_equal(strcmp(mode, "cfb"), 0);
+    assert_int_equal(strcmp(cipher, "AES256"), 0);
+    assert_false(valid);
+    rnp_buffer_destroy(mode);
+    rnp_buffer_destroy(cipher);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* message with MDC */
+    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.enc-mdc"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    mode = NULL;
+    cipher = NULL;
+    valid = false;
+    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+    assert_int_equal(strcmp(mode, "cfb-mdc"), 0);
+    assert_int_equal(strcmp(cipher, "AES256"), 0);
+    assert_true(valid);
+    rnp_buffer_destroy(mode);
+    rnp_buffer_destroy(cipher);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* message with AEAD-OCB */
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    mode = NULL;
+    cipher = NULL;
+    valid = false;
+    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+    assert_int_equal(strcmp(mode, "aead-ocb"), 0);
+    assert_int_equal(strcmp(cipher, "CAMELLIA192"), 0);
+    assert_true(valid);
+    rnp_buffer_destroy(mode);
+    rnp_buffer_destroy(cipher);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* modified message with AEAD-OCB */
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb-malf"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_failure(rnp_op_verify_execute(verify));
+    mode = NULL;
+    cipher = NULL;
+    valid = false;
+    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+    assert_int_equal(strcmp(mode, "aead-ocb"), 0);
+    assert_int_equal(strcmp(cipher, "CAMELLIA192"), 0);
+    assert_false(valid);
+    rnp_buffer_destroy(mode);
+    rnp_buffer_destroy(cipher);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* message with AEAD-EAX */
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    mode = NULL;
+    cipher = NULL;
+    valid = false;
+    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+    assert_int_equal(strcmp(mode, "aead-eax"), 0);
+    assert_int_equal(strcmp(cipher, "AES256"), 0);
+    assert_true(valid);
+    rnp_buffer_destroy(mode);
+    rnp_buffer_destroy(cipher);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* modified message with AEAD-EAX */
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax-malf"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_failure(rnp_op_verify_execute(verify));
+    mode = NULL;
+    cipher = NULL;
+    valid = false;
+    assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+    assert_int_equal(strcmp(mode, "aead-eax"), 0);
+    assert_int_equal(strcmp(cipher, "AES256"), 0);
+    assert_false(valid);
+    rnp_buffer_destroy(mode);
+    rnp_buffer_destroy(cipher);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    rnp_ffi_destroy(ffi);
+}
+
+static bool
+getpasscb_for_key(rnp_ffi_t        ffi,
+                  void *           app_ctx,
+                  rnp_key_handle_t key,
+                  const char *     pgp_context,
+                  char *           buf,
+                  size_t           buf_len)
+{
+    if (!key) {
+        return false;
+    }
+    char *keyid = NULL;
+    rnp_key_get_keyid(key, &keyid);
+    const char *pass = "password";
+    if (strcmp(keyid, (const char *) app_ctx) != 0) {
+        pass = "wrongpassword";
+    }
+    rnp_buffer_destroy(keyid);
+    strcpy(buf, pass);
+    return true;
+}
+
+TEST_F(rnp_tests, test_ffi_op_verify_recipients_info)
+{
+    rnp_ffi_t    ffi = NULL;
+    rnp_input_t  input = NULL;
+    rnp_output_t output = NULL;
+
+    // init ffi
+    test_ffi_init(&ffi);
+    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb, (void *) "password"));
+
+    /* message just signed */
+    assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    rnp_op_verify_t verify = NULL;
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    /* rnp_op_verify_get_recipient_count */
+    assert_rnp_failure(rnp_op_verify_get_recipient_count(verify, NULL));
+    size_t count = 255;
+    assert_rnp_failure(rnp_op_verify_get_recipient_count(NULL, &count));
+    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+    assert_int_equal(count, 0);
+    /* rnp_op_verify_get_recipient_at */
+    rnp_recipient_handle_t recipient = NULL;
+    assert_rnp_failure(rnp_op_verify_get_recipient_at(NULL, 0, &recipient));
+    assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 0, NULL));
+    assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
+    assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 10, &recipient));
+    /* rnp_op_verify_get_used_recipient */
+    assert_rnp_failure(rnp_op_verify_get_used_recipient(NULL, &recipient));
+    assert_rnp_failure(rnp_op_verify_get_used_recipient(verify, NULL));
+    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+    assert_null(recipient);
+    /* rnp_op_verify_get_symenc_count */
+    assert_rnp_failure(rnp_op_verify_get_symenc_count(verify, NULL));
+    count = 255;
+    assert_rnp_failure(rnp_op_verify_get_symenc_count(NULL, &count));
+    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+    assert_int_equal(count, 0);
+    /* rnp_op_verify_get_symenc_at */
+    rnp_symenc_handle_t symenc = NULL;
+    assert_rnp_failure(rnp_op_verify_get_symenc_at(NULL, 0, &symenc));
+    assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 0, NULL));
+    assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+    assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 10, &symenc));
+    /* rnp_op_verify_get_used_symenc */
+    assert_rnp_failure(rnp_op_verify_get_used_symenc(NULL, &symenc));
+    assert_rnp_failure(rnp_op_verify_get_used_symenc(verify, NULL));
+    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+    assert_null(symenc);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* message without MDC: single recipient */
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-no-mdc"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+    assert_int_equal(count, 1);
+    assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 1, &recipient));
+    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
+    assert_non_null(recipient);
+    char *alg = NULL;
+    assert_rnp_failure(rnp_recipient_get_alg(NULL, &alg));
+    assert_rnp_failure(rnp_recipient_get_alg(recipient, NULL));
+    assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+    assert_string_equal(alg, "RSA");
+    rnp_buffer_destroy(alg);
+    char *keyid = NULL;
+    assert_rnp_failure(rnp_recipient_get_keyid(NULL, &keyid));
+    assert_rnp_failure(rnp_recipient_get_keyid(recipient, NULL));
+    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+    assert_string_equal(keyid, "8A05B89FAD5ADED1");
+    rnp_buffer_destroy(keyid);
+    recipient = NULL;
+    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+    assert_non_null(recipient);
+    alg = NULL;
+    assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+    assert_string_equal(alg, "RSA");
+    rnp_buffer_destroy(alg);
+    keyid = NULL;
+    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+    assert_string_equal(keyid, "8A05B89FAD5ADED1");
+    rnp_buffer_destroy(keyid);
+    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+    assert_int_equal(count, 0);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* message with AEAD-OCB: single password */
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    count = 255;
+    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+    assert_int_equal(count, 0);
+    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+    assert_int_equal(count, 1);
+    assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
+    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+    assert_non_null(symenc);
+    char *cipher = NULL;
+    assert_rnp_failure(rnp_symenc_get_cipher(symenc, NULL));
+    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+    assert_string_equal(cipher, "CAMELLIA192");
+    rnp_buffer_destroy(cipher);
+    char *aead = NULL;
+    assert_rnp_failure(rnp_symenc_get_aead_alg(symenc, NULL));
+    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
+    assert_string_equal(aead, "OCB");
+    rnp_buffer_destroy(aead);
+    char *hash = NULL;
+    assert_rnp_failure(rnp_symenc_get_hash_alg(symenc, NULL));
+    assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
+    assert_string_equal(hash, "SHA1");
+    rnp_buffer_destroy(hash);
+    char *s2k = NULL;
+    assert_rnp_failure(rnp_symenc_get_s2k_type(symenc, NULL));
+    assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
+    assert_string_equal(s2k, "Iterated and salted");
+    rnp_buffer_destroy(s2k);
+    uint32_t iterations = 0;
+    assert_rnp_failure(rnp_symenc_get_s2k_iterations(symenc, NULL));
+    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+    assert_int_equal(iterations, 30408704);
+    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+    assert_non_null(symenc);
+    cipher = NULL;
+    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+    assert_string_equal(cipher, "CAMELLIA192");
+    rnp_buffer_destroy(cipher);
+    aead = NULL;
+    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
+    assert_string_equal(aead, "OCB");
+    rnp_buffer_destroy(aead);
+    hash = NULL;
+    assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
+    assert_string_equal(hash, "SHA1");
+    rnp_buffer_destroy(hash);
+    s2k = NULL;
+    assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
+    assert_string_equal(s2k, "Iterated and salted");
+    rnp_buffer_destroy(s2k);
+    iterations = 0;
+    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+    assert_int_equal(iterations, 30408704);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* modified message with AEAD-EAX: one recipient and one password, decrypt with recipient
+     */
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax-malf"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_failure(rnp_op_verify_execute(verify));
+    count = 255;
+    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+    assert_int_equal(count, 1);
+    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
+    assert_non_null(recipient);
+    alg = NULL;
+    assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+    assert_string_equal(alg, "RSA");
+    rnp_buffer_destroy(alg);
+    keyid = NULL;
+    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+    assert_string_equal(keyid, "1ED63EE56FADC34D");
+    rnp_buffer_destroy(keyid);
+    recipient = NULL;
+    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+    assert_non_null(recipient);
+    assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+    assert_string_equal(alg, "RSA");
+    rnp_buffer_destroy(alg);
+    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+    assert_string_equal(keyid, "1ED63EE56FADC34D");
+    rnp_buffer_destroy(keyid);
+    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+    assert_non_null(recipient);
+    alg = NULL;
+    assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+    assert_string_equal(alg, "RSA");
+    rnp_buffer_destroy(alg);
+    keyid = NULL;
+    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+    assert_string_equal(keyid, "1ED63EE56FADC34D");
+    rnp_buffer_destroy(keyid);
+    recipient = NULL;
+    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+    assert_non_null(recipient);
+    assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+    assert_string_equal(alg, "RSA");
+    rnp_buffer_destroy(alg);
+    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+    assert_string_equal(keyid, "1ED63EE56FADC34D");
+    rnp_buffer_destroy(keyid);
+
+    count = 255;
+    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+    assert_int_equal(count, 1);
+    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+    assert_non_null(symenc);
+    cipher = NULL;
+    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+    assert_string_equal(cipher, "AES256");
+    rnp_buffer_destroy(cipher);
+    aead = NULL;
+    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
+    assert_string_equal(aead, "EAX");
+    rnp_buffer_destroy(aead);
+    hash = NULL;
+    assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
+    assert_string_equal(hash, "SHA256");
+    rnp_buffer_destroy(hash);
+    s2k = NULL;
+    assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
+    assert_string_equal(s2k, "Iterated and salted");
+    rnp_buffer_destroy(s2k);
+    iterations = 0;
+    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+    assert_int_equal(iterations, 3932160);
+    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+    assert_null(symenc);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* message with AEAD-EAX: one recipient and one password, decrypt with password */
+    assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET));
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    count = 255;
+    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+    assert_int_equal(count, 1);
+    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+    assert_null(recipient);
+    count = 255;
+    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+    assert_int_equal(count, 1);
+    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+    assert_non_null(symenc);
+    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+    assert_non_null(symenc);
+    cipher = NULL;
+    assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+    assert_string_equal(cipher, "AES256");
+    rnp_buffer_destroy(cipher);
+    aead = NULL;
+    assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
+    assert_string_equal(aead, "EAX");
+    rnp_buffer_destroy(aead);
+    hash = NULL;
+    assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
+    assert_string_equal(hash, "SHA256");
+    rnp_buffer_destroy(hash);
+    s2k = NULL;
+    assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
+    assert_string_equal(s2k, "Iterated and salted");
+    rnp_buffer_destroy(s2k);
+    iterations = 0;
+    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+    assert_int_equal(iterations, 3932160);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    /* message encrypted to 3 recipients and 2 passwords: password1, password2 */
+    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb, (void *) "wrongpassword"));
+    assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/secring.gpg"));
+    assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_SECRET_KEYS, NULL));
+    rnp_input_destroy(input);
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-3key-2p"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_failure(rnp_op_verify_execute(verify));
+    count = 255;
+    assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+    assert_int_equal(count, 3);
+    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
+    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+    assert_string_equal(keyid, "1ED63EE56FADC34D");
+    rnp_buffer_destroy(keyid);
+    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 1, &recipient));
+    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+    assert_string_equal(keyid, "8A05B89FAD5ADED1");
+    rnp_buffer_destroy(keyid);
+    assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 2, &recipient));
+    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+    assert_string_equal(keyid, "54505A936A4A970E");
+    rnp_buffer_destroy(keyid);
+    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+    assert_null(recipient);
+    count = 255;
+    assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+    assert_int_equal(count, 2);
+    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+    assert_int_equal(iterations, 3932160);
+    assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
+    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+    assert_int_equal(iterations, 3276800);
+    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+    assert_null(symenc);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb, (void *) "password2"));
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-3key-2p"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+    assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+    assert_null(recipient);
+    assert_int_equal(iterations, 3276800);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    assert_rnp_success(
+      rnp_ffi_set_pass_provider(ffi, getpasscb_for_key, (void *) "8A05B89FAD5ADED1"));
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_messages/message.txt.enc-3key-2p"));
+    assert_rnp_success(rnp_output_to_null(&output));
+    assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+    assert_rnp_success(rnp_op_verify_execute(verify));
+    assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+    assert_null(symenc);
+    assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+    assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+    assert_string_equal(keyid, "8A05B89FAD5ADED1");
+    rnp_buffer_destroy(keyid);
+    rnp_op_verify_destroy(verify);
+    rnp_input_destroy(input);
+    rnp_output_destroy(output);
+
+    rnp_ffi_destroy(ffi);
+}
+
 static bool
 check_import_sigs(rnp_ffi_t ffi, json_object **jso, json_object **sigarr, const char *sigpath)
 {
     rnp_input_t input = NULL;
     if (rnp_input_from_path(&input, sigpath)) {
         return false;
     }
     bool  res = false;
@@ -7855,8 +8388,172 @@ TEST_F(rnp_tests, test_ffi_key_import_ed
     rnp_buffer_destroy(results);
     revoked = false;
     assert_rnp_success(rnp_key_is_revoked(key, &revoked));
     assert_true(revoked);
     rnp_key_handle_destroy(key);
 
     rnp_ffi_destroy(ffi);
 }
+
+TEST_F(rnp_tests, test_ffi_key_remove)
+{
+    rnp_ffi_t ffi = NULL;
+    test_ffi_init(&ffi);
+
+    rnp_key_handle_t key0 = NULL;
+    rnp_key_handle_t key0_sub0 = NULL;
+    rnp_key_handle_t key0_sub1 = NULL;
+    rnp_key_handle_t key0_sub2 = NULL;
+    assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key0));
+    assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ed63ee56fadc34d", &key0_sub0));
+    assert_rnp_success(rnp_locate_key(ffi, "keyid", "1d7e8a5393c997a8", &key0_sub1));
+    assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &key0_sub2));
+
+    /* edge cases */
+    assert_rnp_failure(rnp_key_remove(NULL, RNP_KEY_REMOVE_PUBLIC));
+    assert_rnp_failure(rnp_key_remove(key0, 0));
+    /* make sure we correctly remove public and secret keys */
+    bool pub = false;
+    assert_rnp_success(rnp_key_have_public(key0_sub2, &pub));
+    assert_true(pub);
+    bool sec = false;
+    assert_rnp_success(rnp_key_have_secret(key0_sub2, &sec));
+    assert_true(sec);
+    assert_rnp_success(rnp_key_remove(key0_sub2, RNP_KEY_REMOVE_PUBLIC));
+    pub = true;
+    assert_rnp_success(rnp_key_have_public(key0_sub2, &pub));
+    assert_false(pub);
+    sec = false;
+    assert_rnp_success(rnp_key_have_secret(key0_sub2, &sec));
+    assert_true(sec);
+    assert_rnp_failure(rnp_key_remove(key0_sub2, RNP_KEY_REMOVE_PUBLIC));
+    rnp_key_handle_destroy(key0_sub2);
+    /* locate it back */
+    assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &key0_sub2));
+    assert_non_null(key0_sub2);
+    pub = true;
+    assert_rnp_success(rnp_key_have_public(key0_sub2, &pub));
+    assert_false(pub);
+    sec = false;
+    assert_rnp_success(rnp_key_have_secret(key0_sub2, &sec));
+    assert_true(sec);
+
+    pub = false;
+    assert_rnp_success(rnp_key_have_public(key0_sub0, &pub));
+    assert_true(pub);
+    sec = false;
+    assert_rnp_success(rnp_key_have_secret(key0_sub0, &sec));
+    assert_true(sec);
+    assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET));
+    pub = false;
+    assert_rnp_success(rnp_key_have_public(key0_sub0, &pub));
+    assert_true(pub);
+    sec = true;
+    assert_rnp_success(rnp_key_have_secret(key0_sub0, &sec));
+    assert_false(sec);
+    assert_rnp_failure(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET));
+    rnp_key_handle_destroy(key0_sub0);
+    assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ed63ee56fadc34d", &key0_sub0));
+    assert_non_null(key0_sub0);
+
+    size_t count = 0;
+    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+    assert_int_equal(count, 6);
+    count = 0;
+    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+    assert_int_equal(count, 6);
+
+    /* while there are 2 public and 1 secret subkey, this calculates only public */
+    assert_rnp_success(rnp_key_get_subkey_count(key0, &count));
+    assert_int_equal(count, 2);
+
+    assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_PUBLIC));
+    assert_rnp_success(rnp_key_get_subkey_count(key0, &count));
+    assert_int_equal(count, 1);
+    count = 0;
+    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+    assert_int_equal(count, 5);
+    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+    assert_int_equal(count, 6);
+
+    assert_rnp_success(rnp_key_remove(key0_sub2, RNP_KEY_REMOVE_SECRET));
+    assert_rnp_success(rnp_key_get_subkey_count(key0, &count));
+    assert_int_equal(count, 1);
+    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+    assert_int_equal(count, 5);
+
+    assert_rnp_success(rnp_key_remove(key0, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET));
+    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+    assert_int_equal(count, 4);
+    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+    assert_int_equal(count, 4);
+
+    rnp_key_handle_destroy(key0_sub1);
+    /* key0_sub1 should be left in keyring */
+    assert_rnp_success(rnp_locate_key(ffi, "keyid", "1d7e8a5393c997a8", &key0_sub1));
+    pub = false;
+    assert_rnp_success(rnp_key_have_public(key0_sub1, &pub));
+    assert_true(pub);
+    sec = false;
+    assert_rnp_success(rnp_key_have_secret(key0_sub1, &sec));
+    assert_true(sec);
+
+    rnp_key_handle_destroy(key0);
+    rnp_key_handle_destroy(key0_sub0);
+    rnp_key_handle_destroy(key0_sub1);
+    rnp_key_handle_destroy(key0_sub2);
+
+    /* let's import keys back */
+    rnp_input_t input = NULL;
+    assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));
+    assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+    rnp_input_destroy(input);
+    assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/secring.gpg"));
+    assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_SECRET_KEYS, NULL));
+    rnp_input_destroy(input);
+
+    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+    assert_int_equal(count, 7);
+    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+    assert_int_equal(count, 7);
+
+    /* now try to remove the whole key */
+    assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key0));
+    assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ed63ee56fadc34d", &key0_sub0));
+
+    assert_rnp_failure(
+      rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET | RNP_KEY_REMOVE_SUBKEYS));
+    assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET));
+    assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_PUBLIC));
+
+    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+    assert_int_equal(count, 6);
+    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+    assert_int_equal(count, 6);
+
+    assert_rnp_success(rnp_key_remove(key0, RNP_KEY_REMOVE_SECRET | RNP_KEY_REMOVE_SUBKEYS));
+    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+    assert_int_equal(count, 6);
+    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+    assert_int_equal(count, 3);
+
+    assert_rnp_success(rnp_key_remove(key0, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SUBKEYS));
+    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+    assert_int_equal(count, 3);
+    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+    assert_int_equal(count, 3);
+
+    rnp_key_handle_destroy(key0);
+    rnp_key_handle_destroy(key0_sub0);
+
+    /* delete the second key all at once */
+    assert_rnp_success(rnp_locate_key(ffi, "keyid", "2fcadf05ffa501bb", &key0));
+    assert_rnp_success(rnp_key_remove(
+      key0, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET | RNP_KEY_REMOVE_SUBKEYS));
+    assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+    assert_int_equal(count, 0);
+    assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+    assert_int_equal(count, 0);
+    rnp_key_handle_destroy(key0);
+
+    rnp_ffi_destroy(ffi);
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rnp/src/tests/issues/1171.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *
+ * 2.  Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "../rnp_tests.h"
+#include "../support.h"
+#include <librepgp/stream-ctx.h>
+#include "pgp-key.h"
+#include "ffi-priv-types.h"
+
+TEST_F(rnp_tests, test_issue_1171_key_import_and_remove)
+{
+    rnp_ffi_t ffi = NULL;
+    assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+    rnp_input_t input = NULL;
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_key_validity/alice-sub-pub.pgp"));
+    assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+    rnp_input_destroy(input);
+
+    rnp_key_handle_t key = NULL;
+    assert_rnp_success(
+      rnp_locate_key(ffi, "grip", "3E3D52A346F0AD47754611B078117C240ED237E9", &key));
+    assert_non_null(key);
+    assert_rnp_success(rnp_key_remove(key, RNP_KEY_REMOVE_PUBLIC));
+    assert_rnp_success(rnp_key_handle_destroy(key));
+
+    assert_rnp_success(
+      rnp_locate_key(ffi, "grip", "3E3D52A346F0AD47754611B078117C240ED237E9", &key));
+    assert_null(key);
+
+    assert_rnp_success(
+      rnp_locate_key(ffi, "grip", "128E494F41F107E119AA1EEF7850C375A804341C", &key));
+    assert_non_null(key);
+    uint32_t bits = 0;
+    assert_rnp_success(rnp_key_get_bits(key, &bits));
+    assert_int_equal(bits, 256);
+
+    /* directly use rnp_key_store_get_key_by_grip() which caused crash */
+    pgp_key_t *subkey =
+      rnp_key_store_get_key_by_grip(ffi->pubring, pgp_key_get_grip(key->pub));
+    assert_int_equal(pgp_key_get_bits(subkey), 256);
+    assert_rnp_success(rnp_key_handle_destroy(key));
+
+    assert_rnp_success(
+      rnp_input_from_path(&input, "data/test_key_validity/alice-sub-pub.pgp"));
+    assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+    rnp_input_destroy(input);
+
+    rnp_ffi_destroy(ffi);
+}
--- a/third_party/rnp/src/tests/rnp_tests.h
+++ b/third_party/rnp/src/tests/rnp_tests.h
@@ -272,16 +272,20 @@ void test_ffi_op_set_hash(void **state);
 void test_ffi_op_set_compression(void **state);
 
 void test_ffi_aead_params(void **state);
 
 void test_ffi_detached_verify_input(void **state);
 
 void test_ffi_op_verify_sig_count(void **state);
 
+void test_ffi_op_verify_get_protection_info(void **state);
+
+void test_ffi_op_verify_recipients_info(void **state);
+
 void test_ffi_import_signatures(void **state);
 
 void test_ffi_export_revocation(void **state);
 
 void test_ffi_secret_sig_import(void **state);
 
 void test_ffi_rnp_request_password(void **state);
 
@@ -290,16 +294,18 @@ void test_ffi_key_revoke(void **state);
 void test_ffi_set_key_expiry(void **state);
 
 void test_ffi_mdc_8k_boundary(void **state);
 
 void test_ffi_decrypt_wrong_mpi_bits(void **state);
 
 void test_ffi_key_import_edge_cases(void **state);
 
+void test_ffi_key_remove(void **state);
+
 void test_dsa_roundtrip(void **state);
 
 void test_dsa_verify_negative(void **state);
 
 void test_stream_memory(void **state);
 
 void test_stream_memory_discard(void **state);
 
@@ -364,16 +370,20 @@ void test_partial_length_zero_last_chunk
 void test_partial_length_largest(void **state);
 
 void test_partial_length_first_packet_length(void **state);
 
 void test_kbx_nsigs(void **state);
 
 void test_issue_1115(void **state);
 
+void issue_1030_rnpkeys_secret_keys_unprotected(void **state);
+
+void test_issue_1171_key_import_and_remove(void **state);
+
 void test_log_switch(void **state);
 
 #define assert_true(a) EXPECT_TRUE((a))
 #define assert_false(a) EXPECT_FALSE((a))
 #define assert_string_equal(a, b) EXPECT_STREQ((a), (b))
 #define assert_int_equal(a, b) EXPECT_EQ((a), (b))
 #define assert_int_not_equal(a, b) EXPECT_NE((a), (b))
 #define assert_non_null(a) EXPECT_NE((a), nullptr)