From a1174d647fab3a65cab5db6763ec11952cdae92c Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Dec 15 2022 15:58:26 +0000 Subject: import thunderbird-102.6.0-2.el9_1 --- diff --git a/.gitignore b/.gitignore index a03adbe..11b5b09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ SOURCES/cbindgen-vendor.tar.xz SOURCES/nspr-4.34.0-3.el8_1.src.rpm SOURCES/nss-3.79.0-6.el8_1.src.rpm -SOURCES/thunderbird-102.5.0.processed-source.tar.xz -SOURCES/thunderbird-langpacks-102.5.0-20221115.tar.xz +SOURCES/thunderbird-102.6.0.processed-source.tar.xz +SOURCES/thunderbird-langpacks-102.6.0-20221213.tar.xz SOURCES/thunderbird-symbolic.svg diff --git a/.thunderbird.metadata b/.thunderbird.metadata index 57ba0eb..d91eee9 100644 --- a/.thunderbird.metadata +++ b/.thunderbird.metadata @@ -1,6 +1,6 @@ 2a430d6252dbea45482ba316a6e9fa605c15e747 SOURCES/cbindgen-vendor.tar.xz af58b3c87a8b5491dde63b07efaeb3d7f1ec56c1 SOURCES/nspr-4.34.0-3.el8_1.src.rpm fc5297c6830f0a1e88f84b94b0b066487664061b SOURCES/nss-3.79.0-6.el8_1.src.rpm -9aa205e4b8d075f7292d9b1941ca70f7f17ca914 SOURCES/thunderbird-102.5.0.processed-source.tar.xz -cd691f3bb1cd19e1102bca10a3bac61e013f9e03 SOURCES/thunderbird-langpacks-102.5.0-20221115.tar.xz +2e5705870dd47decb800757a4e26d288b24b61b1 SOURCES/thunderbird-102.6.0.processed-source.tar.xz +d28522497a56117469dbabbde833b69619d8e090 SOURCES/thunderbird-langpacks-102.6.0-20221213.tar.xz 42e80b86948cdba0f69af5b15a69bc6a1274d938 SOURCES/thunderbird-symbolic.svg diff --git a/README.debrand b/README.debrand deleted file mode 100644 index 01c46d2..0000000 --- a/README.debrand +++ /dev/null @@ -1,2 +0,0 @@ -Warning: This package was configured for automatic debranding, but the changes -failed to apply. diff --git a/SOURCES/D161379.diff b/SOURCES/D161379.diff new file mode 100644 index 0000000..c8469bd --- /dev/null +++ b/SOURCES/D161379.diff @@ -0,0 +1,322 @@ +diff -up comm/third_party/moz.build.D161379.diff comm/third_party/moz.build +--- comm/third_party/moz.build.D161379.diff 2022-10-14 21:45:15.000000000 +0200 ++++ comm/third_party/moz.build 2022-11-10 11:49:44.194016978 +0100 +@@ -11,9 +11,11 @@ if CONFIG["TB_LIBOTR_PREBUILT"]: + + if CONFIG["MZLA_LIBRNP"]: + DIRS += [ +- "botan", + "bzip2", + "json-c", + "rnp", + "zlib", + ] ++ if CONFIG["MZLA_LIBRNP_BACKEND"] == "botan": ++ DIRS += [ "botan" ] ++ +diff -up comm/third_party/openpgp.configure.D161379.diff comm/third_party/openpgp.configure +--- comm/third_party/openpgp.configure.D161379.diff 2022-11-10 11:49:37.605024129 +0100 ++++ comm/third_party/openpgp.configure 2022-11-10 11:49:44.194016978 +0100 +@@ -199,16 +199,136 @@ with only_when(in_tree_librnp): + set_config("MZLA_BZIP2_CFLAGS", bzip2_flags.cflags) + set_config("MZLA_BZIP2_LIBS", bzip2_flags.ldflags) + +- # BOTAN --with-system-botan +- system_lib_option( +- "--with-system-botan", +- help="Use system Botan for librnp (located with pkgconfig)", +- ) +- +- botan_pkg = pkg_check_modules( +- "MZLA_BOTAN", "botan-2 >= 2.8.0", when="--with-system-botan" +- ) +- set_config("MZLA_SYSTEM_BOTAN", depends_if(botan_pkg)(lambda _: True)) ++ # librnp crypto backend selection ++ option("--with-librnp-backend", ++ help="Build librnp with the selected backend: {botan, openssl}", ++ default="botan") ++ ++ @depends("--with-librnp-backend") ++ def librnp_backend(backend): ++ allowed = ("botan", "openssl") ++ if backend[0] in allowed: ++ return backend[0] ++ else: ++ die(f"Unsupported librnp backend {backend[0]}.") ++ ++ set_config("MZLA_LIBRNP_BACKEND", librnp_backend) ++ ++ @depends(librnp_backend) ++ def rnp_botan(backend): ++ return backend == "botan" ++ ++ @depends(librnp_backend) ++ def rnp_openssl(backend): ++ return backend == "openssl" ++ ++ # Botan backend (--with-system-botan) ++ with only_when(rnp_botan): ++ system_lib_option( ++ "--with-system-botan", ++ help="Use system Botan for librnp (located with pkgconfig)", ++ ) ++ ++ botan_pkg = pkg_check_modules( ++ "MZLA_BOTAN", "botan-2 >= 2.8.0", when="--with-system-botan" ++ ) ++ set_config("MZLA_SYSTEM_BOTAN", depends_if(botan_pkg)(lambda _: True)) ++ ++ ++ # OpenSSL backend ++ with only_when(rnp_openssl): ++ option( ++ "--with-openssl", ++ nargs=1, ++ help="OpenSSL library prefix (when not found by pkgconfig)" ++ ) ++ openssl_pkg = pkg_check_modules( ++ "MZLA_LIBRNP_OPENSSL", ++ "openssl > 1.1.1", ++ allow_missing=True, ++ config=False ++ ) ++ @depends_if("--with-openssl", openssl_pkg) ++ @imports(_from="os.path", _import="isdir") ++ @imports(_from="os.path", _import="join") ++ def openssl_flags(openssl_prefix, openssl_pkg): ++ if openssl_prefix: ++ openssl_prefix = openssl_prefix[0] ++ include = join(openssl_prefix, "include") ++ lib = join(openssl_prefix, "lib") ++ if not isdir(lib): ++ lib = join(openssl_prefix, "lib64") ++ if isdir(include) and isdir(lib): ++ log.info(f"Using OpenSSL at {openssl_prefix}.") ++ return namespace( ++ cflags=(f"-I{include}",), ++ ldflags=(f"-L{lib}", "-lssl", "-lcrypto"), ++ ) ++ if openssl_pkg: ++ return namespace( ++ cflags=openssl_pkg.cflags, ++ ldflags=openssl_pkg.libs, ++ ) ++ set_config("MZLA_LIBRNP_OPENSSL_CFLAGS", openssl_flags.cflags) ++ set_config("MZLA_LIBRNP_OPENSSL_LIBS", openssl_flags.ldflags) ++ ++ ++ @depends(c_compiler, openssl_flags) ++ @imports(_from="textwrap", _import="dedent") ++ def openssl_version(compiler, openssl_flags): ++ log.info("Checking for OpenSSL >= 1.1.1") ++ if openssl_flags is None: ++ die("OpenSSL not found. Must be locatable with pkg-config or use --with-openssl.") ++ ++ def ossl_hexver(hex_str): ++ # See opensshlv.h for description of OPENSSL_VERSION_NUMBER ++ MIN_OSSL_VER = 0x1010100f # Version 1.1.1 ++ ver_as_int = int(hex_str[:-1], 16) ++ ossl_major = (ver_as_int & 0xf0000000) >> 28 ++ ossl_minor = (ver_as_int & 0x0ff00000) >> 20 ++ ossl_fix = (ver_as_int & 0x000ff000) >> 12 ++ ossl_patch = chr(96 + (ver_as_int & 0x00000ff0) >> 4) # as a letter a-z ++ ver_as_str = f"{ossl_major}.{ossl_minor}.{ossl_fix}{ossl_patch}" ++ if ver_as_int < MIN_OSSL_VER: ++ die(f"OpenSSL version {ver_as_str} is too old.") ++ return ver_as_str ++ ++ check = dedent( ++ """\ ++ #include ++ #ifdef OPENSSL_VERSION_STR ++ OPENSSL_VERSION_STR ++ #elif defined(OPENSSL_VERSION_NUMBER) ++ OPENSSL_VERSION_NUMBER ++ #else ++ #error Unable to determine OpenSSL version. ++ #endif ++ """ ++ ) ++ result = try_preprocess( ++ compiler.wrapper ++ + [compiler.compiler] ++ + compiler.flags ++ + list(openssl_flags.cflags), ++ "C", ++ check ++ ) ++ if result: ++ openssl_ver = result.splitlines()[-1] ++ if openssl_ver.startswith("0x"): ++ # OpenSSL 1.x.x - like 0x1010107fL ++ openssl_ver = ossl_hexver(openssl_ver) ++ else: ++ # OpenSSL 3.x.x - quoted version like "3.0.7" ++ openssl_ver = openssl_ver.replace('"', "") ++ major_version = openssl_ver.split(".")[0] ++ if major_version != "3": ++ die("Unrecognized OpenSSL version {openssl_version} found. Require >= 1.1.1 or 3.x.x") ++ ++ log.info(f"Found OpenSSL {openssl_ver}.") ++ return openssl_ver ++ ++ set_config("MZLA_LIBRNP_OPENSSL_VERSION", openssl_version) + + # Checks for building librnp itself + # ================================= +diff -up comm/third_party/rnp/moz.build.D161379.diff comm/third_party/rnp/moz.build +--- comm/third_party/rnp/moz.build.D161379.diff 2022-11-10 11:49:43.682017534 +0100 ++++ comm/third_party/rnp/moz.build 2022-11-10 11:51:22.878909880 +0100 +@@ -36,17 +36,53 @@ if CONFIG["CC_TYPE"] == "clang-cl": + "/EHs", + ] + ++LOCAL_INCLUDES = [ ++ "include", ++ "src", ++ "src/common", ++ "src/lib", ++] ++ ++IQuote( ++ "{}/src/lib".format(OBJDIR), ++ "{}/src/lib".format(SRCDIR), ++) ++ ++# Set up defines for src/lib/config.h + rnp_defines = { + "HAVE_BZLIB_H": True, + "HAVE_ZLIB_H": True, +- "CRYPTO_BACKEND_OPENSSL": True, +- "ENABLE_AEAD": True, +- "ENABLE_TWOFISH": True, +- "ENABLE_BRAINPOOL": True, + "ENABLE_IDEA": True, + "PACKAGE_BUGREPORT": '"https://bugzilla.mozilla.org/enter_bug.cgi?product=Thunderbird"', + "PACKAGE_STRING": '"rnp {}"'.format(CONFIG["MZLA_LIBRNP_FULL_VERSION"]) + } ++if CONFIG["MZLA_LIBRNP_BACKEND"] == "botan": ++ LOCAL_INCLUDES += ["!../botan/build/include"] ++ if CONFIG["MZLA_SYSTEM_BOTAN"]: ++ CXXFLAGS += CONFIG["MZLA_BOTAN_CFLAGS"] ++ ++ rnp_defines.update({ ++ "CRYPTO_BACKEND_BOTAN": True, ++ "ENABLE_AEAD": True, ++ "ENABLE_TWOFISH": True, ++ "ENABLE_BRAINPOOL": True, ++ }) ++elif CONFIG["MZLA_LIBRNP_BACKEND"] == "openssl": ++ CXXFLAGS += CONFIG["MZLA_LIBRNP_OPENSSL_CFLAGS"] ++ OS_LIBS += CONFIG["MZLA_LIBRNP_OPENSSL_LIBS"] ++ ++ rnp_defines.update({ ++ "CRYPTO_BACKEND_OPENSSL": True, ++ # Not supported with RNP+OpenSSL https://github.com/rnpgp/rnp/issues/1642 ++ "ENABLE_AEAD": False, ++ # Not supported by OpenSSL https://github.com/openssl/openssl/issues/2046 ++ "ENABLE_TWOFISH": False, ++ # Supported, but not with RHEL's OpenSSL, disabled for now; ++ "ENABLE_BRAINPOOL": False, ++ }) ++ if CONFIG["MZLA_LIBRNP_OPENSSL_VERSION"][0] == "3": ++ rnp_defines["CRYPTO_BACKEND_OPENSSL3"] = True ++ + GeneratedFile( + "src/lib/config.h", + script="/comm/python/rocbuild/process_cmake_define_files.py", +@@ -57,23 +93,6 @@ GeneratedFile( + ], + ) + +-LOCAL_INCLUDES = [ +- "include", +- "src", +- "src/common", +- "src/lib", +-] +- +-IQuote( +- "{}/src/lib".format(OBJDIR), +- "{}/src/lib".format(SRCDIR), +-) +- +-if CONFIG["MZLA_SYSTEM_BOTAN"]: +- CXXFLAGS += CONFIG["MZLA_BOTAN_CFLAGS"] +-else: +- LOCAL_INCLUDES += ["!../botan/build/include"] +- + if CONFIG["MOZ_SYSTEM_ZLIB"]: + CXXFLAGS += CONFIG["MOZ_ZLIB_CFLAGS"] + else: +@@ -109,29 +128,16 @@ SOURCES += [ + "src/common/time-utils.cpp", + "src/lib/crypto.cpp", + "src/lib/crypto/backend_version.cpp", +- "src/lib/crypto/bn.cpp", + "src/lib/crypto/cipher.cpp", +- "src/lib/crypto/cipher_botan.cpp", +- "src/lib/crypto/dsa.cpp", +- "src/lib/crypto/ec.cpp", + "src/lib/crypto/ec_curves.cpp", +- "src/lib/crypto/ecdh.cpp", + "src/lib/crypto/ecdh_utils.cpp", +- "src/lib/crypto/ecdsa.cpp", +- "src/lib/crypto/eddsa.cpp", +- "src/lib/crypto/elgamal.cpp", +- "src/lib/crypto/hash.cpp", + "src/lib/crypto/hash_common.cpp", + "src/lib/crypto/hash_sha1cd.cpp", +- "src/lib/crypto/mem.cpp", + "src/lib/crypto/mpi.cpp", +- "src/lib/crypto/rng.cpp", +- "src/lib/crypto/rsa.cpp", + "src/lib/crypto/s2k.cpp", + "src/lib/crypto/sha1cd/sha1.c", + "src/lib/crypto/sha1cd/ubc_check.c", + "src/lib/crypto/signatures.cpp", +- "src/lib/crypto/symmetric.cpp", + "src/lib/fingerprint.cpp", + "src/lib/generate-key.cpp", + "src/lib/json-utils.cpp", +@@ -159,4 +165,40 @@ SOURCES += [ + "src/librepgp/stream-write.cpp", + ] + ++if CONFIG["MZLA_LIBRNP_BACKEND"] == "botan": ++ SOURCES += [ ++ "src/lib/crypto/bn.cpp", ++ "src/lib/crypto/cipher_botan.cpp", ++ "src/lib/crypto/dsa.cpp", ++ "src/lib/crypto/ec.cpp", ++ "src/lib/crypto/ecdh.cpp", ++ "src/lib/crypto/ecdsa.cpp", ++ "src/lib/crypto/eddsa.cpp", ++ "src/lib/crypto/elgamal.cpp", ++ "src/lib/crypto/hash.cpp", ++ "src/lib/crypto/mem.cpp", ++ "src/lib/crypto/rng.cpp", ++ "src/lib/crypto/rsa.cpp", ++ "src/lib/crypto/symmetric.cpp", ++ ] ++if CONFIG["MZLA_LIBRNP_BACKEND"] == "openssl": ++ SOURCES += [ ++ "src/lib/crypto/bn_ossl.cpp", ++ "src/lib/crypto/cipher_ossl.cpp", ++ "src/lib/crypto/dl_ossl.cpp", ++ "src/lib/crypto/dsa_ossl.cpp", ++ "src/lib/crypto/ec_ossl.cpp", ++ "src/lib/crypto/ecdh_ossl.cpp", ++ "src/lib/crypto/ecdsa_ossl.cpp", ++ "src/lib/crypto/eddsa_ossl.cpp", ++ "src/lib/crypto/elgamal_ossl.cpp", ++ "src/lib/crypto/hash_crc24.cpp", ++ "src/lib/crypto/hash_ossl.cpp", ++ "src/lib/crypto/mem_ossl.cpp", ++ "src/lib/crypto/rng_ossl.cpp", ++ "src/lib/crypto/rsa_ossl.cpp", ++ "src/lib/crypto/s2k_ossl.cpp", ++ "src/lib/crypto/symmetric_ossl.cpp", ++ ] ++ + DIRS += ["src/rnp", "src/rnpkeys"] diff --git a/SOURCES/D161895.diff b/SOURCES/D161895.diff new file mode 100644 index 0000000..fd0953f --- /dev/null +++ b/SOURCES/D161895.diff @@ -0,0 +1,49 @@ +diff --git a/third_party/openpgp.configure b/third_party/openpgp.configure +--- a/third_party/openpgp.configure ++++ b/third_party/openpgp.configure +@@ -198,21 +198,27 @@ + ) + set_config("MZLA_BZIP2_CFLAGS", bzip2_flags.cflags) + set_config("MZLA_BZIP2_LIBS", bzip2_flags.ldflags) + + # librnp crypto backend selection ++ @depends(target_is_linux) ++ def librnp_backend_choices(is_linux): ++ if is_linux: ++ return ("botan", "openssl") ++ else: ++ return ("botan",) ++ + option("--with-librnp-backend", +- help="Build librnp with the selected backend: {botan, openssl}", ++ help="Build librnp with the selected backend", ++ choices=librnp_backend_choices, ++ nargs=1, + default="botan") + + @depends("--with-librnp-backend") + def librnp_backend(backend): +- allowed = ("botan", "openssl") +- if backend[0] in allowed: ++ if backend: + return backend[0] +- else: +- die(f"Unsupported librnp backend {backend[0]}.") + + set_config("MZLA_LIBRNP_BACKEND", librnp_backend) + + @depends(librnp_backend) + def rnp_botan(backend): +@@ -273,10 +279,11 @@ + set_config("MZLA_LIBRNP_OPENSSL_LIBS", openssl_flags.ldflags) + + + @depends(c_compiler, openssl_flags) + @imports(_from="textwrap", _import="dedent") ++ @imports(_from="__builtin__", _import="chr") + def openssl_version(compiler, openssl_flags): + log.info("Checking for OpenSSL >= 1.1.1") + if openssl_flags is None: + die("OpenSSL not found. Must be locatable with pkg-config or use --with-openssl.") + + diff --git a/SOURCES/backport-rnp-0.16.2-to-esr102-a-bug-1753683.patch b/SOURCES/backport-rnp-0.16.2-to-esr102-a-bug-1753683.patch new file mode 100644 index 0000000..7dcf504 --- /dev/null +++ b/SOURCES/backport-rnp-0.16.2-to-esr102-a-bug-1753683.patch @@ -0,0 +1,81 @@ +# HG changeset patch +# User Daniel +# Date 1658184582 0 +# Mon Jul 18 22:49:42 2022 +0000 +# Node ID 9998ed5c2bcee289b03828eba670053614fa26da +# Parent e572bc3cfa07492189aec439e98378b0811ae3bb +Bug 1753683 - Replace distutils (deprecated) with packaging. r=rjl + +Differential Revision: https://phabricator.services.mozilla.com/D152123 + +diff --git a/comm/python/thirdroc/thirdroc/__init__.py b/comm/python/thirdroc/thirdroc/__init__.py +--- a/comm/python/thirdroc/thirdroc/__init__.py ++++ b/comm/python/thirdroc/thirdroc/__init__.py +@@ -3,11 +3,11 @@ + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + + from __future__ import print_function, absolute_import + + import re +-from distutils.version import StrictVersion ++from packaging.version import parse + + VTAG_RE = re.compile(r"^v\d+\.\d+\.\d+$") + + + def tag2version(tag): +@@ -22,16 +22,16 @@ def tag2version(tag): + raise Exception("Invalid tag {}".format(tag)) + + + def get_latest_version(*versions): + """ +- Given a list of versions (that must parse with distutils.version.StrictVersion, ++ Given a list of versions (that must parse with packaging.version.parse), + return the latest/newest version. + :param list versions: +- :return StrictVersion: ++ :return Version: + """ +- version_list = [StrictVersion(tag2version(v)) for v in versions] ++ version_list = [parse(tag2version(v)) for v in versions] + version_list.sort() + return version_list[-1] + + + def latest_version(*versions): +diff --git a/comm/python/thirdroc/thirdroc/rnp.py b/comm/python/thirdroc/thirdroc/rnp.py +--- a/comm/python/thirdroc/thirdroc/rnp.py ++++ b/comm/python/thirdroc/thirdroc/rnp.py +@@ -6,11 +6,11 @@ from __future__ import absolute_import + + import os + from io import StringIO + from datetime import date + import re +-from distutils.version import StrictVersion ++from packaging.version import parse + + from mozbuild.preprocessor import Preprocessor + + + def rnp_source_update(rnp_root, version_str, revision, timestamp, bug_report): +@@ -21,14 +21,14 @@ def rnp_source_update(rnp_root, version_ + :param string version_str: latest version + :param string revision: revision hash (short form) + :param float timestamp: UNIX timestamp from revision + :param string bug_report: where to report bugs for this RNP build + """ +- version = StrictVersion(version_str) +- version_major = version.version[0] +- version_minor = version.version[1] +- version_patch = version.version[2] ++ version = parse(version_str) ++ version_major = version.major ++ version_minor = version.minor ++ version_patch = version.micro + date_str = date.fromtimestamp(float(timestamp)).strftime("%Y%m%d") + revision_short = revision[:8] + version_full = "{}+git{}.{}.MZLA".format(version_str, date_str, revision_short) + + defines = dict( diff --git a/SOURCES/backport-rnp-0.16.2-to-esr102-b-bug-1790446.patch b/SOURCES/backport-rnp-0.16.2-to-esr102-b-bug-1790446.patch new file mode 100644 index 0000000..ebad418 --- /dev/null +++ b/SOURCES/backport-rnp-0.16.2-to-esr102-b-bug-1790446.patch @@ -0,0 +1,118 @@ +# HG changeset patch +# User Rob Lemley +# Date 1662996130 0 +# Mon Sep 12 15:22:10 2022 +0000 +# Node ID 5dfb405f325609c62215f9d74e01dba029b84611 +# Parent 9998ed5c2bcee289b03828eba670053614fa26da +Bug 1790446 - Stop rewriting RNP config.h.in when updating the source from upstream. r=dandarnell + +Differential Revision: https://phabricator.services.mozilla.com/D157151 + +diff --git a/comm/python/thirdroc/thirdroc/rnp.py b/comm/python/thirdroc/thirdroc/rnp.py +--- a/comm/python/thirdroc/thirdroc/rnp.py ++++ b/comm/python/thirdroc/thirdroc/rnp.py +@@ -11,19 +11,18 @@ import re + from packaging.version import parse + + from mozbuild.preprocessor import Preprocessor + + +-def rnp_source_update(rnp_root, version_str, revision, timestamp, bug_report): ++def rnp_source_update(rnp_root, version_str, revision, timestamp): + """ + Update RNP source files: generate version.h and mangle config.h.in + :param rnp_root: + :type rnp_root: + :param string version_str: latest version + :param string revision: revision hash (short form) + :param float timestamp: UNIX timestamp from revision +- :param string bug_report: where to report bugs for this RNP build + """ + version = parse(version_str) + version_major = version.major + version_minor = version.minor + version_patch = version.micro +@@ -36,20 +35,17 @@ def rnp_source_update(rnp_root, version_ + RNP_VERSION_MINOR=version_minor, + RNP_VERSION_PATCH=version_patch, + RNP_VERSION=version_str, + RNP_VERSION_FULL=version_full, + RNP_VERSION_COMMIT_TIMESTAMP=str(timestamp), +- BUGREPORT_EMAIL=bug_report, + ) + src_lib = os.path.join(rnp_root, "src", "lib") + version_h_in = os.path.join(src_lib, "version.h.in") + version_h = os.path.join(src_lib, "version.h") +- config_h_in = os.path.join(src_lib, "config.h.in") + readme_rnp = os.path.join(rnp_root, "..", "README.rnp") + + generate_version_h(version_h_in, version_h, defines) +- mangle_config_h_in(config_h_in, defines) + update_readme(readme_rnp, revision) + + + def rnp_preprocess(tmpl, dest, defines): + """ +@@ -79,30 +75,10 @@ def generate_version_h(template, destina + with open(template) as tmpl: + with open(destination, "w") as dest: + rnp_preprocess(tmpl, dest, defines) + + +-def mangle_config_h_in(template, defines): +- """ +- Mangle RNP's config.h.in so that it will work with CONFIGURE_DEFINE_FILES +- :param string template: path to config.h.in +- :param dict defines: result of get_defines() +- """ +- with open(template) as tmpl: +- tmp_string = StringIO() +- rnp_preprocess(tmpl, tmp_string, defines) +- +- tmp_string.seek(0) +- +- with open(template, "w") as dest: +- for line in tmp_string: +- if line.startswith("#cmakedefine"): +- line = line.replace("#cmakedefine", "#undef") +- dest.write(line) +- dest.write("\n") +- +- + def update_readme(path, revision): + """ + Updates the commit hash in README.rnp + :param string path: Path to README.rnp + :param string revision: revision to insert +diff --git a/comm/third_party/update_rnp.sh b/comm/third_party/update_rnp.sh +--- a/comm/third_party/update_rnp.sh ++++ b/comm/third_party/update_rnp.sh +@@ -42,26 +42,23 @@ TAGLIST=$(git -C "${RNPgit}" tag --list + + LATEST_VERSION=$($THIRDROC latest_version $TAGLIST) + REVISION=$(git -C "${RNPgit}" rev-parse --verify HEAD) + TIMESTAMP=$(git -C "${RNPgit}" show -s --format=%ct) + +-BUGREPORT="https://bugzilla.mozilla.org/enter_bug.cgi?product=Thunderbird" +- + # Cleanup rnp checkout + rm -rf ${RNPgit}/{.git,.github,.cirrus.yml,.clang-format,.gitignore} + rm -rf ${RNPgit}/{_config.yml,docker.sh,ci,cmake,git-hooks,travis.sh,vcpkg.txt} + rm -rf ${RNPgit}/{Brewfile,CMakeLists.txt,CMakeSettings.json} + + # Do the switch + rm -rf rnp + mv "${RNPgit}" rnp +-# Build version.h/config.h.in ++# Build version.h + $THIRDROC rnp_source_update rnp/ \ + "${LATEST_VERSION}" \ + "${REVISION}" \ +- "${TIMESTAMP}" \ +- "${BUGREPORT}" ++ "${TIMESTAMP}" + + # Restore moz.build + hg revert rnp/moz.build rnp/module.ver rnp/rnp.symbols rnp/src/lib/rnp/rnp_export.h \ + rnp/src/rnp/moz.build rnp/src/rnpkeys/moz.build + diff --git a/SOURCES/backport-rnp-0.16.2-to-esr102-c-bug-1790446.patch b/SOURCES/backport-rnp-0.16.2-to-esr102-c-bug-1790446.patch new file mode 100644 index 0000000..d1270f8 --- /dev/null +++ b/SOURCES/backport-rnp-0.16.2-to-esr102-c-bug-1790446.patch @@ -0,0 +1,185 @@ +# HG changeset patch +# User Rob Lemley +# Date 1662996529 0 +# Mon Sep 12 15:28:49 2022 +0000 +# Node ID c9e44c0a569253884961ad2e18fae23f5ed0f6dc +# Parent 5dfb405f325609c62215f9d74e01dba029b84611 +Bug 1790446 - Add build script to preprocess CMake config.h templates. r=dandarnell + + + +Right now config.h.in is rewritten when the RNP source is updated. +This has caused problems when new lines are added to it. + +Depends on D157151 + +Differential Revision: https://phabricator.services.mozilla.com/D157152 + +diff --git a/comm/python/rocbuild/process_cmake_define_files.py b/python/rocb/commuild/process_cmake_define_files.py +new file mode 100644 +--- /dev/null ++++ b/comm/python/rocbuild/process_cmake_define_files.py +@@ -0,0 +1,103 @@ ++# This Source Code Form is subject to the terms of the Mozilla Public ++# License, v. 2.0. If a copy of the MPL was not distributed with this ++# file, You can obtain one at http://mozilla.org/MPL/2.0/. ++ ++from __future__ import absolute_import, print_function, unicode_literals ++ ++import argparse ++import os ++import re ++import sys ++from buildconfig import topsrcdir, topobjdir ++from mozbuild.backend.configenvironment import PartialConfigEnvironment ++ ++ ++def define_type(string): ++ vals = string.split("=", 1) ++ if len(vals) == 1: ++ vals.append(1) ++ elif vals[1].isdecimal(): ++ vals[1] = int(vals[1]) ++ return tuple(vals) ++ ++ ++def process_cmake_define_file(output, input_file, extra_defines): ++ """Creates the given config header. A config header is generated by ++ taking the corresponding source file and replacing some #define/#undef ++ occurences: ++ "#undef NAME" is turned into "#define NAME VALUE" ++ "#cmakedefine NAME" is turned into "#define NAME VALUE" ++ "#define NAME" is unchanged ++ "#define NAME ORIGINAL_VALUE" is turned into "#define NAME VALUE" ++ "#undef UNKNOWN_NAME" is turned into "/* #undef UNKNOWN_NAME */" ++ "#cmakedefine UNKNOWN_NAME" is turned into "/* #undef UNKNOWN_NAME */" ++ Whitespaces are preserved. ++ """ ++ ++ path = os.path.abspath(input_file) ++ ++ config = PartialConfigEnvironment(topobjdir) ++ ++ defines = dict(config.defines.iteritems()) ++ defines.update(extra_defines) ++ ++ with open(path, "r") as input_file: ++ r = re.compile( ++ r'^\s*#\s*(?P[a-z]+)(?:\s+(?P\S+)(?:\s+(?P("[^"]+"|\S+)))?)?', ++ re.U, ++ ) ++ for line in input_file: ++ m = r.match(line) ++ if m: ++ cmd = m.group("cmd") ++ name = m.group("name") ++ value = m.group("value") ++ if name: ++ if cmd == "define": ++ if value and name in defines: ++ line = ( ++ line[: m.start("value")] ++ + str(defines[name]) ++ + line[m.end("value") :] ++ ) ++ elif cmd in ("undef", "cmakedefine"): ++ if name in defines: ++ line = ( ++ line[: m.start("cmd")] ++ + "define" ++ + line[m.end("cmd") : m.end("name")] ++ + " " ++ + str(defines[name]) ++ + line[m.end("name") :] ++ ) ++ else: ++ line = ( ++ "/* #undef " ++ + line[m.start("name") : m.end("name")] ++ + " */" ++ + line[m.end("name") :] ++ ) ++ ++ output.write(line) ++ ++ ++def main(output, *argv): ++ parser = argparse.ArgumentParser(description="Process define files.") ++ ++ parser.add_argument("input", help="Input define file.") ++ parser.add_argument( ++ "-D", ++ type=define_type, ++ action="append", ++ dest="extra_defines", ++ default=[], ++ help="Additional defines not set at configure time.", ++ ) ++ ++ args = parser.parse_args(argv) ++ ++ return process_cmake_define_file(output, args.input, args.extra_defines) ++ ++ ++if __name__ == "__main__": ++ sys.exit(main(*sys.argv)) +diff --git a/comm/third_party/rnp/moz.build b/third_party/rnp/moz.b/commuild +--- a/comm/third_party/rnp/moz.build ++++ b/comm/third_party/rnp/moz.build +@@ -34,19 +34,27 @@ COMPILE_FLAGS["WARNINGS_CFLAGS"] += [ + if CONFIG["CC_TYPE"] == "clang-cl": + CXXFLAGS += [ + "/EHs", + ] + +-DEFINES["_GNU_SOURCE"] = True +- +-DEFINES["HAVE_BZLIB_H"] = True +-DEFINES["HAVE_ZLIB_H"] = True +-DEFINES["MOZ_RNP_DIST_INFO"] = rnp_dist_info +- +-CONFIGURE_DEFINE_FILES += [ ++rnp_defines = { ++ "HAVE_BZLIB_H": True, ++ "HAVE_ZLIB_H": True, ++ "CRYPTO_BACKEND_BOTAN": True, ++ "ENABLE_AEAD": True, ++ "ENABLE_TWOFISH": True, ++ "ENABLE_BRAINPOOL": True, ++} ++GeneratedFile( + "src/lib/config.h", +-] ++ script="/comm/python/rocbuild/process_cmake_define_files.py", ++ inputs=["src/lib/config.h.in"], ++ flags=[ ++ "-D%s=%s" % (k, "1" if v is True else v) ++ for k, v in rnp_defines.items() ++ ], ++) + + LOCAL_INCLUDES = [ + "include", + "src", + "src/common", +diff --git a/comm/third_party/rnpdefs.mozbuild b/third_party/rnpdefs.mozb/commuild +--- a/comm/third_party/rnpdefs.mozbuild ++++ b/comm/third_party/rnpdefs.mozbuild +@@ -16,17 +16,10 @@ rnp_dist_info = "{} {} rnp".format( + COMPILE_FLAGS["OS_CFLAGS"] = [] + COMPILE_FLAGS["OS_CXXFLAGS"] = [] + COMPILE_FLAGS["OS_INCLUDES"] = [] + COMPILE_FLAGS["CLANG_PLUGIN"] = [] + +-DEFINES["RNP_NO_DEPRECATED"] = True +-DEFINES["CRYPTO_BACKEND_BOTAN"] = True +-DEFINES["ENABLE_AEAD"] = True +-DEFINES["ENABLE_TWOFISH"] = True +-DEFINES["ENABLE_BRAINPOOL"] = True +- +- + if CONFIG["COMPILE_ENVIRONMENT"]: + COMPILE_FLAGS["MOZ_HARDENING_CFLAGS"] = [] + + if CONFIG["CC_TYPE"] == "clang-cl": + CFLAGS += [ diff --git a/SOURCES/backport-rnp-0.16.2-to-esr102-d-bug-1790446.patch b/SOURCES/backport-rnp-0.16.2-to-esr102-d-bug-1790446.patch new file mode 100644 index 0000000..f2c9671 --- /dev/null +++ b/SOURCES/backport-rnp-0.16.2-to-esr102-d-bug-1790446.patch @@ -0,0 +1,77 @@ +# HG changeset patch +# User Rob Lemley +# Date 1662997034 0 +# Mon Sep 12 15:37:14 2022 +0000 +# Node ID 17dc6bb322b5d40299bba0a90d59c0593137d4f6 +# Parent c9e44c0a569253884961ad2e18fae23f5ed0f6dc +Bug 1790446 - Get RNP version during configure and set in config.h. r=dandarnell + + + + +Depends on D157152 + +Differential Revision: https://phabricator.services.mozilla.com/D157153 + +diff --git a/comm/third_party/openpgp.configure b/comm/third_party/openpgp.configure +--- a/comm/third_party/openpgp.configure ++++ b/comm/third_party/openpgp.configure +@@ -86,10 +86,42 @@ with only_when("--enable-compile-environ + set_config("MZLA_LIBRNP", depends_if(in_tree_librnp)(lambda _: True)) + set_define("MZLA_LIBRNP", depends_if(in_tree_librnp)(lambda _: True)) + + + with only_when(in_tree_librnp): ++ ++ @depends(build_environment, c_compiler) ++ @imports(_from="textwrap", _import="dedent") ++ @imports(_from="os.path", _import="join") ++ def rnp_version_string(build_env, compiler): ++ log.info("Determining librnp version from version.h.") ++ include_path = join( ++ build_env.topsrcdir, "comm", "third_party", "rnp", "src", "lib" ++ ) ++ check = dedent( ++ """\ ++ #include "version.h" ++ RNP_VERSION_STRING_FULL ++ """ ++ ) ++ result = try_preprocess( ++ compiler.wrapper ++ + [compiler.compiler] ++ + compiler.flags ++ + ["-I", include_path], ++ "C", ++ check, ++ ) ++ if result: ++ rnp_version = result.splitlines()[-1] ++ rnp_version = rnp_version.replace('"', "") ++ else: ++ raise FatalCheckError("Unable to determine RNP version string.") ++ return rnp_version ++ ++ set_config("MZLA_LIBRNP_FULL_VERSION", rnp_version_string) ++ + # JSON-C --with-system-json + system_lib_option( + "--with-system-jsonc", + help="Use system JSON-C for librnp (located with pkgconfig)", + ) +diff --git a/comm/third_party/rnp/moz.build b/third_party/rnp/moz.b/commuild +--- a/comm/third_party/rnp/moz.build ++++ b/comm/third_party/rnp/moz.build +@@ -41,10 +41,12 @@ rnp_defines = { + "HAVE_ZLIB_H": True, + "CRYPTO_BACKEND_BOTAN": True, + "ENABLE_AEAD": True, + "ENABLE_TWOFISH": True, + "ENABLE_BRAINPOOL": True, ++ "PACKAGE_BUGREPORT": '"https://bugzilla.mozilla.org/enter_bug.cgi?product=Thunderbird"', ++ "PACKAGE_STRING": '"rnp {}"'.format(CONFIG["MZLA_LIBRNP_FULL_VERSION"]) + } + GeneratedFile( + "src/lib/config.h", + script="/comm/python/rocbuild/process_cmake_define_files.py", + inputs=["src/lib/config.h.in"], diff --git a/SOURCES/backport-rnp-0.16.2-to-esr102-e-bug-1790116.patch b/SOURCES/backport-rnp-0.16.2-to-esr102-e-bug-1790116.patch new file mode 100644 index 0000000..9ee68a5 --- /dev/null +++ b/SOURCES/backport-rnp-0.16.2-to-esr102-e-bug-1790116.patch @@ -0,0 +1,58 @@ +# HG changeset patch +# User Rob Lemley +# Date 1663866047 14400 +# Thu Sep 22 13:00:47 2022 -0400 +# Node ID 8c718243f4e83fc18dfc88bf5d817c5c18f13937 +# Parent 17dc6bb322b5d40299bba0a90d59c0593137d4f6 +Bug 1790116 - update_rnp.sh changes for RNP v0.16.2. r=kaie + +The changes in bug_1768424.patch are now included upstream in +https://github.com/rnpgp/rnp/commit/ac6f58ef7ccea270b735b53f87da2c3ca5b34290. + +bug_1763641.patch removed per bug 1790116 comment 26. + +disable_obsolete_ciphers.patch no longer needed, use security rules instead. + +Differential Revision: https://phabricator.services.mozilla.com/D157010 + +diff --git a/comm/third_party/update_rnp.sh b/comm/third_party/update_rnp.sh +--- a/comm/third_party/update_rnp.sh ++++ b/comm/third_party/update_rnp.sh +@@ -43,11 +43,11 @@ TAGLIST=$(git -C "${RNPgit}" tag --list + LATEST_VERSION=$($THIRDROC latest_version $TAGLIST) + REVISION=$(git -C "${RNPgit}" rev-parse --verify HEAD) + TIMESTAMP=$(git -C "${RNPgit}" show -s --format=%ct) + + # Cleanup rnp checkout +-rm -rf ${RNPgit}/{.git,.github,.cirrus.yml,.clang-format,.gitignore} ++rm -rf ${RNPgit}/{.git,.github,.cirrus.yml,.clang-format,.gitignore,.codespellrc} + rm -rf ${RNPgit}/{_config.yml,docker.sh,ci,cmake,git-hooks,travis.sh,vcpkg.txt} + rm -rf ${RNPgit}/{Brewfile,CMakeLists.txt,CMakeSettings.json} + + # Do the switch + rm -rf rnp +@@ -60,17 +60,17 @@ mv "${RNPgit}" rnp + + # Restore moz.build + hg revert rnp/moz.build rnp/module.ver rnp/rnp.symbols rnp/src/lib/rnp/rnp_export.h \ + rnp/src/rnp/moz.build rnp/src/rnpkeys/moz.build + +-# Reapply Thunderbird patch to disable obsolete ciphers +-PATCH_FILES=("patches/rnp/disable_obsolete_ciphers.patch" \ +- "patches/rnp/bug_1763641.patch" \ +- "patches/rnp/bug_1768424.patch") +-for PATCH_FILE in "${PATCH_FILES[@]}"; do +- patch -p2 -i "${PATCH_FILE}" -N -r "${MY_TEMP_DIR}/${PATCH_FILE}.rej" +-done ++# Patch librnp - currently not needed ++#PATCH_FILES=("patches/rnp/disable_obsolete_ciphers.patch") ++#for PATCH_FILE in "${PATCH_FILES[@]}"; do ++# # shellcheck disable=SC2086 ++# echo "Applying patch $(basename ${PATCH_FILE})" ++# patch -p2 -i "${PATCH_FILE}" -N -r "${MY_TEMP_DIR}/${PATCH_FILE}.rej" ++#done + + # Patch sometimes creates backup files that are not wanted. + find rnp -name '*.orig' -exec rm -f '{}' \; + + rm -rf "${MY_TEMP_DIR}" diff --git a/SOURCES/backport-rnp-0.16.2-to-esr102-f-bug-1790116.patch b/SOURCES/backport-rnp-0.16.2-to-esr102-f-bug-1790116.patch new file mode 100644 index 0000000..bc0e638 --- /dev/null +++ b/SOURCES/backport-rnp-0.16.2-to-esr102-f-bug-1790116.patch @@ -0,0 +1,34760 @@ +# HG changeset patch +# User Rob Lemley +# Date 1663866499 14400 +# Thu Sep 22 13:08:19 2022 -0400 +# Node ID 3625a887f020a9a3cb3ad96e5107bfeacd54386e +# Parent 8c718243f4e83fc18dfc88bf5d817c5c18f13937 +Bug 1790116 - Update librnp to v0.16.2. r=kaie +Executed: ./update_rnp.sh 298ad98b9ba2fb58e6eadec3c226f8184b41ab98 + +Differential Revision: https://phabricator.services.mozilla.com/D157011 + +diff --git a/comm/third_party/README.rnp b/comm/third_party/README.rnp +--- a/comm/third_party/README.rnp ++++ b/comm/third_party/README.rnp +@@ -1,9 +1,9 @@ + Directory ./rnp contains a copy of rnp which has been obtained from: + https://github.com/rnpgp/rnp + +-[commit f06439f77e50974e427023f77a459843e46ac682] ++[commit 298ad98b9ba2fb58e6eadec3c226f8184b41ab98] + + 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 +diff --git a/comm/third_party/rnp/CHANGELOG.md b/comm/third_party/rnp/CHANGELOG.md +--- a/comm/third_party/rnp/CHANGELOG.md ++++ b/comm/third_party/rnp/CHANGELOG.md +@@ -1,7 +1,54 @@ + ## Changelog + ++### 0.16.2 [2022-09-20] ++ ++#### General ++ ++* Fixed CMake issues with ENABLE_IDEA and ENABLE_BRAINPOOL ++ ++### 0.16.1 [2022-09-06] ++ ++#### General ++ ++* Ensure support for RHEL9/CentOS Stream 9/Fedora 36, updating OpenSSL backend support for v3.0. ++* Optional import and export of base64-encoded keys. ++* Optional raw encryption of the data. ++* Optional overriding of the current timestamp. ++* Do not fail completely on unknown signature versions. ++* Do not fail completely on unknown PKESK/SKESK packet versions. ++* Support armored messages without empty line after the headers. ++* Added automatic feature detection based on backend. ++ ++#### Security ++ ++* Separate security rules for the data and key signatures, extending SHA1 key signature support till the Jan, 19 2024. ++* Set default key expiration time to 2 years. ++* Limit maximum AEAD chunk bits to 16. ++ ++#### FFI ++ ++* Changed behaviour of `rnp_op_verify_execute()`: now it requires single valid signature to succeed. ++* Added function `rnp_op_verify_set_flags()` to override default behaviour of verification. ++* Added function `rnp_key_is_expired()`. ++* Added function `rnp_op_encrypt_set_flags()` and flag `RNP_ENCRYPT_NOWRAP` to allow raw encryption. ++* Added flag `RNP_LOAD_SAVE_BASE64` to the function `rnp_import_keys()`. ++* Added flag `RNP_KEY_EXPORT_BASE64` to the function `rnp_key_export_autocrypt()`. ++* Added function `rnp_set_timestamp()` to allow to override current time. ++* Update security rules functions with flags `RNP_SECURITY_VERIFY_KEY` and `RNP_SECURITY_VERIFY_DATA`. ++ ++#### CLI ++ ++* Make password request more verbose. ++* Print `RSA` instead of `RSA (Encrypt and Sign)` in the key listing to avoid confusion. ++* Added option `--source` to specify detached signature's source file. ++* Added option `--no-wrap` to allow raw data encryption. ++* Added option `--current-time` to allow to override current timestamp. ++* Strip known extensions (like `.pgp`, `.asc`, etc.) when decrypting or verifying data. ++* Display key and signature validity status in the key listing. ++* Do not attempt to use GnuPG's config to set default key. ++ + ### 0.16.0 [2022-01-20] + + #### General + + * Added support for OpenSSL cryptography backend so RNP may be built and used on systems without the Botan installed. +diff --git a/comm/third_party/rnp/LICENSE-OCB.md b/comm/third_party/rnp/LICENSE-OCB.md +--- a/comm/third_party/rnp/LICENSE-OCB.md ++++ b/comm/third_party/rnp/LICENSE-OCB.md +@@ -4,11 +4,11 @@ License for OCB Usage + This license has been graciously granted by Professor Phillip Rogaway to allow + users of [`rnp`](https://github.com/rnpgp/rnp) to utilize the patented + [OCB](http://web.cs.ucdavis.edu/~rogaway/ocb/) blockcipher mode of operation, + which simultaneously provides privacy and authenticity. + +-The license text is presented below in plain text form purely for referencial ++The license text is presented below in plain text form purely for referential + purposes. The original signed license is available on request from Ribose Inc., + reachable at open.source@ribose.com. + + This file adheres to the formatting guidelines of + [readable-licenses](https://github.com/nevir/readable-licenses). +diff --git a/comm/third_party/rnp/docs/develop.adoc b/comm/third_party/rnp/docs/develop.adoc +--- a/comm/third_party/rnp/docs/develop.adoc ++++ b/comm/third_party/rnp/docs/develop.adoc +@@ -72,40 +72,37 @@ history of `master`, depending on how th + squashing. From the other hand, developers should squash commits and + create meaningful commit stack before PR is merged into mainstream branch. + Merging commits like "Fix build" or "Implement comments from code review" + should be avoided. + +-== Continuous Integration (Travis CI) +- +-Travis CI is used for continuously testing new commits and pull +-requests. ++== Continuous Integration (Github Actions) + +-We use the sudo-less beta Ubuntu Trusty containers, which do not permit +-root access. ++Github actions are used for continuously testing new commits and pull requests. ++Those include testing for different operating systems, linting via clang-format and shellcheck, ++and code coverage and quality checks via `Codecov` and `LGTM.io`. + +-See the file `.travis.yml` and the scripts in `ci/` for the most +-up-to-date details. ++For Github workflows sources see `.github/workflows/` folder and scripts from the `ci/` folder. ++Also there is a Cirrus CI runner, configuration for which is stored in `.cirrus.yml`. + + === Reproducing Locally + +-Sometimes tests fail in Travis CI and you will want to reproduce them +-locally for easier troubleshooting. +- +-We can use a container for this, like so: ++If tests fail in CI, you may attempt to reproduce those locally via `ctest` command: + + [source,console] + -- +-./travis.sh ++ctest -j4 -V -R rnp_tests + -- + +-Inside the container, you can do local CI runs like so: ++Or, more specific: + + [source,console] + -- +-env GPG_VERSION=beta BUILD_MODE=sanitize ci/local.sh ++ctest -V -R cli_tests-Misc + -- + ++If test fails under the specific OS, you should construct corresponding Docker container and run tests inside, taking Github workflows as a guide. ++ + == Code Coverage + + CodeCov is used for assessing our test coverage. + The current coverage can always be viewed here: https://codecov.io/github/rnpgp/rnp/ + +@@ -113,42 +110,16 @@ The current coverage can always be viewe + + === Static Analysis + + ==== Coverity Scan + +-Coverity Scan is used for occasional static analysis of the code base. +- +-To initiate analysis, a developer must push to the `coverity_scan` branch. +-You may wish to perform a clean clone for this, like so: +- +-[source,console] +--- +-cd /tmp +- +-git clone https://github.com/rnpgp/rnp +-# or +-# git clone git@github.com:rnpgp/rnp.git +-cd rnp ++Coverity Scan is used for static analysis of the code base. ++It is run daily on the master branch via the Github actions. ++See `.github/workflows/coverity.yml` for the details. + +-# switch to the coverity_scan branch +-git checkout coverity_scan +- +-# replay all commits from master onto coverity_scan +-git rebase master coverity_scan +- +-# forcefully push the coverity_scan branch +-git push -u origin coverity_scan -f +--- +- +-Note: The `master` and `coverity_scan` branches have separate +-`.travis.yml` files, so you may need to perform a manual merge. In +-general, the `coverity_scan` branch's `.travis.yml` is identical to +-`master`'s, but with a build matrix of only one entry. +- +-The results can be accessed on +-https://scan.coverity.com/projects/rnpgp-rnp. You will need to +-create an account and request access to the rnpgp/rnp project. ++The results can be accessed on https://scan.coverity.com/projects/rnpgp-rnp. ++You will need to create an account and request access to the rnpgp/rnp project. + + Since the scan results are not updated live, line numbers may no longer + be accurate against the `master` branch, issues may already be resolved, + etc. + +diff --git a/comm/third_party/rnp/docs/develop/packaging.adoc b/comm/third_party/rnp/docs/develop/packaging.adoc +--- a/comm/third_party/rnp/docs/develop/packaging.adoc ++++ b/comm/third_party/rnp/docs/develop/packaging.adoc +@@ -42,10 +42,11 @@ yum -y install epel-release + yum -y install git cmake3 make gcc-c++ + yum -y install bzip2-devel zlib-devel json-c12-devel + + # botan + rpm --import https://github.com/riboseinc/yum/raw/master/ribose-packages.pub ++rpm --import https://github.com/riboseinc/yum/raw/master/ribose-packages-next.pub + curl -L https://github.com/riboseinc/yum/raw/master/ribose.repo > /etc/yum.repos.d/ribose.repo + yum -y install botan2-devel + -- + + === 4. Build the RPM +diff --git a/comm/third_party/rnp/docs/installation.adoc b/comm/third_party/rnp/docs/installation.adoc +--- a/comm/third_party/rnp/docs/installation.adoc ++++ b/comm/third_party/rnp/docs/installation.adoc +@@ -41,10 +41,11 @@ We provide pre-built packages for RHEL a + at GitHub. + + [source,console] + ---- + rpm --import https://github.com/riboseinc/yum/raw/master/ribose-packages.pub ++rpm --import https://github.com/riboseinc/yum/raw/master/ribose-packages-next.pub + curl -L https://github.com/riboseinc/yum/raw/master/ribose.repo > /etc/yum.repos.d/ribose.repo + yum install -y rnp + ---- + + == On Ubuntu +@@ -52,11 +53,11 @@ yum install -y rnp + Prerequisite: please ensure `git` is installed on the system. + + [source,console] + ---- + # Clone the repository by version tag (or omit it to get the latest sources) +-git clone https://github.com/rnpgp/rnp.git -b v0.16.0 ++git clone https://github.com/rnpgp/rnp.git -b v0.16.2 + + # Install required packages + sudo apt install g++-8 cmake libbz2-dev zlib1g-dev libjson-c-dev \ + build-essential python-minimal + +@@ -88,11 +89,11 @@ sudo make install + Prerequisite: please ensure `git` is installed on the system. + + [source,console] + ---- + # Clone the repository by version tag (or omit it to get the latest sources) +-git clone https://github.com/rnpgp/rnp.git -b v0.16.0 ++git clone https://github.com/rnpgp/rnp.git -b v0.16.2 + + # Enable access to `testing` packages by editing /etc/apt/sources.list + # deb http://deb.debian.org/debian testing main + + # Install required packages +diff --git a/comm/third_party/rnp/include/repgp/repgp_def.h b/comm/third_party/rnp/include/repgp/repgp_def.h +--- a/comm/third_party/rnp/include/repgp/repgp_def.h ++++ b/comm/third_party/rnp/include/repgp/repgp_def.h +@@ -93,11 +93,11 @@ + /* Size of the keyid */ + #define PGP_KEY_ID_SIZE 8 + + /* Size of the fingerprint */ + #define PGP_FINGERPRINT_SIZE 20 +-#define PGP_FINGERPRINT_HEX_SIZE (PGP_FINGERPRINT_SIZE * 3) + 1 ++#define PGP_FINGERPRINT_HEX_SIZE (PGP_FINGERPRINT_SIZE * 2) + 1 + + /* Size of the key grip */ + #define PGP_KEY_GRIP_SIZE 20 + + /* PGP marker packet contents */ +diff --git a/comm/third_party/rnp/include/rnp/rnp.h b/comm/third_party/rnp/include/rnp/rnp.h +--- a/comm/third_party/rnp/include/rnp/rnp.h ++++ b/comm/third_party/rnp/include/rnp/rnp.h +@@ -43,10 +43,13 @@ 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) + ++/* Export base64-encoded autocrypt key instead of binary */ ++#define RNP_KEY_EXPORT_BASE64 (1U << 9) ++ + #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) +@@ -76,10 +79,11 @@ typedef uint32_t rnp_result_t; + */ + #define RNP_LOAD_SAVE_PUBLIC_KEYS (1U << 0) + #define RNP_LOAD_SAVE_SECRET_KEYS (1U << 1) + #define RNP_LOAD_SAVE_PERMISSIVE (1U << 8) + #define RNP_LOAD_SAVE_SINGLE (1U << 9) ++#define RNP_LOAD_SAVE_BASE64 (1U << 10) + + /** + * Flags for the rnp_key_remove_signatures + */ + +@@ -116,13 +120,26 @@ typedef uint32_t rnp_result_t; + + /** + * Flags for feature security rules. + */ + #define RNP_SECURITY_OVERRIDE (1U << 0) ++#define RNP_SECURITY_VERIFY_KEY (1U << 1) ++#define RNP_SECURITY_VERIFY_DATA (1U << 2) + #define RNP_SECURITY_REMOVE_ALL (1U << 16) + + /** ++ * Encryption flags ++ */ ++#define RNP_ENCRYPT_NOWRAP (1U << 0) ++ ++/** ++ * Decryption/verification flags ++ */ ++#define RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT (1U << 0) ++#define RNP_VERIFY_REQUIRE_ALL_SIGS (1U << 1) ++ ++/** + * Return a constant string describing the result code + */ + RNP_API const char *rnp_result_to_string(rnp_result_t result); + + RNP_API const char *rnp_version_string(); +@@ -364,37 +381,38 @@ RNP_API rnp_result_t rnp_ffi_set_pass_pr + /* Operations on key rings */ + + /** retrieve the default homedir (example: /home/user/.rnp) + * + * @param homedir pointer that will be set to the homedir path. +- * The caller should free this with rnp_buffer_free. ++ * The caller should free this with rnp_buffer_destroy. + * @return RNP_SUCCESS on success, or any other value on error + */ + RNP_API rnp_result_t rnp_get_default_homedir(char **homedir); + +-/** try to detect the formats and paths of the homedir keyrings +- * ++/** Try to detect the formats and paths of the homedir keyrings. + * @param homedir the path to the home directory (example: /home/user/.rnp) + * @param pub_format pointer that will be set to the format of the public keyring. +- * The caller should free this with rnp_buffer_free. ++ * The caller should free this with rnp_buffer_destroy. ++ * Note: this and below may be set to NULL in case of no known format is found. + * @param pub_path pointer that will be set to the path to the public keyring. +- * The caller should free this with rnp_buffer_free. ++ * The caller should free this with rnp_buffer_destroy. + * @param sec_format pointer that will be set to the format of the secret keyring. +- * The caller should free this with rnp_buffer_free. ++ * The caller should free this with rnp_buffer_destroy. + * @param sec_path pointer that will be set to the path to the secret keyring. +- * The caller should free this with rnp_buffer_free. +- * @return RNP_SUCCESS on success, or any other value on error ++ * The caller should free this with rnp_buffer_destroy. ++ * @return RNP_SUCCESS on success (even if no known format was found), or any other value on ++ * error. + */ + RNP_API rnp_result_t rnp_detect_homedir_info( + const char *homedir, char **pub_format, char **pub_path, char **sec_format, char **sec_path); + + /** try to detect the key format of the provided data + * + * @param buf the key data, must not be NULL + * @param buf_len the size of the buffer, must be > 0 + * @param format pointer that will be set to the format of the keyring. +- * Must not be NULL. The caller should free this with rnp_buffer_free. ++ * Must not be NULL. The caller should free this with rnp_buffer_destroy. + * @return RNP_SUCCESS on success, or any other value on error + */ + RNP_API rnp_result_t rnp_detect_key_format(const uint8_t buf[], size_t buf_len, char **format); + + /** Get the number of s2k hash iterations, based on calculation time requested. +@@ -446,10 +464,15 @@ RNP_API rnp_result_t rnp_supported_featu + * @param flags additional flags. Following ones currently supported: + * - RNP_SECURITY_OVERRIDE : override all other rules for the specified feature. + * May be used to temporarily enable or disable some feature value (e.g., to + * enable verification of SHA1 or MD5 signature), and then revert changes via + * rnp_remove_security_rule(). ++ * - RNP_SECURITY_VERIFY_KEY : limit rule only to the key signature verification. ++ * - RNP_SECURITY_VERIFY_DATA : limit rule only to the data signature ++ * verification. ++ * Note: by default rule applies to all possible usages. ++ * + * @param from timestamp, from when the rule is active. Objects that have creation time (like + * signatures) are matched with the closest rules from the past, unless there is + * a rule with an override flag. For instance, given a single rule with algorithm + * 'MD5', level 'insecure' and timestamp '2012-01-01', all signatures made before + * 2012-01-01 using the MD5 hash algorithm are considered to be at the default +@@ -482,11 +505,17 @@ RNP_API rnp_result_t rnp_add_security_ru + * + * @param ffi initialized FFI object. + * @param type feature type to search for. Only RNP_FEATURE_HASH_ALG is supported right now. + * @param name feature name, i.e. SHA1 or so on. + * @param time timestamp for which feature should be checked. +- * @param flags if non-NULL then rule's flags will be put here. ++ * @param flags if non-NULL then rule's flags will be put here. In this case *flags must be ++ * initialized to the desired usage limitation: ++ * - 0 to look up for any usage (this is also assumed if flags parameter is ++ * NULL). ++ * - RNP_SECURITY_VERIFY_KEY, RNP_SECURITY_VERIFY_DATA and so on to look up for ++ * the specific usage. Please note that constants cannot be ORed here, only ++ * single one must be present. + * @param from if non-NULL then rule's from time will be put here. + * @param level cannot be NULL. Security level will be stored here. + * @return RNP_SUCCESS or any other value on error. + */ + RNP_API rnp_result_t rnp_get_security_rule(rnp_ffi_t ffi, +@@ -506,10 +535,12 @@ RNP_API rnp_result_t rnp_get_security_ru + * @param type type of the feature. If NULL, then all of the rules will be cleared. + * @param name name of the feature. If NULL, then all rules of the type will be cleared. + * @param level security level of the rule. + * @param flags additional flags, following are defined at the moment: + * - RNP_SECURITY_OVERRIDE : rule should match this flag ++ * - RNP_SECURITY_VERIFY_KEY, RNP_SECURITY_VERIFY_DATA : rule should match these flags ++ * (can be ORed together) + * - RNP_SECURITY_REMOVE_ALL : remove all rules for type and name. + * @param from timestamp, for when the rule should be removed. Ignored if + * RNP_SECURITY_REMOVE_ALL_FROM is specified. + * @param removed if non-NULL then number of removed rules will be stored here. + * @return RNP_SUCCESS on success or any other value on error. Please note that if no rules are +@@ -540,10 +571,24 @@ RNP_API rnp_result_t rnp_remove_security + RNP_API rnp_result_t rnp_request_password(rnp_ffi_t ffi, + rnp_key_handle_t key, + const char * context, + char ** password); + ++/** ++ * @brief Set timestamp, used in all operations instead of system's time. These operations ++ * include key/signature generation (this timestamp will be used as signature/key ++ * creation date), verification of the keys and signatures (this timestamp will be used ++ * as 'current' time). ++ * Please note, that exactly this timestamp will be used during the whole ffi lifetime. ++ * ++ * @param ffi initialized FFI structure ++ * @param time non-zero timestamp to be used. Zero value restores original behaviour and uses ++ * system's time. ++ * @return RNP_SUCCESS or other value on error. ++ */ ++RNP_API rnp_result_t rnp_set_timestamp(rnp_ffi_t ffi, uint64_t time); ++ + /** load keys + * + * Note that for G10, the input must be a directory (which must already exist). + * + * @param ffi +@@ -576,10 +621,12 @@ RNP_API rnp_result_t rnp_unload_keys(rnp + * If flag RNP_LOAD_SAVE_SINGLE is set, then only first key will be loaded (subkey + * or primary key with its subkeys). In case RNP_LOAD_SAVE_PERMISSIVE and + * erroneous first key on the stream RNP_SUCCESS will be returned, but results + * will include an empty array. Also RNP_ERROR_EOF will be returned if the last + * key was read. ++ * RNP_LOAD_SAVE_BASE64 should set to allow import of base64-encoded keys (i.e. ++ * autocrypt ones). By default only binary and OpenPGP-armored keys are allowed. + * @param results if not NULL then after the successful execution will contain JSON with + * information about new and updated keys. You must free it using the + * rnp_buffer_destroy() function. + * @return RNP_SUCCESS on success + * RNP_ERROR_EOF if last key was read (if RNP_LOAD_SAVE_SINGLE was used) +@@ -702,12 +749,12 @@ RNP_API rnp_result_t rnp_generate_key_sm + const char * userid, + const char * password, + rnp_key_handle_t *key); + + /** +- * @brief Shortcut for quick key generation. While it is used in other shortcut functions for +- * key generation ++ * @brief Shortcut for quick key generation. It is used in other shortcut functions for ++ * key generation (rnp_generate_key_*). + * + * @param ffi + * @param key_alg string with primary key algorithm. Cannot be NULL. + * @param sub_alg string with subkey algorithm. If NULL then subkey will not be generated. + * @param key_bits size of key in bits. If zero then default value will be used. +@@ -717,10 +764,12 @@ RNP_API rnp_result_t rnp_generate_key_sm + * @param key_curve Curve name. Must be non-NULL only with EC-based primary key algorithm, + * otherwise error will be returned. + * @param sub_curve Subkey curve name. Must be non-NULL only with EC-based subkey algorithm, + * otherwise error will be returned. + * @param userid String with userid. Cannot be NULL. ++ * @param password String with password which would be used to protect the key and subkey. ++ * If NULL then key will be stored in cleartext (unencrypted). + * @param key if non-NULL, then handle of the primary key will be stored here on success. + * Caller must destroy it with rnp_key_handle_destroy() call. + * @return RNP_SUCCESS or error code instead. + */ + RNP_API rnp_result_t rnp_generate_key_ex(rnp_ffi_t ffi, +@@ -986,11 +1035,11 @@ RNP_API rnp_result_t rnp_op_generate_exe + * You must free handle after use with rnp_key_handle_destroy. + * @return RNP_SUCCESS or error code if failed. + */ + RNP_API rnp_result_t rnp_op_generate_get_key(rnp_op_generate_t op, rnp_key_handle_t *handle); + +-/** Free resources associated with signing operation. ++/** Free resources associated with key generation operation. + * + * @param op opaque key generation context. Must be successfully initialized with one of the + * rnp_op_generate_*_create functions. + * @return RNP_SUCCESS or error code if failed. + */ +@@ -1011,11 +1060,12 @@ RNP_API rnp_result_t rnp_key_export(rnp_ + * + * @param key primary key handle, cannot be NULL. + * @param subkey subkey to export. May be NULL to pick the first suitable. + * @param uid userid to export. May be NULL if key has only one uid. + * @param output the stream to write to +- * @param flags additional flags, must be 0 for now. ++ * @param flags additional flags. Currently only RNP_KEY_EXPORT_BASE64 is supported. Enabling ++ * it would export key base64-encoded instead of binary. + * @return RNP_SUCCESS on success, or any other value if failed. + */ + RNP_API rnp_result_t rnp_key_export_autocrypt(rnp_key_handle_t key, + rnp_key_handle_t subkey, + const char * uid, +@@ -1028,11 +1078,11 @@ RNP_API rnp_result_t rnp_key_export_auto + * rnp_key_revoke() function. + * @param key primary key to be revoked. Must have secret key, otherwise keyrings will be + * searched for the authorized to issue revocation signature secret key. If secret + * key is locked then password will be asked via password provider. + * @param output signature contents will be saved here. +- * @param flags currently must be 0. ++ * @param flags must be RNP_KEY_EXPORT_ARMORED or 0. + * @param hash hash algorithm used to calculate signature. Pass NULL for default algorithm + * selection. + * @param code reason for revocation code. Possible values: 'no', 'superseded', 'compromised', + * 'retired'. May be NULL - then 'no' value will be used. + * @param reason textual representation of the reason for revocation. May be NULL or empty +@@ -1385,10 +1435,20 @@ RNP_API rnp_result_t rnp_signature_get_e + * later on using the rnp_buffer_destroy() function. + * @return RNP_SUCCESS or error code if failed. + */ + RNP_API rnp_result_t rnp_signature_get_keyid(rnp_signature_handle_t sig, char **result); + ++/** Get signer's key fingerprint from the signature. ++ * Note: if key fingerprint is not available from the signature then NULL value will ++ * be stored to result. ++ * @param sig signature handle ++ * @param result hex-encoded key fp will be stored here. Cannot be NULL. You must free it ++ * later on using the rnp_buffer_destroy() function. ++ * @return RNP_SUCCESS or error code if failed. ++ */ ++RNP_API rnp_result_t rnp_signature_get_key_fprint(rnp_signature_handle_t sig, char **result); ++ + /** Get signing key handle, if available. + * Note: if signing key is not available then NULL will be stored in key. + * @param sig signature handle + * @param key on success and key availability will contain signing key's handle. You must + * destroy it using the rnp_key_handle_destroy() function. +@@ -1626,15 +1686,16 @@ RNP_API rnp_result_t rnp_key_get_grip(rn + RNP_API rnp_result_t rnp_key_get_primary_grip(rnp_key_handle_t key, char **grip); + + /** + * @brief Get primary's key fingerprint for the subkey, if available. + * +- * @param key key handle, should not be NULL ++ * @param key subkey handle, should not be NULL + * @param grip pointer to the NULL-terminated string with hex-encoded key fingerprint or NULL + * will be stored here, depending whether primary key is available or not. You must + * free it later using rnp_buffer_destroy function. +- * @return RNP_SUCCESS or error code on failure. ++ * @return RNP_SUCCESS on success, RNP_BAD_PARAMETERS if not a subkey, or other error code ++ * on failure. + */ + RNP_API rnp_result_t rnp_key_get_primary_fprint(rnp_key_handle_t key, char **fprint); + + /** + * @brief Check whether certain usage type is allowed for the key. +@@ -1752,10 +1813,22 @@ RNP_API rnp_result_t rnp_key_is_compromi + * @param result on success result will be stored here. Could not be NULL. + * @return RNP_SUCCESS or error code on failure. + */ + RNP_API rnp_result_t rnp_key_is_retired(rnp_key_handle_t key, bool *result); + ++/** ++ * @brief Check whether key is expired. ++ * Note: while expired key cannot be used to generate new signatures or encrypt to, it ++ * still could be used to check older signatures/decrypt previously encrypted data. ++ * ++ * @param key key handle, should not be NULL. ++ * @param result on success result will be stored here. True means that key is expired and is ++ * not usable and false otherwise. ++ * @return RNP_SUCCESS or error code on failure. ++ */ ++RNP_API rnp_result_t rnp_key_is_expired(rnp_key_handle_t key, bool *result); ++ + /** check if a key is currently locked + * + * @param key + * @param result pointer to hold the result. This will be set to true if + * the key is currently locked, or false otherwise. Must not be NULL. +@@ -2029,11 +2102,11 @@ RNP_API rnp_result_t rnp_op_sign_clearte + /** @brief Create detached signing operation context. Output will contain only signature of the + * source data. + * @param op pointer to opaque signing context + * @param ffi + * @param input stream with data to be signed. Could not be NULL. +- * @param output stream to write results to. Could not be NULL. ++ * @param signature stream to write results to. Could not be NULL. + * @return RNP_SUCCESS or error code if failed + */ + RNP_API rnp_result_t rnp_op_sign_detached_create(rnp_op_sign_t *op, + rnp_ffi_t ffi, + rnp_input_t input, +@@ -2157,12 +2230,13 @@ RNP_API rnp_result_t rnp_op_sign_execute + RNP_API rnp_result_t rnp_op_sign_destroy(rnp_op_sign_t op); + + /* Verification */ + + /** @brief Create verification operation context. This method should be used for embedded +- * signatures or cleartext signed data. For detached verification corresponding +- * function should be used. ++ * signatures, cleartext signed data and encrypted (and possibly signed) data. ++ * For the detached signature verification the function rnp_op_verify_detached_create() ++ * should be used. + * @param op pointer to opaque verification context + * @param ffi + * @param input stream with signed data. Could not be NULL. + * @param output stream to write results to. Could not be NULL, but may be null output stream + * if verified data should be discarded. +@@ -2183,16 +2257,36 @@ RNP_API rnp_result_t rnp_op_verify_creat + RNP_API rnp_result_t rnp_op_verify_detached_create(rnp_op_verify_t *op, + rnp_ffi_t ffi, + rnp_input_t input, + rnp_input_t signature); + ++/** ++ * @brief Set additional flags which control data verification/decryption process. ++ * ++ * @param op pointer to opaque verification context. ++ * @param flags verification flags. OR-ed combination of RNP_VERIFY_* values. ++ * Following flags are supported: ++ * RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT - ignore invalid signatures for the encrypted ++ * and signed data. If this flag is set then rnp_op_verify_execute() call will ++ * succeed and output data even if all signatures are invalid or issued by the ++ * unknown key(s). ++ * RNP_VERIFY_REQUIRE_ALL_SIGS - require that all signatures (if any) must be ++ * valid for successful run of rnp_op_verify_execute(). ++ * @return RNP_SUCCESS or error code if failed ++ */ ++RNP_API rnp_result_t rnp_op_verify_set_flags(rnp_op_verify_t op, uint32_t flags); ++ + /** @brief Execute previously initialized verification operation. + * @param op opaque verification context. Must be successfully initialized. +- * @return RNP_SUCCESS if data was processed successfully and all signatures are valid. +- * Otherwise error code is returned. After rnp_op_verify_execute() +- * rnp_op_verify_get_* functions may be used to query information about the +- * signature(s). ++ * @return RNP_SUCCESS if data was processed successfully and output may be used. By default ++ * this means at least one valid signature for the signed data, or successfully ++ * decrypted data if no signatures are present. ++ * This behaviour may be overriden via rnp_op_verify_set_flags() call. ++ * ++ * To check number of signatures and their verification status use functions ++ * rnp_op_verify_get_signature_count() and rnp_op_verify_get_signature_at(). ++ * To check data encryption status use function rnp_op_verify_get_protection_info(). + */ + RNP_API rnp_result_t rnp_op_verify_execute(rnp_op_verify_t op); + + /** @brief Get number of the signatures for verified data. + * @param op opaque verification context. Must be initialized and have execute() called on it. +@@ -2212,12 +2306,12 @@ RNP_API rnp_result_t rnp_op_verify_get_s + + /** @brief Get embedded in OpenPGP data file name and modification time. Makes sense only for + * embedded signature verification. + * @param op opaque verification context. Must be initialized and have execute() called on it. + * @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. ++ * via the rnp_buffer_destroy 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_API rnp_result_t rnp_op_verify_get_file_info(rnp_op_verify_t op, + char ** filename, +@@ -2226,19 +2320,19 @@ RNP_API rnp_result_t rnp_op_verify_get_f + /** + * @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: ++ * freeing it using the rnp_buffer_destroy() 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. ++ * needed. Must be freed by rnp_buffer_destroy() 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. + */ +@@ -2403,29 +2497,29 @@ RNP_API rnp_result_t rnp_op_verify_destr + * @return signature verification status: + * RNP_SUCCESS : signature is valid + * RNP_ERROR_SIGNATURE_EXPIRED : signature is valid but expired + * RNP_ERROR_KEY_NOT_FOUND : public key to verify signature was not available + * RNP_ERROR_SIGNATURE_INVALID : data or signature was modified ++ * RNP_ERROR_SIGNATURE_UNKNOWN : signature has unknown format + */ + RNP_API rnp_result_t rnp_op_verify_signature_get_status(rnp_op_verify_signature_t sig); + + /** Get the signature handle from the verified signature. This would allow to query extended + * information on the signature. + * + * @param sig verified signature context, cannot be NULL. + * @param handle signature handle will be stored here on success. You must free it after use +- * with +- * the rnp_signature_handle_destroy() function. ++ * with the rnp_signature_handle_destroy() function. + * @return RNP_SUCCESS or error code if failed. + */ + RNP_API rnp_result_t rnp_op_verify_signature_get_handle(rnp_op_verify_signature_t sig, + rnp_signature_handle_t * handle); + + /** @brief Get hash function used to calculate signature + * @param sig opaque signature context obtained via rnp_op_verify_get_signature_at call. + * @param hash pointer to string with hash algorithm name will be put here on success. +- * Caller is responsible for freeing it with rnp_buffer_free ++ * Caller is responsible for freeing it with rnp_buffer_destroy + * @return RNP_SUCCESS or error code otherwise + */ + RNP_API rnp_result_t rnp_op_verify_signature_get_hash(rnp_op_verify_signature_t sig, + char ** hash); + +@@ -2468,15 +2562,24 @@ RNP_API void rnp_buffer_clear(void *ptr, + * @brief Initialize input struct to read from a path + * + * @param input pointer to the input opaque structure + * @param path path of the file to read from + * @return RNP_SUCCESS if operation succeeded and input struct is ready to read, or error code +- * otherwise ++ * otherwise + */ + RNP_API rnp_result_t rnp_input_from_path(rnp_input_t *input, const char *path); + + /** ++ * @brief Initialize input struct to read from the stdin ++ * ++ * @param input pointer to the input opaque structure ++ * @return RNP_SUCCESS if operation succeeded and input struct is ready to read, or error code ++ * otherwise ++ */ ++RNP_API rnp_result_t rnp_input_from_stdin(rnp_input_t *input); ++ ++/** + * @brief Initialize input struct to read from memory + * + * @param input pointer to the input opaque structure + * @param buf memory buffer. Could not be NULL. + * @param buf_len number of bytes available to read from buf +@@ -2517,11 +2620,11 @@ RNP_API rnp_result_t rnp_input_destroy(r + * that already exists then it will be overwritten. + * + * @param output pointer to the opaque output structure. + * @param path path to the file. + * @return RNP_SUCCESS if file was opened successfully and ready for writing or error code +- * otherwise. ++ * otherwise. + */ + RNP_API rnp_result_t rnp_output_to_path(rnp_output_t *output, const char *path); + + /** + * @brief Initialize structure to write to a file. +@@ -2539,10 +2642,19 @@ RNP_API rnp_result_t rnp_output_to_path( + RNP_API rnp_result_t rnp_output_to_file(rnp_output_t *output, + const char * path, + uint32_t flags); + + /** ++ * @brief Initialize structure to write to the stdout. ++ * ++ * @param output pointer to the opaque output structure. After use you must free it using the ++ * rnp_output_destroy() function. ++ * @return RNP_SUCCESS if output was initialized successfully or error code otherwise. ++ */ ++RNP_API rnp_result_t rnp_output_to_stdout(rnp_output_t *output); ++ ++/** + * @brief Initialize output structure to write to the memory. + * + * @param output pointer to the opaque output structure. + * @param max_alloc maximum amount of memory to allocate. 0 value means unlimited. + * @return RNP_SUCCESS if operation succeeded or error code otherwise. +@@ -2773,10 +2885,23 @@ RNP_API rnp_result_t rnp_op_encrypt_set_ + RNP_API rnp_result_t rnp_op_encrypt_set_compression(rnp_op_encrypt_t op, + const char * compression, + int level); + + /** ++ * @brief Set additional encryption flags. ++ * ++ * @param op opaque encrypting context. Must be allocated and initialized. ++ * @param flags encryption flags. ORed combination of RNP_ENCRYPT_* values. ++ * Following flags are supported: ++ * RNP_ENCRYPT_NOWRAP - do not wrap the data in a literal data packet. This ++ * would allow to encrypt already signed data. ++ * ++ * @return RNP_SUCESS or error code if failed. ++ */ ++RNP_API rnp_result_t rnp_op_encrypt_set_flags(rnp_op_encrypt_t op, uint32_t flags); ++ ++/** + * @brief set the internally stored file name for the data being encrypted + * + * @param op opaque encrypted context. Must be allocated and initialized + * @param filename file name as NULL-terminated string. May be empty string. Value "_CONSOLE" + * may have specific processing (see RFC 4880 for the details), depending on implementation. +@@ -2794,10 +2919,23 @@ RNP_API rnp_result_t rnp_op_encrypt_set_ + RNP_API rnp_result_t rnp_op_encrypt_set_file_mtime(rnp_op_encrypt_t op, uint32_t mtime); + + RNP_API rnp_result_t rnp_op_encrypt_execute(rnp_op_encrypt_t op); + RNP_API rnp_result_t rnp_op_encrypt_destroy(rnp_op_encrypt_t op); + ++/** ++ * @brief Decrypt encrypted data in input and write it to the output on success. ++ * If data is additionally signed then signatures are ignored. ++ * For more control over the decryption process see functions rnp_op_verify_create() and ++ * rnp_op_verify_execute(), which allows to verify signatures as well as decrypt data ++ * and retrieve encryption-related information. ++ * ++ * @param ffi initialized FFI object. Cannot be NULL. ++ * @param input source with encrypted data. Cannot be NULL. ++ * @param output on success decrypted data will be written here. Cannot be NULL. ++ * @return RNP_SUCCESS if data was successfully decrypted and written to the output, or any ++ * other value on error. ++ */ + RNP_API rnp_result_t rnp_decrypt(rnp_ffi_t ffi, rnp_input_t input, rnp_output_t output); + + /** retrieve the raw data for a public key + * + * This will always be PGP packets and will never include ASCII armor. +@@ -2839,11 +2977,11 @@ RNP_API rnp_result_t rnp_key_to_json(rnp + + /** create an identifier iterator + * + * @param ffi + * @param it pointer that will be set to the created iterator +- * @param identifier_type the type of identifier ("userid", "keyid", "grip") ++ * @param identifier_type the type of identifier ("userid", "keyid", "grip", "fingerprint") + * @return RNP_SUCCESS on success, or any other value on error + */ + RNP_API rnp_result_t rnp_identifier_iterator_create(rnp_ffi_t ffi, + rnp_identifier_iterator_t *it, + const char *identifier_type); +diff --git a/comm/third_party/rnp/include/rnp/rnp_err.h b/comm/third_party/rnp/include/rnp/rnp_err.h +--- a/comm/third_party/rnp/include/rnp/rnp_err.h ++++ b/comm/third_party/rnp/include/rnp/rnp_err.h +@@ -61,10 +61,11 @@ enum { + RNP_ERROR_SIGNING_FAILED, + RNP_ERROR_NO_SIGNATURES_FOUND, + + RNP_ERROR_SIGNATURE_EXPIRED, + RNP_ERROR_VERIFICATION_FAILED, ++ RNP_ERROR_SIGNATURE_UNKNOWN, + + /* Parsing */ + RNP_ERROR_NOT_ENOUGH_DATA = 0x13000000, + RNP_ERROR_UNKNOWN_TAG, + RNP_ERROR_PACKET_NOT_CONSUMED, +diff --git a/comm/third_party/rnp/src/common/file-utils.cpp b/comm/third_party/rnp/src/common/file-utils.cpp +--- a/comm/third_party/rnp/src/common/file-utils.cpp ++++ b/comm/third_party/rnp/src/common/file-utils.cpp +@@ -37,13 +37,14 @@ + #else + #include + #include + #include + #endif // !_MSC_VER ++#include "str-utils.h" ++#include + #ifdef _WIN32 + #include // for rnp_mkstemp +-#include "str-utils.h" + #define CATCH_AND_RETURN(v) \ + catch (...) \ + { \ + errno = ENOMEM; \ + return v; \ +@@ -107,10 +108,20 @@ rnp_fopen(const char *filename, const ch + #else + return fopen(filename, mode); + #endif + } + ++FILE * ++rnp_fdopen(int fildes, const char *mode) ++{ ++#ifdef _WIN32 ++ return _fdopen(fildes, mode); ++#else ++ return fdopen(fildes, mode); ++#endif ++} ++ + int + rnp_access(const char *path, int mode) + { + #ifdef _WIN32 + try { +@@ -283,117 +294,63 @@ rnp_mkstemp(char *tmpl) + } + CATCH_AND_RETURN(-1) + } + #endif // _WIN32 + +-static char * +-vcompose_path(char **buf, size_t *buf_len, const char *first, va_list ap) ++namespace rnp { ++namespace path { ++inline char ++separator() + { +- size_t curlen = 0; +- char * tmp_buf = NULL; +- size_t tmp_buf_len = 0; +- +- if (!first) { +- return NULL; +- } +- if (!buf) { +- buf = &tmp_buf; +- } +- if (!buf_len) { +- buf_len = &tmp_buf_len; +- } +- +- const char *s = first; +- do { +- size_t len = strlen(s); ++#ifdef _WIN32 ++ return '\\'; ++#else ++ return '/'; ++#endif ++} + +- // current string len + NULL terminator + possible '/' + +- // len of this path component +- size_t reqsize = curlen + 1 + 1 + len; +- if (*buf_len < reqsize) { +- char *newbuf = (char *) realloc(*buf, reqsize); +- if (!newbuf) { +- // realloc failed, bail +- free(*buf); +- *buf = NULL; +- break; +- } +- *buf = newbuf; +- *buf_len = reqsize; +- } ++bool ++exists(const std::string &path, bool is_dir) ++{ ++ return is_dir ? rnp_dir_exists(path.c_str()) : rnp_file_exists(path.c_str()); ++} + +- if (s != first) { +- if ((*buf)[curlen - 1] != '/' && *s != '/') { +- // add missing separator +- (*buf)[curlen] = '/'; +- curlen += 1; +- } else if ((*buf)[curlen - 1] == '/' && *s == '/') { +- // skip duplicate separator +- s++; +- len--; +- } +- } +- memcpy(*buf + curlen, s, len + 1); +- curlen += len; +- } while ((s = va_arg(ap, const char *))); +- +- return *buf; ++bool ++empty(const std::string &path) ++{ ++ auto dir = rnp_opendir(path.c_str()); ++ bool empty = !dir || rnp_readdir_name(dir).empty(); ++ rnp_closedir(dir); ++ return empty; + } + +-/** compose a path from one or more components +- * +- * Notes: +- * - The final argument must be NULL. +- * - The caller must free the returned buffer. +- * - The returned buffer is always NULL-terminated. +- * +- * @param first the first path component +- * @return the composed path buffer. The caller must free it. +- */ +-char * +-rnp_compose_path(const char *first, ...) ++std::string ++HOME(const std::string &sdir) + { +- va_list ap; +- va_start(ap, first); +- char *path = vcompose_path(NULL, NULL, first, ap); +- va_end(ap); +- return path; ++ const char *home = getenv("HOME"); ++ if (!home) { ++ return ""; ++ } ++ return sdir.empty() ? home : append(home, sdir); ++} ++ ++static bool ++has_forward_slash(const std::string &path) ++{ ++ return std::find(path.begin(), path.end(), '/') != path.end(); + } + +-/** compose a path from one or more components +- * +- * This version is useful when a function is composing +- * multiple paths and wants to try to avoid unnecessary +- * allocations. +- * +- * Notes: +- * - The final argument must be NULL. +- * - The caller must free the returned buffer. +- * - The returned buffer is always NULL-terminated. +- * +- * @code +- * char *buf = NULL; +- * size_t buf_len = 0; +- * rnp_compose_path_ex(&buf, &buf_len, "/tmp", dir1, file1, NULL); +- * // the calls below will realloc the buffer if needed +- * rnp_compose_path_ex(&buf, &buf_len, "/tmp", dir3, NULL); +- * rnp_compose_path_ex(&buf, &buf_len, "/tmp", something, NULL); +- * free(buf); +- * @endcode +- * +- * @param buf pointer to the buffer where the result will be stored. +- * If buf is NULL, the caller must use the returned value. +- * If *buf is NULL, a new buffer will be allocated. +- * @param buf_len pointer to the allocated buffer size. +- * Can be NULL. +- * @param first the first path component +- * @return the composed path buffer. The caller must free it. +- */ +-char * +-rnp_compose_path_ex(char **buf, size_t *buf_len, const char *first, ...) ++std::string ++append(const std::string &path, const std::string &name) + { +- va_list ap; +- va_start(ap, first); +- char *path = vcompose_path(buf, buf_len, first, ap); +- va_end(ap); +- return path; ++ bool no_sep = path.empty() || name.empty() || (rnp::is_slash(path.back())) || ++ (rnp::is_slash(name.front())); ++ if (no_sep) { ++ return path + name; ++ } ++ /* Use forward slash if there is at least one in the path/name. */ ++ char sep = has_forward_slash(path) || has_forward_slash(name) ? '/' : separator(); ++ return path + sep + name; + } ++ ++} // namespace path ++} // namespace rnp +diff --git a/comm/third_party/rnp/src/common/file-utils.h b/comm/third_party/rnp/src/common/file-utils.h +--- a/comm/third_party/rnp/src/common/file-utils.h ++++ b/comm/third_party/rnp/src/common/file-utils.h +@@ -35,10 +35,11 @@ + bool rnp_file_exists(const char *path); + bool rnp_dir_exists(const char *path); + int64_t rnp_filemtime(const char *path); + int rnp_open(const char *filename, int oflag, int pmode); + FILE * rnp_fopen(const char *filename, const char *mode); ++FILE * rnp_fdopen(int fildes, const char *mode); + int rnp_access(const char *path, int mode); + int rnp_stat(const char *filename, struct stat *statbuf); + int rnp_rename(const char *oldpath, const char *newpath); + int rnp_unlink(const char *path); + +@@ -62,13 +63,10 @@ std::string rnp_readdir_name(DIR *dir); + #define R_OK 4 /* Test for read permission. */ + #define W_OK 2 /* Test for write permission. */ + #define F_OK 0 /* Test for existence. */ + #endif + +-char *rnp_compose_path(const char *first, ...); +-char *rnp_compose_path_ex(char **buf, size_t *buf_len, const char *first, ...); +- + /** @private + * generate a temporary file name based on TMPL. TMPL must match the + * rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed + * does not exist at the time of the call to mkstemp. TMPL is + * overwritten with the result.get the list item at specified index +@@ -76,6 +74,16 @@ char *rnp_compose_path_ex(char **buf, si + * @param tmpl filename template + * @return file descriptor of newly created and opened file, or -1 on error + **/ + int rnp_mkstemp(char *tmpl); + ++namespace rnp { ++namespace path { ++inline char separator(); ++bool exists(const std::string &path, bool is_dir = false); ++bool empty(const std::string &path); ++std::string HOME(const std::string &sdir = ""); ++std::string append(const std::string &path, const std::string &name); ++} // namespace path ++} // namespace rnp ++ + #endif +diff --git a/comm/third_party/rnp/src/common/str-utils.cpp b/comm/third_party/rnp/src/common/str-utils.cpp +--- a/comm/third_party/rnp/src/common/str-utils.cpp ++++ b/comm/third_party/rnp/src/common/str-utils.cpp +@@ -87,10 +87,50 @@ str_case_eq(const char *s1, const char * + s1++; + s2++; + } + return !*s1 && !*s2; + } ++ ++bool ++str_case_eq(const std::string &s1, const std::string &s2) ++{ ++ if (s1.size() != s2.size()) { ++ return false; ++ } ++ return str_case_eq(s1.c_str(), s2.c_str()); ++} ++ ++char * ++lowercase(char *s) ++{ ++ if (!s) { ++ return s; ++ } ++ for (char *ptr = s; *ptr; ++ptr) { ++ *ptr = tolower(*ptr); ++ } ++ return s; ++} ++ ++bool ++str_to_int(const std::string &s, int &val) ++{ ++ for (const char &ch : s) { ++ if ((ch < '0') || (ch > '9')) { ++ return false; ++ } ++ } ++ val = std::stoi(s); ++ return true; ++} ++ ++bool ++is_slash(char c) ++{ ++ return (c == '/') || (c == '\\'); ++} ++ + } // namespace rnp + + #ifdef _WIN32 + std::wstring + wstr_from_utf8(const char *s) +diff --git a/comm/third_party/rnp/src/common/str-utils.h b/comm/third_party/rnp/src/common/str-utils.h +--- a/comm/third_party/rnp/src/common/str-utils.h ++++ b/comm/third_party/rnp/src/common/str-utils.h +@@ -38,10 +38,17 @@ char *strip_eol(char *s); + * @return true if EOL was found and stripped, or false otherwise. + */ + bool strip_eol(std::string &s); + bool is_blank_line(const char *line, size_t len); + bool str_case_eq(const char *s1, const char *s2); ++bool str_case_eq(const std::string &s1, const std::string &s2); ++/** ++ * @brief Convert string to lowercase and return it. ++ */ ++char *lowercase(char *s); ++bool str_to_int(const std::string &s, int &val); ++bool is_slash(char c); + } // namespace rnp + #ifdef _WIN32 + #include + std::wstring wstr_from_utf8(const char *s); + std::wstring wstr_from_utf8(const char *first, const char *last); +diff --git a/comm/third_party/rnp/src/examples/sign.c b/comm/third_party/rnp/src/examples/sign.c +--- a/comm/third_party/rnp/src/examples/sign.c ++++ b/comm/third_party/rnp/src/examples/sign.c +@@ -142,11 +142,11 @@ ffi_sign() + rnp_key_handle_destroy(key); + key = NULL; + + /* finally do signing */ + if (rnp_op_sign_execute(sign) != RNP_SUCCESS) { +- fprintf(stdout, "failed to add signature for key 25519@key.\n"); ++ fprintf(stdout, "failed to sign\n"); + goto finish; + } + + fprintf(stdout, "Signing succeeded. See file signed.asc.\n"); + +diff --git a/comm/third_party/rnp/src/fuzzing/dump.c b/comm/third_party/rnp/src/fuzzing/dump.c +--- a/comm/third_party/rnp/src/fuzzing/dump.c ++++ b/comm/third_party/rnp/src/fuzzing/dump.c +@@ -33,18 +33,22 @@ dump_LLVMFuzzerTestOneInput(const uint8_ + #else + int + LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) + #endif + { +- rnp_input_t input = NULL; +- rnp_result_t ret = 0; +- ret = rnp_input_from_memory(&input, data, size, false); +- ++ rnp_input_t input = NULL; ++ (void) rnp_input_from_memory(&input, data, size, false); + rnp_output_t output = NULL; +- ret = rnp_output_to_null(&output); ++ (void) rnp_output_to_null(&output); + +- ret = rnp_dump_packets_to_output(input, output, RNP_DUMP_RAW); ++ (void) rnp_dump_packets_to_output(input, output, RNP_DUMP_RAW); + rnp_output_destroy(output); + rnp_input_destroy(input); + ++ (void) rnp_input_from_memory(&input, data, size, false); ++ char *json = NULL; ++ (void) rnp_dump_packets_to_json(input, RNP_DUMP_RAW, &json); ++ rnp_buffer_destroy(json); ++ rnp_input_destroy(input); ++ + return 0; + } +diff --git a/comm/third_party/rnp/src/lib/CMakeLists.txt b/third_party/rnp/src/lib/comm/CMakeLists.txt +--- a/comm/third_party/rnp/src/lib/CMakeLists.txt ++++ b/comm/third_party/rnp/src/lib/CMakeLists.txt +@@ -36,10 +36,13 @@ if (CRYPTO_BACKEND_BOTAN) + endif() + if (CRYPTO_BACKEND_OPENSSL) + include(FindOpenSSL) + find_package(OpenSSL 1.1.1 REQUIRED) + include(FindOpenSSLFeatures) ++ if("${OPENSSL_VERSION}" VERSION_GREATER_EQUAL "3.0.0") ++ set(CRYPTO_BACKEND_OPENSSL3 1) ++ endif() + endif() + + # generate a config.h + include(CheckIncludeFileCXX) + include(CheckCXXSymbolExists) +@@ -62,22 +65,78 @@ check_cxx_symbol_exists(realpath stdlib. + check_cxx_symbol_exists(O_BINARY fcntl.h HAVE_O_BINARY) + check_cxx_symbol_exists(_O_BINARY fcntl.h HAVE__O_BINARY) + check_cxx_symbol_exists(_tempnam stdio.h HAVE__TEMPNAM) + set(HAVE_ZLIB_H "${ZLIB_FOUND}") + set(HAVE_BZLIB_H "${BZIP2_FOUND}") +-configure_file(config.h.in config.h) + # generate a version.h + configure_file(version.h.in version.h) + ++# Checks feature in CRYPTO_BACKEND and puts the result into variable whose name is stored in RESULT_VARNAME variable. ++function(backend_has_feature FEATURE RESULT_VARNAME) ++ if (CRYPTO_BACKEND STREQUAL "botan") ++ check_cxx_symbol_exists("BOTAN_HAS_${FEATURE}" botan/build.h ${RESULT_VARNAME}) ++ else() ++ message(STATUS "Looking for OpenSSL feature ${FEATURE}") ++ OpenSSLHasFeature(${FEATURE} ${RESULT_VARNAME}) ++ if (${RESULT_VARNAME}) ++ message(STATUS "Looking for OpenSSL feature ${FEATURE} - found") ++ endif() ++ set(${RESULT_VARNAME} "${${RESULT_VARNAME}}" PARENT_SCOPE) ++ endif() ++endfunction() ++ ++function(resolve_feature_state RNP_FEATURE BACKEND_FEATURES) ++ if (NOT ${RNP_FEATURE}) # User has explicitly disabled this feature ++ return() ++ endif() ++ ++ string(TOLOWER "${${RNP_FEATURE}}" ${RNP_FEATURE}) ++ if (${RNP_FEATURE} STREQUAL "auto") ++ set(MESSAGE_TYPE "NOTICE") ++ set(OUTCOME "Disabling") ++ else() # User has explicitly enabled this feature ++ set(MESSAGE_TYPE "FATAL_ERROR") ++ set(OUTCOME "Aborting") ++ endif() ++ ++ foreach(feature ${BACKEND_FEATURES}) ++ backend_has_feature("${feature}" _has_${feature}) ++ if (NOT ${_has_${feature}}) ++ set(${RNP_FEATURE} Off CACHE STRING "Autodetected" FORCE) ++ message(${MESSAGE_TYPE} "${RNP_FEATURE} requires ${CRYPTO_BACKEND} feature which is missing: ${feature}. ${OUTCOME}.") ++ return() ++ endif() ++ endforeach() ++ set(${RNP_FEATURE} On CACHE STRING "Autodetected" FORCE) ++endfunction() ++ ++function(openssl_nope RNP_FEATURE REASON) ++ if (NOT ${RNP_FEATURE}) # User has explicitly disabled this feature ++ return() ++ endif() ++ ++ string(TOLOWER "${${RNP_FEATURE}}" ${RNP_FEATURE}) ++ if (${RNP_FEATURE} STREQUAL "auto") ++ set(MESSAGE_TYPE "NOTICE") ++ set(OUTCOME "Disabling") ++ else() # User has explicitly enabled this feature ++ set(MESSAGE_TYPE "FATAL_ERROR") ++ set(OUTCOME "Aborting") ++ endif() ++ ++ set(${RNP_FEATURE} Off CACHE STRING "Auto -> Off as no support" FORCE) ++ message(${MESSAGE_TYPE} "${RNP_FEATURE} doesn't work with OpenSSL backend (${REASON}). ${OUTCOME}.") ++endfunction() ++ + if(CRYPTO_BACKEND_BOTAN) + # check botan's enabled features + set(CMAKE_REQUIRED_INCLUDES "${BOTAN2_INCLUDE_DIRS}") + set(_botan_required_features + # base + BIGINT FFI HEX_CODEC PGP_S2K + # symmetric ciphers +- BLOCK_CIPHER AES BLOWFISH CAMELLIA CAST_128 DES IDEA ++ BLOCK_CIPHER AES BLOWFISH CAMELLIA CAST_128 DES + # cipher modes + MODE_CBC MODE_CFB + # RNG + AUTO_RNG AUTO_SEEDING_RNG HMAC HMAC_DRBG + # hash +@@ -87,62 +146,57 @@ if(CRYPTO_BACKEND_BOTAN) + # public-key algs + CURVE_25519 DSA ECDH ECDSA ED25519 ELGAMAL RSA + # public-key operations etc + EME_PKCS1v15 EMSA_PKCS1 EMSA_RAW KDF_BASE RFC3394_KEYWRAP SP800_56A + ) +- # SM2 algorithms suite +- if (ENABLE_SM2) +- list(APPEND _botan_required_features SM2 SM3 SM4) +- endif() +- # AEAD algorithms +- if (ENABLE_AEAD) +- list(APPEND _botan_required_features AEAD_EAX AEAD_OCB) +- endif() +- # Twofish +- if (ENABLE_TWOFISH) +- list(APPEND _botan_required_features TWOFISH) +- endif() + foreach(feature ${_botan_required_features}) + check_cxx_symbol_exists("BOTAN_HAS_${feature}" botan/build.h _botan_has_${feature}) + if (NOT _botan_has_${feature}) + message(FATAL_ERROR "A required botan feature is missing: ${feature}") + endif() + endforeach() ++ ++ resolve_feature_state(ENABLE_SM2 "SM2;SM3;SM4") ++ resolve_feature_state(ENABLE_AEAD "AEAD_EAX;AEAD_OCB") ++ resolve_feature_state(ENABLE_TWOFISH "TWOFISH") ++ resolve_feature_state(ENABLE_IDEA "IDEA") ++ # Botan supports Brainpool curves together with SECP via the ECC_GROUP define + set(CMAKE_REQUIRED_INCLUDES) + endif() + if(CRYPTO_BACKEND_OPENSSL) + # check OpenSSL features + set(_openssl_required_features + # symmetric ciphers + AES-128-ECB AES-192-ECB AES-256-ECB AES-128-CBC AES-192-CBC AES-256-CBC + BF-ECB CAMELLIA-128-ECB CAMELLIA-192-ECB CAMELLIA-256-ECB CAST5-ECB +- DES-EDE3 IDEA-ECB IDEA-CBC ++ DES-EDE3 + # hashes + MD5 SHA1 SHA224 SHA256 SHA384 SHA512 SHA3-256 SHA3-512 RIPEMD160 + # curves + PRIME256V1 SECP384R1 SECP521R1 SECP256K1 + # public key + RSAENCRYPTION DSAENCRYPTION DHKEYAGREEMENT ID-ECPUBLICKEY X25519 ED25519 + ) +- # SM2 algorithms suite +- if (ENABLE_SM2) +- list(APPEND _openssl_required_features SM2 SM3 SM4-ECB) +- endif() +- # Brainpool curves +- if (ENABLE_BRAINPOOL) +- list(APPEND _openssl_required_features BRAINPOOLP256R1 BRAINPOOLP384R1 BRAINPOOLP512R1) +- endif() + foreach(feature ${_openssl_required_features}) + message(STATUS "Looking for OpenSSL feature ${feature}") + OpenSSLHasFeature("${feature}" _openssl_has_${feature}) + if (NOT _openssl_has_${feature}) + message(FATAL_ERROR "A required OpenSSL feature is missing: ${feature}") + endif() + message(STATUS "Looking for OpenSSL feature ${feature} - found") + endforeach() ++ ++ resolve_feature_state(ENABLE_BRAINPOOL "BRAINPOOLP256R1;BRAINPOOLP384R1;BRAINPOOLP512R1") ++ resolve_feature_state(ENABLE_IDEA "IDEA-ECB;IDEA-CBC") ++ openssl_nope(ENABLE_SM2 "it's on our roadmap, see https://github.com/rnpgp/rnp/issues/1877") ++ #resolve_feature_state(ENABLE_SM2 "SM2;SM3;SM4-ECB") ++ openssl_nope(ENABLE_TWOFISH "Twofish isn't and won't be supported by OpenSSL, see https://github.com/openssl/openssl/issues/2046") ++ openssl_nope(ENABLE_AEAD "it's on our roadmap, see https://github.com/rnpgp/rnp/issues/1642") + endif() + ++configure_file(config.h.in config.h) ++ + if(CRYPTO_BACKEND_OPENSSL) + set(CRYPTO_SOURCES + crypto/bn_ossl.cpp + crypto/dsa_ossl.cpp + crypto/ec_curves.cpp +@@ -200,11 +254,11 @@ else() + message(FATAL_ERROR "Unknown crypto backend: ${CRYPTO_BACKEND}.") + endif() + list(APPEND CRYPTO_SOURCES crypto/backend_version.cpp) + + # sha11collisiondetection sources +-list(APPEND CRYPTO_SOURCES crypto/sha1cd/hash_sha1cd.cpp crypto/sha1cd/sha1.c crypto/sha1cd/ubc_check.c) ++list(APPEND CRYPTO_SOURCES crypto/hash_sha1cd.cpp crypto/sha1cd/sha1.c crypto/sha1cd/ubc_check.c) + + add_library(librnp-obj OBJECT + # librepgp + ../librepgp/stream-armor.cpp + ../librepgp/stream-common.cpp +@@ -461,10 +515,22 @@ 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) ++ ++ if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") ++ set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") ++ else() ++ set(PKGCONFIG_LIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}") ++ endif() ++ if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") ++ set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") ++ else() ++ set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") ++ endif() ++ + configure_file( + "${PROJECT_SOURCE_DIR}/cmake/librnp.pc.in" + "${PROJECT_BINARY_DIR}/librnp.pc" + @ONLY + ) +diff --git a/comm/third_party/rnp/src/lib/config.h.in b/third_party/rnp/src/lib/comm/config.h.in +--- a/comm/third_party/rnp/src/lib/config.h.in ++++ b/comm/third_party/rnp/src/lib/config.h.in +@@ -22,46 +22,47 @@ + * 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.16+git20220124.f06439f7.MZLA" +-#define PACKAGE_BUGREPORT "https://bugzilla.mozilla.org/enter_bug.cgi?product=Thunderbird" ++#define PACKAGE_STRING "rnp @RNP_VERSION_FULL@" ++#define PACKAGE_BUGREPORT "@BUGREPORT_EMAIL@" + +-#undef HAVE_BZLIB_H +-#undef HAVE_ZLIB_H ++#cmakedefine HAVE_BZLIB_H ++#cmakedefine HAVE_ZLIB_H + +-#undef HAVE_FCNTL_H +-#undef HAVE_INTTYPES_H +-#undef HAVE_LIMITS_H +-#undef HAVE_STDINT_H +-#undef HAVE_STRING_H +-#undef HAVE_SYS_CDEFS_H +-#undef HAVE_SYS_MMAN_H +-#undef HAVE_SYS_RESOURCE_H +-#undef HAVE_SYS_STAT_H +-#undef HAVE_SYS_TYPES_H +-#undef HAVE_UNISTD_H +-#undef HAVE_SYS_WAIT_H +-#undef HAVE_SYS_PARAM_H +-#undef HAVE_MKDTEMP +-#undef HAVE_MKSTEMP +-#undef HAVE_REALPATH +-#undef HAVE_O_BINARY +-#undef HAVE__O_BINARY +-#undef HAVE__TEMPNAM ++#cmakedefine HAVE_FCNTL_H ++#cmakedefine HAVE_INTTYPES_H ++#cmakedefine HAVE_LIMITS_H ++#cmakedefine HAVE_STDINT_H ++#cmakedefine HAVE_STRING_H ++#cmakedefine HAVE_SYS_CDEFS_H ++#cmakedefine HAVE_SYS_MMAN_H ++#cmakedefine HAVE_SYS_RESOURCE_H ++#cmakedefine HAVE_SYS_STAT_H ++#cmakedefine HAVE_SYS_TYPES_H ++#cmakedefine HAVE_UNISTD_H ++#cmakedefine HAVE_SYS_WAIT_H ++#cmakedefine HAVE_SYS_PARAM_H ++#cmakedefine HAVE_MKDTEMP ++#cmakedefine HAVE_MKSTEMP ++#cmakedefine HAVE_REALPATH ++#cmakedefine HAVE_O_BINARY ++#cmakedefine HAVE__O_BINARY ++#cmakedefine HAVE__TEMPNAM + +-#undef CRYPTO_BACKEND_BOTAN +-#undef CRYPTO_BACKEND_OPENSSL ++#cmakedefine CRYPTO_BACKEND_BOTAN ++#cmakedefine CRYPTO_BACKEND_OPENSSL ++#cmakedefine CRYPTO_BACKEND_OPENSSL3 + +-#undef ENABLE_SM2 +-#undef ENABLE_AEAD +-#undef ENABLE_TWOFISH +-#undef ENABLE_BRAINPOOL ++#cmakedefine ENABLE_SM2 ++#cmakedefine ENABLE_AEAD ++#cmakedefine ENABLE_TWOFISH ++#cmakedefine ENABLE_BRAINPOOL ++#cmakedefine ENABLE_IDEA + + /* 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) || defined(MSVC) || \ + (defined(__clang__) && (__clang_major__ >= 4)) + #define RNP_USE_STD_REGEX 1 + #endif +- +diff --git a/comm/third_party/rnp/src/lib/crypto.cpp b/third_party/rnp/src/lib/comm/crypto.cpp +--- a/comm/third_party/rnp/src/lib/crypto.cpp ++++ b/comm/third_party/rnp/src/lib/crypto.cpp +@@ -86,11 +86,11 @@ pgp_generate_seckey(const rnp_keygen_cry + bool primary) + { + /* populate pgp key structure */ + seckey = {}; + seckey.version = PGP_V4; +- seckey.creation_time = time(NULL); ++ seckey.creation_time = crypto.ctx->time(); + seckey.alg = crypto.key_alg; + seckey.material.alg = crypto.key_alg; + seckey.tag = primary ? PGP_PKT_SECRET_KEY : PGP_PKT_SECRET_SUBKEY; + + switch (seckey.alg) { +diff --git a/comm/third_party/rnp/src/lib/crypto.h b/third_party/rnp/src/lib/comm/crypto.h +--- a/comm/third_party/rnp/src/lib/crypto.h ++++ b/comm/third_party/rnp/src/lib/crypto.h +@@ -101,31 +101,10 @@ bool pgp_generate_subkey(rnp_keygen_subk + pgp_key_t & subkey_sec, + pgp_key_t & subkey_pub, + const pgp_password_provider_t &password_provider, + pgp_key_store_format_t secformat); + +-/** generate a new primary key and subkey +- * +- * @param primary_desc primary keygen description +- * @param subkey_desc subkey keygen description +- * @param merge_defaults true if you want defaults to be set for unset +- * keygen description parameters. +- * @param primary_sec pointer to store the generated secret key, must not be NULL +- * @param primary_pub pointer to store the generated public key, must not be NULL +- * @param subkey_sec pointer to store the generated secret key, must not be NULL +- * @param subkey_pub pointer to store the generated public key, must not be NULL +- * @return true if successful, false otherwise. +- **/ +-bool pgp_generate_keypair(rnp_keygen_primary_desc_t &primary_desc, +- rnp_keygen_subkey_desc_t & subkey_desc, +- bool merge_defaults, +- pgp_key_t & primary_sec, +- pgp_key_t & primary_pub, +- pgp_key_t & subkey_sec, +- pgp_key_t & subkey_pub, +- pgp_key_store_format_t secformat); +- + /** + * @brief Check two key material for equality. Only public part is checked, so this can be + * called on public/secret key material + * + * @param key1 first key material +diff --git a/comm/third_party/rnp/src/lib/crypto/backend_version.cpp b/third_party/rnp/src/lib/crypto/b/commackend_version.cpp +--- a/comm/third_party/rnp/src/lib/crypto/backend_version.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/backend_version.cpp +@@ -29,10 +29,14 @@ + #if defined(CRYPTO_BACKEND_BOTAN) + #include + #elif defined(CRYPTO_BACKEND_OPENSSL) + #include + #include ++#include "ossl_common.h" ++#if defined(CRYPTO_BACKEND_OPENSSL3) ++#include ++#endif + #include + #include "config.h" + #ifndef RNP_USE_STD_REGEX + #include + #else +@@ -100,6 +104,69 @@ backend_version() + #else + #error "Unknown backend" + #endif + } + ++#if defined(CRYPTO_BACKEND_OPENSSL3) ++typedef struct openssl3_state { ++ OSSL_PROVIDER *legacy; ++ OSSL_PROVIDER *def; ++} openssl3_state; ++ ++bool ++backend_init(void **param) ++{ ++ if (!param) { ++ return false; ++ } ++ ++ *param = NULL; ++ openssl3_state *state = (openssl3_state *) calloc(1, sizeof(openssl3_state)); ++ if (!state) { ++ RNP_LOG("Allocation failure."); ++ return false; ++ } ++ /* Load default crypto provider */ ++ state->def = OSSL_PROVIDER_load(NULL, "default"); ++ if (!state->def) { ++ RNP_LOG("Failed to load default crypto provider: %s", ossl_latest_err()); ++ free(state); ++ return false; ++ } ++ /* Load legacy crypto provider */ ++ state->legacy = OSSL_PROVIDER_load(NULL, "legacy"); ++ if (!state->legacy) { ++ RNP_LOG("Failed to load legacy crypto provider: %s", ossl_latest_err()); ++ OSSL_PROVIDER_unload(state->def); ++ free(state); ++ return false; ++ } ++ *param = state; ++ return true; ++} ++ ++void ++backend_finish(void *param) ++{ ++ if (!param) { ++ return; ++ } ++ openssl3_state *state = (openssl3_state *) param; ++ OSSL_PROVIDER_unload(state->def); ++ OSSL_PROVIDER_unload(state->legacy); ++ free(state); ++} ++#else ++bool ++backend_init(void **param) ++{ ++ return true; ++} ++ ++void ++backend_finish(void *param) ++{ ++ // Do nothing ++} ++#endif ++ + } // namespace rnp +diff --git a/comm/third_party/rnp/src/lib/crypto/backend_version.h b/third_party/rnp/src/lib/crypto/b/commackend_version.h +--- a/comm/third_party/rnp/src/lib/crypto/backend_version.h ++++ b/comm/third_party/rnp/src/lib/crypto/backend_version.h +@@ -33,8 +33,12 @@ namespace rnp { + + const char *backend_string(); + + const char *backend_version(); + ++bool backend_init(void **param); ++ ++void backend_finish(void *param); ++ + } // namespace rnp + + #endif // CRYPTO_BACKEND_VERSION_H_ +diff --git a/comm/third_party/rnp/src/lib/crypto/cipher_botan.cpp b/third_party/rnp/src/lib/crypto/cipher_b/commotan.cpp +--- a/comm/third_party/rnp/src/lib/crypto/cipher_botan.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/cipher_botan.cpp +@@ -44,10 +44,16 @@ static const id_str_pair cipher_map[] = + }; + + Cipher_Botan * + Cipher_Botan::create(pgp_symm_alg_t alg, const std::string &name, bool encrypt) + { ++#if !defined(ENABLE_IDEA) ++ if (alg == PGP_SA_IDEA) { ++ RNP_LOG("IDEA support has been disabled"); ++ return nullptr; ++ } ++#endif + auto cipher = Botan::Cipher_Mode::create( + name, encrypt ? Botan::Cipher_Dir::ENCRYPTION : Botan::Cipher_Dir::DECRYPTION); + if (!cipher) { + RNP_LOG("Failed to create cipher '%s'", name.c_str()); + return nullptr; +diff --git a/comm/third_party/rnp/src/lib/crypto/cipher_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/cipher_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/cipher_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/cipher_ossl.cpp +@@ -41,15 +41,22 @@ static const id_str_pair cipher_map[] = + {PGP_SA_IDEA, "IDEA"}, + {0, NULL}, + }; + + EVP_CIPHER_CTX * +-Cipher_OpenSSL::create(const std::string &name, ++Cipher_OpenSSL::create(pgp_symm_alg_t alg, ++ const std::string &name, + bool encrypt, + size_t tag_size, + bool disable_padding) + { ++#if !defined(ENABLE_IDEA) ++ if (alg == PGP_SA_IDEA) { ++ RNP_LOG("IDEA support has been disabled"); ++ return nullptr; ++ } ++#endif + const EVP_CIPHER *cipher = EVP_get_cipherbyname(name.c_str()); + if (!cipher) { + RNP_LOG("Unsupported cipher: %s", name.c_str()); + return nullptr; + } +@@ -92,28 +99,32 @@ std::unique_ptr + Cipher_OpenSSL::encryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) + { +- return std::unique_ptr(new (std::nothrow) Cipher_OpenSSL( +- cipher, +- create(make_name(cipher, mode), true, tag_size, disable_padding), +- tag_size, +- true)); ++ EVP_CIPHER_CTX *ossl_ctx = ++ create(cipher, make_name(cipher, mode), true, tag_size, disable_padding); ++ if (!ossl_ctx) { ++ return NULL; ++ } ++ return std::unique_ptr(new (std::nothrow) ++ Cipher_OpenSSL(cipher, ossl_ctx, tag_size, true)); + } + + std::unique_ptr + Cipher_OpenSSL::decryption(pgp_symm_alg_t cipher, + pgp_cipher_mode_t mode, + size_t tag_size, + bool disable_padding) + { +- return std::unique_ptr(new (std::nothrow) Cipher_OpenSSL( +- cipher, +- create(make_name(cipher, mode), false, tag_size, disable_padding), +- tag_size, +- false)); ++ EVP_CIPHER_CTX *ossl_ctx = ++ create(cipher, make_name(cipher, mode), false, tag_size, disable_padding); ++ if (!ossl_ctx) { ++ return NULL; ++ } ++ return std::unique_ptr( ++ new (std::nothrow) Cipher_OpenSSL(cipher, ossl_ctx, tag_size, false)); + } + + bool + Cipher_OpenSSL::set_key(const uint8_t *key, size_t key_length) + { +diff --git a/comm/third_party/rnp/src/lib/crypto/cipher_ossl.hpp b/third_party/rnp/src/lib/comm/crypto/cipher_ossl.hpp +--- a/comm/third_party/rnp/src/lib/crypto/cipher_ossl.hpp ++++ b/comm/third_party/rnp/src/lib/crypto/cipher_ossl.hpp +@@ -68,11 +68,12 @@ class Cipher_OpenSSL : public Cipher { + EVP_CIPHER_CTX *m_ctx; + size_t m_block_size; + size_t m_tag_size; + bool m_encrypt; + +- static EVP_CIPHER_CTX *create(const std::string &name, ++ static EVP_CIPHER_CTX *create(pgp_symm_alg_t alg, ++ const std::string &name, + bool encrypt, + size_t tag_size, + bool disable_padding); + }; + +diff --git a/comm/third_party/rnp/src/lib/crypto/common.h b/third_party/rnp/src/lib/comm/crypto/common.h +--- a/comm/third_party/rnp/src/lib/crypto/common.h ++++ b/comm/third_party/rnp/src/lib/crypto/common.h +@@ -40,11 +40,11 @@ + #include "sm2.h" + #include "eddsa.h" + /* symmetric crypto */ + #include "symmetric.h" + /* hash */ +-#include "hash.h" ++#include "hash.hpp" + /* s2k */ + #include "s2k.h" + /* backend name and version */ + #include "backend_version.h" + +diff --git a/comm/third_party/rnp/src/lib/crypto/dl_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/dl_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/dl_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/dl_ossl.cpp +@@ -99,11 +99,11 @@ done: + } + + static rnp_result_t + dl_validate_secret_key(EVP_PKEY *dlkey, const pgp_mpi_t &mx) + { +- DH *dh = EVP_PKEY_get0_DH(dlkey); ++ const DH *dh = EVP_PKEY_get0_DH(dlkey); + assert(dh); + const bignum_t *p = DH_get0_p(dh); + const bignum_t *q = DH_get0_q(dh); + const bignum_t *g = DH_get0_g(dh); + const bignum_t *y = DH_get0_pub_key(dh); +@@ -173,12 +173,11 @@ dl_validate_key(EVP_PKEY *pkey, const pg + } + if (res < 1) { + /* ElGamal specification doesn't seem to restrict P to the safe prime */ + auto err = ERR_peek_last_error(); + DHerr(DH_F_DH_CHECK_EX, DH_R_CHECK_P_NOT_SAFE_PRIME); +- if ((ERR_GET_FUNC(err) == DH_F_DH_CHECK_EX) && +- (ERR_GET_REASON(err) == DH_R_CHECK_P_NOT_SAFE_PRIME)) { ++ if ((ERR_GET_REASON(err) == DH_R_CHECK_P_NOT_SAFE_PRIME)) { + RNP_LOG("Warning! P is not a safe prime."); + } else { + goto done; + } + } +diff --git a/comm/third_party/rnp/src/lib/crypto/dsa.cpp b/third_party/rnp/src/lib/comm/crypto/dsa.cpp +--- a/comm/third_party/rnp/src/lib/crypto/dsa.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/dsa.cpp +@@ -78,11 +78,10 @@ + #include + #include + #include + #include + #include "dsa.h" +-#include "hash.h" + #include "bn.h" + #include "utils.h" + + #define DSA_MAX_Q_BITLEN 256 + +diff --git a/comm/third_party/rnp/src/lib/crypto/dsa_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/dsa_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/dsa_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/dsa_ossl.cpp +@@ -28,11 +28,10 @@ + #include + #include + #include "bn.h" + #include "dsa.h" + #include "dl_ossl.h" +-#include "hash.h" + #include "utils.h" + #include + #include + #include + #include +@@ -245,11 +244,11 @@ dsa_generate(rnp::RNG *rng, pgp_dsa_key_ + if ((keylen < 1024) || (keylen > 3072) || (qbits < 160) || (qbits > 256)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; +- DSA * dsa = NULL; ++ const DSA * dsa = NULL; + EVP_PKEY * pkey = NULL; + EVP_PKEY * parmkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + + /* Generate DSA params */ +diff --git a/comm/third_party/rnp/src/lib/crypto/ec_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/ec_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/ec_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/ec_ossl.cpp +@@ -123,11 +123,11 @@ ec_generate(rnp::RNG * rng, + ret = RNP_SUCCESS; + } + EVP_PKEY_free(pkey); + return ret; + } +- EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); ++ const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); + if (!ec) { + RNP_LOG("Failed to retrieve EC key: %lu", ERR_peek_last_error()); + goto done; + } + if (!ec_write_pubkey(pkey, key->p, curve)) { +@@ -327,11 +327,11 @@ ec_write_pubkey(EVP_PKEY *pkey, pgp_mpi_ + assert(mpi.len == 32); + mpi.mpi[0] = 0x40; + mpi.len++; + return true; + } +- EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); ++ const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); + if (!ec) { + RNP_LOG("Failed to retrieve EC key: %lu", ERR_peek_last_error()); + return false; + } + const EC_POINT *p = EC_KEY_get0_public_key(ec); +diff --git a/comm/third_party/rnp/src/lib/crypto/ecdh.cpp b/third_party/rnp/src/lib/comm/crypto/ecdh.cpp +--- a/comm/third_party/rnp/src/lib/crypto/ecdh.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/ecdh.cpp +@@ -1,7 +1,7 @@ + /*- +- * Copyright (c) 2017 Ribose Inc. ++ * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: +@@ -24,13 +24,13 @@ + * POSSIBILITY OF SUCH DAMAGE. + */ + + #include + #include ++#include "hash_botan.hpp" + #include "ecdh.h" + #include "ecdh_utils.h" +-#include "hash.h" + #include "symmetric.h" + #include "types.h" + #include "utils.h" + #include "mem.h" + #include "bn.h" +@@ -67,11 +67,12 @@ compute_kek(uint8_t * kek, + if (botan_pk_op_key_agreement_create(&op_key_agreement, ec_prvkey, "Raw", 0) || + botan_pk_op_key_agreement(op_key_agreement, s.data(), &s_len, p, p_len, NULL, 0)) { + goto end; + } + +- snprintf(kdf_name, sizeof(kdf_name), "SP800-56A(%s)", rnp::Hash::name_backend(hash_alg)); ++ snprintf( ++ kdf_name, sizeof(kdf_name), "SP800-56A(%s)", rnp::Hash_Botan::name_backend(hash_alg)); + ret = !botan_kdf( + kdf_name, kek, kek_len, s.data(), s_len, NULL, 0, other_info, other_info_size); + end: + return ret && !botan_pk_op_key_agreement_destroy(op_key_agreement); + } +diff --git a/comm/third_party/rnp/src/lib/crypto/ecdh_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/ecdh_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/ecdh_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/ecdh_ossl.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2021-2022, [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: + * +@@ -27,11 +27,11 @@ + #include + #include + #include "ecdh.h" + #include "ecdh_utils.h" + #include "ec_ossl.h" +-#include "hash.h" ++#include "hash.hpp" + #include "symmetric.h" + #include "types.h" + #include "utils.h" + #include "logging.h" + #include "mem.h" +@@ -87,15 +87,15 @@ ecdh_derive_kek(uint8_t * + return RNP_ERROR_NOT_SUPPORTED; + } + size_t have = 0; + try { + for (size_t i = 1; i <= reps; i++) { +- rnp::Hash hash(key.kdf_hash_alg); +- hash.add(i); +- hash.add(x, xlen); +- hash.add(other_info, other_len); +- hash.finish(dgst.data()); ++ auto hash = rnp::Hash::create(key.kdf_hash_alg); ++ hash->add(i); ++ hash->add(x, xlen); ++ hash->add(other_info, other_len); ++ hash->finish(dgst.data()); + size_t bytes = std::min(hash_len, kek_len - have); + memcpy(kek + have, dgst.data(), bytes); + have += bytes; + } + return RNP_SUCCESS; +diff --git a/comm/third_party/rnp/src/lib/crypto/ecdh_utils.cpp b/third_party/rnp/src/lib/comm/crypto/ecdh_utils.cpp +--- a/comm/third_party/rnp/src/lib/crypto/ecdh_utils.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/ecdh_utils.cpp +@@ -37,18 +37,18 @@ + static const struct ecdh_params_t { + pgp_curve_t curve; /* Curve ID */ + pgp_hash_alg_t hash; /* Hash used by kdf */ + pgp_symm_alg_t wrap_alg; /* Symmetric algorithm used to wrap KEK*/ + } ecdh_params[] = { +- {.curve = PGP_CURVE_NIST_P_256, .hash = PGP_HASH_SHA256, .wrap_alg = PGP_SA_AES_128}, +- {.curve = PGP_CURVE_NIST_P_384, .hash = PGP_HASH_SHA384, .wrap_alg = PGP_SA_AES_192}, +- {.curve = PGP_CURVE_NIST_P_521, .hash = PGP_HASH_SHA512, .wrap_alg = PGP_SA_AES_256}, +- {.curve = PGP_CURVE_BP256, .hash = PGP_HASH_SHA256, .wrap_alg = PGP_SA_AES_128}, +- {.curve = PGP_CURVE_BP384, .hash = PGP_HASH_SHA384, .wrap_alg = PGP_SA_AES_192}, +- {.curve = PGP_CURVE_BP512, .hash = PGP_HASH_SHA512, .wrap_alg = PGP_SA_AES_256}, +- {.curve = PGP_CURVE_25519, .hash = PGP_HASH_SHA256, .wrap_alg = PGP_SA_AES_128}, +- {.curve = PGP_CURVE_P256K1, .hash = PGP_HASH_SHA256, .wrap_alg = PGP_SA_AES_128}, ++ {PGP_CURVE_NIST_P_256, PGP_HASH_SHA256, PGP_SA_AES_128}, ++ {PGP_CURVE_NIST_P_384, PGP_HASH_SHA384, PGP_SA_AES_192}, ++ {PGP_CURVE_NIST_P_521, PGP_HASH_SHA512, PGP_SA_AES_256}, ++ {PGP_CURVE_BP256, PGP_HASH_SHA256, PGP_SA_AES_128}, ++ {PGP_CURVE_BP384, PGP_HASH_SHA384, PGP_SA_AES_192}, ++ {PGP_CURVE_BP512, PGP_HASH_SHA512, PGP_SA_AES_256}, ++ {PGP_CURVE_25519, PGP_HASH_SHA256, PGP_SA_AES_128}, ++ {PGP_CURVE_P256K1, PGP_HASH_SHA256, PGP_SA_AES_128}, + }; + + // "Anonymous Sender " in hex + static const unsigned char ANONYMOUS_SENDER[] = {0x41, 0x6E, 0x6F, 0x6E, 0x79, 0x6D, 0x6F, + 0x75, 0x73, 0x20, 0x53, 0x65, 0x6E, 0x64, +diff --git a/comm/third_party/rnp/src/lib/crypto/elgamal_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/elgamal_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/elgamal_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/elgamal_ossl.cpp +@@ -333,11 +333,11 @@ elgamal_generate(rnp::RNG *rng, pgp_eg_k + if ((keybits < 1024) || (keybits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp_result_t ret = RNP_ERROR_GENERIC; +- DH * dh = NULL; ++ const DH * dh = NULL; + EVP_PKEY * pkey = NULL; + EVP_PKEY * parmkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + + /* Generate DH params, which usable for ElGamal as well */ +diff --git a/comm/third_party/rnp/src/lib/crypto/hash.cpp b/third_party/rnp/src/lib/comm/crypto/hash.cpp +--- a/comm/third_party/rnp/src/lib/crypto/hash.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/hash.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2021 Ribose Inc. ++ * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: +@@ -22,179 +22,139 @@ + * 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 +-#include +-#include +-#include "hash.h" +-#include "types.h" +-#include "utils.h" +-#include "str-utils.h" +-#include "defaults.h" +-#include "sha1cd/hash_sha1cd.h" ++#include "hash_botan.hpp" ++#include "logging.h" ++#include + + static const id_str_pair botan_alg_map[] = { + {PGP_HASH_MD5, "MD5"}, + {PGP_HASH_SHA1, "SHA-1"}, + {PGP_HASH_RIPEMD, "RIPEMD-160"}, + {PGP_HASH_SHA256, "SHA-256"}, + {PGP_HASH_SHA384, "SHA-384"}, + {PGP_HASH_SHA512, "SHA-512"}, + {PGP_HASH_SHA224, "SHA-224"}, ++#if defined(ENABLE_SM2) + {PGP_HASH_SM3, "SM3"}, ++#endif + {PGP_HASH_SHA3_256, "SHA-3(256)"}, + {PGP_HASH_SHA3_512, "SHA-3(512)"}, + {0, NULL}, + }; + + namespace rnp { + +-Hash::Hash(pgp_hash_alg_t alg) ++Hash_Botan::Hash_Botan(pgp_hash_alg_t alg) : Hash(alg) + { +- if (alg == PGP_HASH_SHA1) { +- /* todo: avoid duplication here and in the OpenSSL backend */ +- handle_ = hash_sha1cd_create(); +- if (!handle_) { +- throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); +- } +- alg_ = alg; +- size_ = rnp::Hash::size(alg); +- return; +- } +- +- const char *name = Hash::name_backend(alg); ++ auto name = Hash_Botan::name_backend(alg); + if (!name) { + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + +- auto hash_fn = Botan::HashFunction::create(name); +- if (!hash_fn) { ++ fn_ = Botan::HashFunction::create(name); ++ if (!fn_) { + RNP_LOG("Error creating hash object for '%s'", name); + throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + +- size_ = hash_fn->output_length(); +- if (!size_) { +- RNP_LOG("output_length() call failed"); +- throw rnp_exception(RNP_ERROR_BAD_STATE); +- } +- handle_ = hash_fn.release(); +- alg_ = alg; ++ assert(size_ == fn_->output_length()); + } + +-void +-Hash::add(const void *buf, size_t len) ++Hash_Botan::Hash_Botan(const Hash_Botan &src) : Hash(src.alg_) + { +- if (!handle_) { +- throw rnp_exception(RNP_ERROR_NULL_POINTER); ++ if (!src.fn_) { ++ throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } +- if (alg_ == PGP_HASH_SHA1) { +- hash_sha1cd_add(handle_, buf, len); +- return; +- } +- static_cast(handle_)->update(static_cast(buf), +- len); ++ fn_ = src.fn_->copy_state(); + } + +-size_t +-Hash::finish(uint8_t *digest) ++Hash_Botan::~Hash_Botan() + { +- if (!handle_) { +- return 0; +- } +- if (alg_ == PGP_HASH_SHA1) { +- int res = hash_sha1cd_finish(handle_, digest); +- handle_ = NULL; +- size_ = 0; +- if (res) { +- throw rnp_exception(RNP_ERROR_BAD_STATE); +- } +- return 20; +- } ++} + +- auto hash_fn = +- std::unique_ptr(static_cast(handle_)); +- if (!hash_fn) { +- RNP_LOG("Hash finalization failed"); +- throw rnp_exception(RNP_ERROR_BAD_STATE); +- } ++std::unique_ptr ++Hash_Botan::create(pgp_hash_alg_t alg) ++{ ++ return std::unique_ptr(new Hash_Botan(alg)); ++} + +- size_t outlen = size_; +- handle_ = NULL; +- size_ = 0; +- +- if (digest) { +- hash_fn->final(digest); +- } +- return outlen; ++std::unique_ptr ++Hash_Botan::clone() const ++{ ++ return std::unique_ptr(new Hash_Botan(*this)); + } + + void +-Hash::clone(Hash &dst) const ++Hash_Botan::add(const void *buf, size_t len) + { +- if (!handle_) { +- throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); +- } +- +- if (dst.handle_) { +- dst.finish(); ++ if (!fn_) { ++ throw rnp_exception(RNP_ERROR_NULL_POINTER); + } +- +- if (alg_ == PGP_HASH_SHA1) { +- dst.handle_ = hash_sha1cd_clone(handle_); +- if (!dst.handle_) { +- throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); +- } +- dst.size_ = size_; +- dst.alg_ = alg_; +- return; +- } +- +- auto hash_fn = static_cast(handle_); +- if (!hash_fn) { +- throw rnp_exception(RNP_ERROR_BAD_STATE); +- } +- +- auto copy = hash_fn->copy_state(); +- if (!copy) { +- RNP_LOG("Failed to clone hash."); +- throw rnp_exception(RNP_ERROR_BAD_STATE); +- } +- +- dst.size_ = size_; +- dst.alg_ = alg_; +- dst.handle_ = copy.release(); ++ fn_->update(static_cast(buf), len); + } + +-Hash::~Hash() ++size_t ++Hash_Botan::finish(uint8_t *digest) + { +- if (!handle_) { +- return; +- } +- if (alg_ == PGP_HASH_SHA1) { +- hash_sha1cd_finish(handle_, NULL); +- } else { +- delete static_cast(handle_); ++ if (!fn_) { ++ return 0; + } +-} +- +-CRC24::CRC24() +-{ +- auto hash_fn = Botan::HashFunction::create("CRC24"); +- if (!hash_fn) { +- RNP_LOG("Error creating hash object for 'CRC24'"); +- throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); ++ size_t outlen = size_; ++ if (digest) { ++ fn_->final(digest); + } +- +- size_ = 3; +- alg_ = PGP_HASH_UNKNOWN; +- handle_ = hash_fn.release(); ++ fn_ = nullptr; ++ size_ = 0; ++ return outlen; + } + + const char * +-Hash::name_backend(pgp_hash_alg_t alg) ++Hash_Botan::name_backend(pgp_hash_alg_t alg) + { + return id_str_pair::lookup(botan_alg_map, alg); + } ++ ++CRC24_Botan::CRC24_Botan() ++{ ++ fn_ = Botan::HashFunction::create("CRC24"); ++ if (!fn_) { ++ RNP_LOG("Error creating CRC24 object"); ++ throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); ++ } ++ assert(3 == fn_->output_length()); ++} ++ ++CRC24_Botan::~CRC24_Botan() ++{ ++} ++ ++std::unique_ptr ++CRC24_Botan::create() ++{ ++ return std::unique_ptr(new CRC24_Botan()); ++} ++ ++void ++CRC24_Botan::add(const void *buf, size_t len) ++{ ++ if (!fn_) { ++ throw rnp_exception(RNP_ERROR_NULL_POINTER); ++ } ++ fn_->update(static_cast(buf), len); ++} ++ ++std::array ++CRC24_Botan::finish() ++{ ++ if (!fn_) { ++ throw rnp_exception(RNP_ERROR_NULL_POINTER); ++ } ++ std::array crc{}; ++ fn_->final(crc.data()); ++ fn_ = nullptr; ++ return crc; ++} ++ + } // namespace rnp +diff --git a/comm/third_party/rnp/src/lib/crypto/hash.h b/third_party/rnp/src/lib/comm/crypto/hash.h +deleted file mode 100644 +--- a/comm/third_party/rnp/src/lib/crypto/hash.h ++++ /dev/null +@@ -1,106 +0,0 @@ +-/* +- * Copyright (c) 2017-2021 Ribose Inc. +- * All rights reserved. +- * +- * Redistribution and use in source and binary forms, with or without +- * modification, are permitted provided that the following conditions +- * are met: +- * 1. Redistributions of source code must retain the above copyright +- * notice, this list of conditions and the following disclaimer. +- * 2. Redistributions in binary form must reproduce the above copyright +- * notice, this list of conditions and the following disclaimer in the +- * documentation and/or other materials provided with the distribution. +- * +- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +- * POSSIBILITY OF SUCH DAMAGE. +- */ +- +-#ifndef CRYPTO_HASH_H_ +-#define CRYPTO_HASH_H_ +- +-#include +-#include "types.h" +-#include "config.h" +- +-/** +- * Output size (in bytes) of biggest supported hash algo +- */ +-#define PGP_MAX_HASH_SIZE (64) +- +-namespace rnp { +-class Hash { +- protected: +- void * handle_; +- size_t size_; +- pgp_hash_alg_t alg_; +- +- public: +- pgp_hash_alg_t alg() const; +- size_t size() const; +- +- Hash() : handle_(NULL), size_(0), alg_(PGP_HASH_UNKNOWN){}; +- Hash(pgp_hash_alg_t alg); +- Hash(Hash &&src); +- +- virtual void add(const void *buf, size_t len); +- virtual void add(uint32_t val); +- virtual void add(const pgp_mpi_t &mpi); +- virtual size_t finish(uint8_t *digest = NULL); +- virtual void clone(Hash &dst) const; +- +- Hash &operator=(const Hash &src); +- Hash &operator=(Hash &&src); +- +- virtual ~Hash(); +- +- /* Hash algorithm by string representation from cleartext-signed text */ +- static pgp_hash_alg_t alg(const char *name); +- /* Hash algorithm representation for cleartext-signed text */ +- static const char *name(pgp_hash_alg_t alg); +- /* Hash algorithm representation for the backend functions */ +- static const char *name_backend(pgp_hash_alg_t alg); +- /* Size of the hash algorithm output or 0 if algorithm is unknown */ +- static size_t size(pgp_hash_alg_t alg); +-}; +- +-#if defined(CRYPTO_BACKEND_BOTAN) +-class CRC24 : public Hash { +- public: +- CRC24(); +-}; +-#endif +-#if defined(CRYPTO_BACKEND_OPENSSL) +-class CRC24 { +- uint32_t state_; +- +- public: +- CRC24(); +- +- void add(const void *buf, size_t len); +- size_t finish(uint8_t *crc); +-}; +-#endif +- +-class HashList { +- std::vector hashes_; +- +- public: +- void add_alg(pgp_hash_alg_t alg); +- const Hash * get(pgp_hash_alg_t alg) const; +- void add(const void *buf, size_t len); +- bool empty() const; +- std::vector &hashes(); +-}; +- +-} // namespace rnp +- +-#endif +diff --git a/comm/third_party/rnp/src/lib/crypto/hash.hpp b/third_party/rnp/src/lib/comm/crypto/hash.hpp +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/lib/crypto/hash.hpp +@@ -0,0 +1,98 @@ ++/* ++ * Copyright (c) 2017-2022 Ribose Inc. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS ++ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef CRYPTO_HASH_H_ ++#define CRYPTO_HASH_H_ ++ ++#include ++#include "types.h" ++#include "config.h" ++#include ++#include ++#include ++ ++/** ++ * Output size (in bytes) of biggest supported hash algo ++ */ ++#define PGP_MAX_HASH_SIZE (64) ++ ++namespace rnp { ++class Hash { ++ protected: ++ pgp_hash_alg_t alg_; ++ size_t size_; ++ Hash(pgp_hash_alg_t alg) : alg_(alg) ++ { ++ size_ = Hash::size(alg); ++ }; ++ ++ public: ++ pgp_hash_alg_t alg() const; ++ size_t size() const; ++ ++ static std::unique_ptr create(pgp_hash_alg_t alg); ++ virtual std::unique_ptr clone() const = 0; ++ ++ virtual void add(const void *buf, size_t len) = 0; ++ virtual void add(uint32_t val); ++ virtual void add(const pgp_mpi_t &mpi); ++ virtual size_t finish(uint8_t *digest = NULL) = 0; ++ ++ virtual ~Hash(); ++ ++ /* Hash algorithm by string representation from cleartext-signed text */ ++ static pgp_hash_alg_t alg(const char *name); ++ /* Hash algorithm representation for cleartext-signed text */ ++ static const char *name(pgp_hash_alg_t alg); ++ /* Size of the hash algorithm output or 0 if algorithm is unknown */ ++ static size_t size(pgp_hash_alg_t alg); ++}; ++ ++class CRC24 { ++ protected: ++ CRC24(){}; ++ ++ public: ++ static std::unique_ptr create(); ++ ++ virtual void add(const void *buf, size_t len) = 0; ++ virtual std::array finish() = 0; ++ ++ virtual ~CRC24(){}; ++}; ++ ++class HashList { ++ public: ++ std::vector> hashes; ++ ++ void add_alg(pgp_hash_alg_t alg); ++ const Hash *get(pgp_hash_alg_t alg) const; ++ void add(const void *buf, size_t len); ++}; ++ ++} // namespace rnp ++ ++#endif +diff --git a/comm/third_party/rnp/src/lib/crypto/hash_botan.hpp b/third_party/rnp/src/lib/crypto/hash_b/commotan.hpp +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/lib/crypto/hash_botan.hpp +@@ -0,0 +1,68 @@ ++/* ++ * Copyright (c) 2022 Ribose Inc. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS ++ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef CRYPTO_HASH_BOTAN_HPP_ ++#define CRYPTO_HASH_BOTAN_HPP_ ++ ++#include "hash.hpp" ++#include ++ ++namespace rnp { ++class Hash_Botan : public Hash { ++ private: ++ std::unique_ptr fn_; ++ ++ Hash_Botan(pgp_hash_alg_t alg); ++ Hash_Botan(const Hash_Botan &src); ++ ++ public: ++ virtual ~Hash_Botan(); ++ ++ static std::unique_ptr create(pgp_hash_alg_t alg); ++ std::unique_ptr clone() const override; ++ ++ void add(const void *buf, size_t len) override; ++ size_t finish(uint8_t *digest = NULL) override; ++ ++ static const char *name_backend(pgp_hash_alg_t alg); ++}; ++ ++class CRC24_Botan : public CRC24 { ++ std::unique_ptr fn_; ++ CRC24_Botan(); ++ ++ public: ++ virtual ~CRC24_Botan(); ++ ++ static std::unique_ptr create(); ++ ++ void add(const void *buf, size_t len) override; ++ std::array finish() override; ++}; ++ ++} // namespace rnp ++ ++#endif +\ No newline at end of file +diff --git a/comm/third_party/rnp/src/lib/crypto/hash_common.cpp b/third_party/rnp/src/lib/comm/crypto/hash_common.cpp +--- a/comm/third_party/rnp/src/lib/crypto/hash_common.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/hash_common.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2021 Ribose Inc. ++ * Copyright (c) 2021-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: +@@ -22,14 +22,23 @@ + * 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 "hash.h" ++#include "config.h" ++#include "hash.hpp" + #include "types.h" + #include "utils.h" + #include "str-utils.h" ++#include "hash_sha1cd.hpp" ++#if defined(CRYPTO_BACKEND_BOTAN) ++#include "hash_botan.hpp" ++#endif ++#if defined(CRYPTO_BACKEND_OPENSSL) ++#include "hash_ossl.hpp" ++#include "hash_crc24.hpp" ++#endif + + static const struct hash_alg_map_t { + pgp_hash_alg_t type; + const char * name; + size_t len; +@@ -53,11 +62,44 @@ Hash::alg() const + } + + size_t + Hash::size() const + { +- return size_; ++ return Hash::size(alg_); ++} ++ ++std::unique_ptr ++Hash::create(pgp_hash_alg_t alg) ++{ ++ if (alg == PGP_HASH_SHA1) { ++ return Hash_SHA1CD::create(); ++ } ++#if !defined(ENABLE_SM2) ++ if (alg == PGP_HASH_SM3) { ++ RNP_LOG("SM3 hash is not available."); ++ throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); ++ } ++#endif ++#if defined(CRYPTO_BACKEND_OPENSSL) ++ return Hash_OpenSSL::create(alg); ++#elif defined(CRYPTO_BACKEND_BOTAN) ++ return Hash_Botan::create(alg); ++#else ++#error "Crypto backend not specified" ++#endif ++} ++ ++std::unique_ptr ++CRC24::create() ++{ ++#if defined(CRYPTO_BACKEND_OPENSSL) ++ return CRC24_RNP::create(); ++#elif defined(CRYPTO_BACKEND_BOTAN) ++ return CRC24_Botan::create(); ++#else ++#error "Crypto backend not specified" ++#endif + } + + void + Hash::add(uint32_t val) + { +@@ -86,10 +128,14 @@ Hash::add(const pgp_mpi_t &val) + add(&padbyte, 1); + } + add(val.mpi + idx, len - idx); + } + ++Hash::~Hash() ++{ ++} ++ + pgp_hash_alg_t + Hash::alg(const char *name) + { + if (!name) { + return PGP_HASH_UNKNOWN; +@@ -116,77 +162,33 @@ Hash::size(pgp_hash_alg_t alg) + size_t val = 0; + ARRAY_LOOKUP_BY_ID(hash_alg_map, type, len, alg, val); + return val; + } + +-Hash::Hash(Hash &&src) +-{ +- handle_ = src.handle_; +- src.handle_ = NULL; +- alg_ = src.alg_; +- src.alg_ = PGP_HASH_UNKNOWN; +- size_ = src.size_; +- src.size_ = 0; +-} +- +-Hash & +-Hash::operator=(const Hash &src) +-{ +- src.clone(*this); +- return *this; +-} +- +-Hash & +-Hash::operator=(Hash &&src) +-{ +- if (handle_) { +- finish(); +- } +- handle_ = src.handle_; +- src.handle_ = NULL; +- alg_ = src.alg_; +- src.alg_ = PGP_HASH_UNKNOWN; +- size_ = src.size_; +- src.size_ = 0; +- return *this; +-} +- + void + HashList::add_alg(pgp_hash_alg_t alg) + { + if (!get(alg)) { +- hashes_.emplace_back(alg); ++ hashes.emplace_back(rnp::Hash::create(alg)); + } + } + + const Hash * + HashList::get(pgp_hash_alg_t alg) const + { +- for (auto &hash : hashes_) { +- if (hash.alg() == alg) { +- return &hash; ++ for (auto &hash : hashes) { ++ if (hash->alg() == alg) { ++ return hash.get(); + } + } + return NULL; + } + + void + HashList::add(const void *buf, size_t len) + { +- for (auto &hash : hashes_) { +- hash.add(buf, len); ++ for (auto &hash : hashes) { ++ hash->add(buf, len); + } + } + +-bool +-HashList::empty() const +-{ +- return hashes_.empty(); +-} +- +-std::vector & +-HashList::hashes() +-{ +- return hashes_; +-} +- + } // namespace rnp +diff --git a/comm/third_party/rnp/src/lib/crypto/hash_crc24.cpp b/third_party/rnp/src/lib/comm/crypto/hash_crc24.cpp +--- a/comm/third_party/rnp/src/lib/crypto/hash_crc24.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/hash_crc24.cpp +@@ -25,11 +25,11 @@ + */ + + #include + #include + #include "utils.h" +-#include "hash.h" ++#include "hash_crc24.hpp" + + static const uint32_t T0[256] = { + 0x00000000, 0x00FB4C86, 0x000DD58A, 0x00F6990C, 0x00E1E693, 0x001AAA15, 0x00EC3319, + 0x00177F9F, 0x003981A1, 0x00C2CD27, 0x0034542B, 0x00CF18AD, 0x00D86732, 0x00232BB4, + 0x00D5B2B8, 0x002EFE3E, 0x00894EC5, 0x00720243, 0x00849B4F, 0x007FD7C9, 0x0068A856, +@@ -250,30 +250,39 @@ crc24_final(uint32_t crc) + return (BSWAP32(crc) >> 8); + } + + namespace rnp { + +-CRC24::CRC24() ++CRC24_RNP::CRC24_RNP() + { + state_ = CRC24_FAST_INIT; + } + ++CRC24_RNP::~CRC24_RNP() ++{ ++} ++ ++std::unique_ptr ++CRC24_RNP::create() ++{ ++ return std::unique_ptr(new CRC24_RNP()); ++} ++ + void +-CRC24::add(const void *buf, size_t len) ++CRC24_RNP::add(const void *buf, size_t len) + { + state_ = crc24_update(state_, static_cast(buf), len); + } + +-size_t +-CRC24::finish(uint8_t *crc) ++std::array ++CRC24_RNP::finish() + { + uint32_t crc_fin = crc24_final(state_); + state_ = 0; +- if (crc) { +- crc[0] = (crc_fin >> 16) & 0xff; +- crc[1] = (crc_fin >> 8) & 0xff; +- crc[2] = crc_fin & 0xff; +- } +- return 3; ++ std::array res; ++ res[0] = (crc_fin >> 16) & 0xff; ++ res[1] = (crc_fin >> 8) & 0xff; ++ res[2] = crc_fin & 0xff; ++ return res; + } + + }; // namespace rnp +diff --git a/comm/third_party/rnp/src/lib/crypto/hash_crc24.hpp b/third_party/rnp/src/lib/comm/crypto/hash_crc24.hpp +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/lib/crypto/hash_crc24.hpp +@@ -0,0 +1,47 @@ ++/* ++ * Copyright (c) 2022 Ribose Inc. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS ++ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef CRYPTO_HASH_CRC24_HPP_ ++#define CRYPTO_HASH_CRC24_HPP_ ++ ++#include "hash.hpp" ++ ++namespace rnp { ++class CRC24_RNP : public CRC24 { ++ uint32_t state_; ++ CRC24_RNP(); ++ ++ public: ++ virtual ~CRC24_RNP(); ++ ++ static std::unique_ptr create(); ++ ++ void add(const void *buf, size_t len) override; ++ std::array finish() override; ++}; ++} // namespace rnp ++ ++#endif +\ No newline at end of file +diff --git a/comm/third_party/rnp/src/lib/crypto/hash_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/hash_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/hash_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/hash_ossl.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2021 Ribose Inc. ++ * Copyright (c) 2021-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: +@@ -22,22 +22,20 @@ + * 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 "hash_ossl.hpp" + #include + #include + #include +-#include + #include + #include "config.h" +-#include "hash.h" + #include "types.h" + #include "utils.h" + #include "str-utils.h" + #include "defaults.h" +-#include "sha1cd/hash_sha1cd.h" + + static const id_str_pair openssl_alg_map[] = { + {PGP_HASH_MD5, "md5"}, + {PGP_HASH_SHA1, "sha1"}, + {PGP_HASH_RIPEMD, "ripemd160"}, +@@ -50,159 +48,105 @@ static const id_str_pair openssl_alg_map + {PGP_HASH_SHA3_512, "sha3-512"}, + {0, NULL}, + }; + + namespace rnp { +-Hash::Hash(pgp_hash_alg_t alg) ++Hash_OpenSSL::Hash_OpenSSL(pgp_hash_alg_t alg) : Hash(alg) + { +- if (alg == PGP_HASH_SHA1) { +- handle_ = hash_sha1cd_create(); +- if (!handle_) { +- throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); +- } +- alg_ = alg; +- size_ = rnp::Hash::size(alg); +- return; +- } +- const char *hash_name = rnp::Hash::name_backend(alg); +- if (!hash_name) { +- throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); +- } +-#if !defined(ENABLE_SM2) +- if (alg == PGP_HASH_SM3) { +- RNP_LOG("SM3 hash is not available."); +- throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); +- } +-#endif ++ const char * hash_name = Hash_OpenSSL::name_backend(alg); + const EVP_MD *hash_tp = EVP_get_digestbyname(hash_name); + if (!hash_tp) { + RNP_LOG("Error creating hash object for '%s'", hash_name); + throw rnp_exception(RNP_ERROR_BAD_STATE); + } +- EVP_MD_CTX *hash_fn = EVP_MD_CTX_new(); +- if (!hash_fn) { ++ fn_ = EVP_MD_CTX_new(); ++ if (!fn_) { ++ RNP_LOG("Allocation failure"); ++ throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); ++ } ++ int res = EVP_DigestInit_ex(fn_, hash_tp, NULL); ++ if (res != 1) { ++ RNP_LOG("Digest initializataion error %d : %lu", res, ERR_peek_last_error()); ++ EVP_MD_CTX_free(fn_); ++ throw rnp_exception(RNP_ERROR_BAD_STATE); ++ } ++ assert(size_ == (size_t) EVP_MD_size(hash_tp)); ++} ++ ++Hash_OpenSSL::Hash_OpenSSL(const Hash_OpenSSL &src) : Hash(src.alg_) ++{ ++ if (!src.fn_) { ++ throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); ++ } ++ ++ fn_ = EVP_MD_CTX_new(); ++ if (!fn_) { + RNP_LOG("Allocation failure"); + throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } +- int res = EVP_DigestInit_ex(hash_fn, hash_tp, NULL); ++ ++ int res = EVP_MD_CTX_copy(fn_, src.fn_); + if (res != 1) { +- RNP_LOG("Digest initializataion error %d : %lu", res, ERR_peek_last_error()); +- EVP_MD_CTX_free(hash_fn); ++ RNP_LOG("Digest copying error %d: %lu", res, ERR_peek_last_error()); ++ EVP_MD_CTX_free(fn_); + throw rnp_exception(RNP_ERROR_BAD_STATE); + } ++} + +- alg_ = alg; +- size_ = EVP_MD_size(hash_tp); +- handle_ = hash_fn; ++std::unique_ptr ++Hash_OpenSSL::create(pgp_hash_alg_t alg) ++{ ++ return std::unique_ptr(new Hash_OpenSSL(alg)); ++} ++ ++std::unique_ptr ++Hash_OpenSSL::clone() const ++{ ++ return std::unique_ptr(new Hash_OpenSSL(*this)); + } + + void +-Hash::add(const void *buf, size_t len) ++Hash_OpenSSL::add(const void *buf, size_t len) + { +- if (!handle_) { ++ if (!fn_) { + throw rnp_exception(RNP_ERROR_NULL_POINTER); + } +- if (alg_ == PGP_HASH_SHA1) { +- hash_sha1cd_add(handle_, buf, len); +- return; +- } +- assert(alg_ != PGP_HASH_UNKNOWN); +- +- EVP_MD_CTX *hash_fn = static_cast(handle_); +- int res = EVP_DigestUpdate(hash_fn, buf, len); ++ int res = EVP_DigestUpdate(fn_, buf, len); + if (res != 1) { + RNP_LOG("Digest updating error %d: %lu", res, ERR_peek_last_error()); + throw rnp_exception(RNP_ERROR_GENERIC); + } + } + + size_t +-Hash::finish(uint8_t *digest) ++Hash_OpenSSL::finish(uint8_t *digest) + { +- if (!handle_) { ++ if (!fn_) { + return 0; + } +- if (alg_ == PGP_HASH_SHA1) { +- int res = hash_sha1cd_finish(handle_, digest); +- handle_ = NULL; +- size_ = 0; +- if (res) { +- throw rnp_exception(RNP_ERROR_BAD_STATE); +- } +- return 20; +- } +- assert(alg_ != PGP_HASH_UNKNOWN); +- +- EVP_MD_CTX *hash_fn = static_cast(handle_); +- int res = digest ? EVP_DigestFinal_ex(hash_fn, digest, NULL) : 1; +- EVP_MD_CTX_free(hash_fn); +- handle_ = NULL; ++ int res = digest ? EVP_DigestFinal_ex(fn_, digest, NULL) : 1; ++ EVP_MD_CTX_free(fn_); ++ fn_ = NULL; + if (res != 1) { + RNP_LOG("Digest finalization error %d: %lu", res, ERR_peek_last_error()); + return 0; + } + + size_t outsz = size_; + size_ = 0; +- alg_ = PGP_HASH_UNKNOWN; + return outsz; + } + +-void +-Hash::clone(Hash &dst) const ++Hash_OpenSSL::~Hash_OpenSSL() + { +- if (!handle_) { +- throw rnp_exception(RNP_ERROR_BAD_PARAMETERS); +- } +- +- assert(alg_ != PGP_HASH_UNKNOWN); +- +- if (dst.handle_) { +- dst.finish(); +- } +- +- if (alg_ == PGP_HASH_SHA1) { +- dst.handle_ = hash_sha1cd_clone(handle_); +- if (!dst.handle_) { +- throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); +- } +- dst.size_ = size_; +- dst.alg_ = alg_; ++ if (!fn_) { + return; + } +- +- EVP_MD_CTX *hash_fn = EVP_MD_CTX_new(); +- if (!hash_fn) { +- RNP_LOG("Allocation failure"); +- throw rnp_exception(RNP_ERROR_OUT_OF_MEMORY); +- } +- +- int res = EVP_MD_CTX_copy(hash_fn, static_cast(handle_)); +- if (res != 1) { +- RNP_LOG("Digest copying error %d: %lu", res, ERR_peek_last_error()); +- EVP_MD_CTX_free(hash_fn); +- throw rnp_exception(RNP_ERROR_BAD_STATE); +- } +- +- dst.size_ = size_; +- dst.alg_ = alg_; +- dst.handle_ = hash_fn; +-} +- +-Hash::~Hash() +-{ +- if (!handle_) { +- return; +- } +- if (alg_ == PGP_HASH_SHA1) { +- hash_sha1cd_finish(handle_, NULL); +- } else { +- EVP_MD_CTX_free(static_cast(handle_)); +- } ++ EVP_MD_CTX_free(fn_); + } + + const char * +-Hash::name_backend(pgp_hash_alg_t alg) ++Hash_OpenSSL::name_backend(pgp_hash_alg_t alg) + { + return id_str_pair::lookup(openssl_alg_map, alg); + } + } // namespace rnp +diff --git a/comm/third_party/rnp/src/lib/crypto/hash_ossl.hpp b/third_party/rnp/src/lib/comm/crypto/hash_ossl.hpp +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/lib/crypto/hash_ossl.hpp +@@ -0,0 +1,55 @@ ++/* ++ * Copyright (c) 2022 Ribose Inc. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS ++ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef CRYPTO_HASH_OSSL_HPP_ ++#define CRYPTO_HASH_OSSL_HPP_ ++ ++#include "hash.hpp" ++#include ++ ++namespace rnp { ++class Hash_OpenSSL : public Hash { ++ private: ++ EVP_MD_CTX *fn_; ++ ++ Hash_OpenSSL(pgp_hash_alg_t alg); ++ Hash_OpenSSL(const Hash_OpenSSL &src); ++ ++ public: ++ virtual ~Hash_OpenSSL(); ++ ++ static std::unique_ptr create(pgp_hash_alg_t alg); ++ std::unique_ptr clone() const override; ++ ++ void add(const void *buf, size_t len) override; ++ size_t finish(uint8_t *digest = NULL) override; ++ ++ static const char *name_backend(pgp_hash_alg_t alg); ++}; ++ ++} // namespace rnp ++ ++#endif +\ No newline at end of file +diff --git a/comm/third_party/rnp/src/lib/crypto/hash_sha1cd.cpp b/third_party/rnp/src/lib/comm/crypto/hash_sha1cd.cpp +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/lib/crypto/hash_sha1cd.cpp +@@ -0,0 +1,94 @@ ++/* ++ * Copyright (c) 2021-2022 Ribose Inc. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS ++ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include "logging.h" ++#include "hash_sha1cd.hpp" ++ ++namespace rnp { ++Hash_SHA1CD::Hash_SHA1CD() : Hash(PGP_HASH_SHA1) ++{ ++ assert(size_ == 20); ++ SHA1DCInit(&ctx_); ++} ++ ++Hash_SHA1CD::Hash_SHA1CD(const Hash_SHA1CD &src) : Hash(PGP_HASH_SHA1) ++{ ++ ctx_ = src.ctx_; ++} ++ ++Hash_SHA1CD::~Hash_SHA1CD() ++{ ++} ++ ++std::unique_ptr ++Hash_SHA1CD::create() ++{ ++ return std::unique_ptr(new Hash_SHA1CD()); ++} ++ ++std::unique_ptr ++Hash_SHA1CD::clone() const ++{ ++ return std::unique_ptr(new Hash_SHA1CD(*this)); ++} ++ ++/* This produces runtime error: load of misaligned address 0x60d0000030a9 for type 'const ++ * uint32_t' (aka 'const unsigned int'), which requires 4 byte alignment */ ++#if defined(__clang__) ++__attribute__((no_sanitize("undefined"))) ++#endif ++void ++Hash_SHA1CD::add(const void *buf, size_t len) ++{ ++ SHA1DCUpdate(&ctx_, (const char *) buf, len); ++} ++ ++#if defined(__clang__) ++__attribute__((no_sanitize("undefined"))) ++#endif ++size_t ++Hash_SHA1CD::finish(uint8_t *digest) ++{ ++ unsigned char fixed_digest[20]; ++ int res = SHA1DCFinal(fixed_digest, &ctx_); ++ if (res && digest) { ++ /* Show warning only if digest is non-null */ ++ RNP_LOG("Warning! SHA1 collision detected and mitigated."); ++ } ++ if (res) { ++ throw rnp_exception(RNP_ERROR_BAD_STATE); ++ } ++ if (digest) { ++ memcpy(digest, fixed_digest, 20); ++ } ++ return 20; ++} ++ ++} // namespace rnp +diff --git a/comm/third_party/rnp/src/lib/crypto/hash_sha1cd.hpp b/third_party/rnp/src/lib/comm/crypto/hash_sha1cd.hpp +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/lib/crypto/hash_sha1cd.hpp +@@ -0,0 +1,52 @@ ++/* ++ * Copyright (c) 2022 Ribose Inc. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR ++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS ++ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef CRYPTO_HASH_SHA1CD_HPP_ ++#define CRYPTO_HASH_SHA1CD_HPP_ ++ ++#include "hash.hpp" ++#include "sha1cd/sha1.h" ++ ++namespace rnp { ++class Hash_SHA1CD : public Hash { ++ private: ++ SHA1_CTX ctx_; ++ ++ Hash_SHA1CD(); ++ Hash_SHA1CD(const Hash_SHA1CD &src); ++ ++ public: ++ virtual ~Hash_SHA1CD(); ++ ++ static std::unique_ptr create(); ++ std::unique_ptr clone() const override; ++ ++ void add(const void *buf, size_t len) override; ++ size_t finish(uint8_t *digest = NULL) override; ++}; ++ ++} // namespace rnp ++#endif +diff --git a/comm/third_party/rnp/src/lib/crypto/mpi.cpp b/third_party/rnp/src/lib/comm/crypto/mpi.cpp +--- a/comm/third_party/rnp/src/lib/crypto/mpi.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/mpi.cpp +@@ -25,11 +25,10 @@ + */ + + #include + #include + #include "mpi.h" +-#include "hash.h" + #include "mem.h" + #include "utils.h" + + size_t + mpi_bits(const pgp_mpi_t *val) +diff --git a/comm/third_party/rnp/src/lib/crypto/ossl_common.h b/third_party/rnp/src/lib/comm/crypto/ossl_common.h +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/lib/crypto/ossl_common.h +@@ -0,0 +1,40 @@ ++/* ++ * Copyright (c) 2022, [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. ++ */ ++ ++#ifndef RNP_OSSL_COMMON_H_ ++#define RNP_OSSL_COMMON_H_ ++ ++#include ++#include "config.h" ++#include ++ ++inline const char * ++ossl_latest_err() ++{ ++ return ERR_error_string(ERR_peek_last_error(), NULL); ++} ++ ++#endif +diff --git a/comm/third_party/rnp/src/lib/crypto/rsa.cpp b/third_party/rnp/src/lib/comm/crypto/rsa.cpp +--- a/comm/third_party/rnp/src/lib/crypto/rsa.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/rsa.cpp +@@ -1,7 +1,7 @@ + /*- +- * Copyright (c) 2017-2018 Ribose Inc. ++ * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: +@@ -76,12 +76,12 @@ + /** \file + */ + #include + #include + #include ++#include "hash_botan.hpp" + #include "crypto/rsa.h" +-#include "hash.h" + #include "config.h" + #include "utils.h" + #include "bn.h" + + rnp_result_t +@@ -241,11 +241,11 @@ rsa_verify_pkcs1(const pgp_rsa_signature + } + + snprintf(padding_name, + sizeof(padding_name), + "EMSA-PKCS1-v1_5(Raw,%s)", +- rnp::Hash::name_backend(hash_alg)); ++ rnp::Hash_Botan::name_backend(hash_alg)); + + if (botan_pk_op_verify_create(&verify_op, rsa_key, padding_name, 0) != 0) { + goto done; + } + +@@ -288,11 +288,11 @@ rsa_sign_pkcs1(rnp::RNG * rng, + } + + snprintf(padding_name, + sizeof(padding_name), + "EMSA-PKCS1-v1_5(Raw,%s)", +- rnp::Hash::name_backend(hash_alg)); ++ rnp::Hash_Botan::name_backend(hash_alg)); + + if (botan_pk_op_sign_create(&sign_op, rsa_key, padding_name, 0) != 0) { + goto done; + } + +diff --git a/comm/third_party/rnp/src/lib/crypto/rsa_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/rsa_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/rsa_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/rsa_ossl.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2021-2022, [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: + * +@@ -24,19 +24,26 @@ + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + #include + #include ++#include + #include "crypto/rsa.h" +-#include "hash.h" + #include "config.h" + #include "utils.h" + #include "bn.h" ++#include "ossl_common.h" + #include + #include + #include ++#ifdef CRYPTO_BACKEND_OPENSSL3 ++#include ++#include ++#endif ++#include "hash_ossl.hpp" + ++#ifndef CRYPTO_BACKEND_OPENSSL3 + static RSA * + rsa_load_public_key(const pgp_rsa_key_t *key) + { + RSA * rsa = NULL; + bignum_t *n = mpi2bn(&key->n); +@@ -135,22 +142,156 @@ rsa_init_context(const pgp_rsa_key_t *ke + done: + RSA_free(rsakey); + EVP_PKEY_free(evpkey); + return ctx; + } ++#else ++static OSSL_PARAM * ++rsa_bld_params(const pgp_rsa_key_t *key, bool secret) ++{ ++ OSSL_PARAM * params = NULL; ++ OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); ++ bignum_t * n = mpi2bn(&key->n); ++ bignum_t * e = mpi2bn(&key->e); ++ bignum_t * d = NULL; ++ bignum_t * p = NULL; ++ bignum_t * q = NULL; ++ bignum_t * u = NULL; ++ BN_CTX * bnctx = NULL; ++ ++ if (!n || !e || !bld) { ++ RNP_LOG("Out of memory"); ++ goto done; ++ } ++ ++ if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n) || ++ !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e)) { ++ RNP_LOG("Failed to push RSA params."); ++ goto done; ++ } ++ if (secret) { ++ d = mpi2bn(&key->d); ++ /* As we have u = p^-1 mod q, and qInv = q^-1 mod p, we need to replace one with ++ * another */ ++ p = mpi2bn(&key->q); ++ q = mpi2bn(&key->p); ++ u = mpi2bn(&key->u); ++ if (!d || !p || !q || !u) { ++ goto done; ++ } ++ /* We need to calculate exponents manually */ ++ bnctx = BN_CTX_new(); ++ if (!bnctx) { ++ RNP_LOG("Failed to allocate BN_CTX."); ++ goto done; ++ } ++ bignum_t *p1 = BN_CTX_get(bnctx); ++ bignum_t *q1 = BN_CTX_get(bnctx); ++ bignum_t *dp = BN_CTX_get(bnctx); ++ bignum_t *dq = BN_CTX_get(bnctx); ++ if (!BN_copy(p1, p) || !BN_sub_word(p1, 1) || !BN_copy(q1, q) || !BN_sub_word(q1, 1) || ++ !BN_mod(dp, d, p1, bnctx) || !BN_mod(dq, d, q1, bnctx)) { ++ RNP_LOG("Failed to calculate dP or dQ."); ++ } ++ /* Push params */ ++ if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_D, d) || ++ !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR1, p) || ++ !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR2, q) || ++ !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, dp) || ++ !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, dq) || ++ !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, u)) { ++ RNP_LOG("Failed to push RSA secret params."); ++ goto done; ++ } ++ } ++ params = OSSL_PARAM_BLD_to_param(bld); ++ if (!params) { ++ RNP_LOG("Failed to build RSA params: %s.", ossl_latest_err()); ++ } ++done: ++ bn_free(n); ++ bn_free(e); ++ bn_free(d); ++ bn_free(p); ++ bn_free(q); ++ bn_free(u); ++ BN_CTX_free(bnctx); ++ OSSL_PARAM_BLD_free(bld); ++ return params; ++} ++ ++static EVP_PKEY * ++rsa_load_key(const pgp_rsa_key_t *key, bool secret) ++{ ++ /* Build params */ ++ OSSL_PARAM *params = rsa_bld_params(key, secret); ++ if (!params) { ++ return NULL; ++ } ++ /* Create context for key creation */ ++ EVP_PKEY * res = NULL; ++ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); ++ if (!ctx) { ++ RNP_LOG("Context allocation failed: %s", ossl_latest_err()); ++ goto done; ++ } ++ /* Create key */ ++ if (EVP_PKEY_fromdata_init(ctx) <= 0) { ++ RNP_LOG("Failed to initialize key creation: %s", ossl_latest_err()); ++ goto done; ++ } ++ if (EVP_PKEY_fromdata( ++ ctx, &res, secret ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY, params) <= 0) { ++ RNP_LOG("Failed to create RSA key: %s", ossl_latest_err()); ++ } ++done: ++ EVP_PKEY_CTX_free(ctx); ++ OSSL_PARAM_free(params); ++ return res; ++} ++ ++static EVP_PKEY_CTX * ++rsa_init_context(const pgp_rsa_key_t *key, bool secret) ++{ ++ EVP_PKEY *pkey = rsa_load_key(key, secret); ++ if (!pkey) { ++ return NULL; ++ } ++ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); ++ if (!ctx) { ++ RNP_LOG("Context allocation failed: %s", ossl_latest_err()); ++ } ++ EVP_PKEY_free(pkey); ++ return ctx; ++} ++#endif + + rnp_result_t + rsa_validate_key(rnp::RNG *rng, const pgp_rsa_key_t *key, bool secret) + { ++#ifdef CRYPTO_BACKEND_OPENSSL3 ++ EVP_PKEY_CTX *ctx = rsa_init_context(key, secret); ++ if (!ctx) { ++ RNP_LOG("Failed to init context: %s", ossl_latest_err()); ++ return RNP_ERROR_GENERIC; ++ } ++ int res = secret ? EVP_PKEY_pairwise_check(ctx) : EVP_PKEY_public_check(ctx); ++ if (res <= 0) { ++ RNP_LOG("Key validation error: %s", ossl_latest_err()); ++ } ++ EVP_PKEY_CTX_free(ctx); ++ return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; ++#else + if (secret) { + EVP_PKEY_CTX *ctx = rsa_init_context(key, secret); + if (!ctx) { ++ RNP_LOG("Failed to init context: %s", ossl_latest_err()); + return RNP_ERROR_GENERIC; + } + int res = EVP_PKEY_check(ctx); +- if (res < 0) { +- RNP_LOG("Key validation error: %lu", ERR_peek_last_error()); ++ if (res <= 0) { ++ RNP_LOG("Key validation error: %s", ossl_latest_err()); + } + EVP_PKEY_CTX_free(ctx); + return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC; + } + +@@ -169,35 +310,52 @@ rsa_validate_key(rnp::RNG *rng, const pg + ret = RNP_SUCCESS; + done: + bn_free(n); + bn_free(e); + return ret; ++#endif + } + + static bool +-rsa_setup_context(EVP_PKEY_CTX *ctx, pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN) ++rsa_setup_context(EVP_PKEY_CTX *ctx) + { + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) { + RNP_LOG("Failed to set padding: %lu", ERR_peek_last_error()); + return false; + } +- if (hash_alg == PGP_HASH_UNKNOWN) { +- return true; +- } +- const char *hash_name = rnp::Hash::name_backend(hash_alg); ++ return true; ++} ++ ++static const uint8_t PKCS1_SHA1_ENCODING[15] = { ++ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}; ++ ++static bool ++rsa_setup_signature_hash(EVP_PKEY_CTX * ctx, ++ pgp_hash_alg_t hash_alg, ++ const uint8_t *&enc, ++ size_t & enc_size) ++{ ++ const char *hash_name = rnp::Hash_OpenSSL::name(hash_alg); + if (!hash_name) { + RNP_LOG("Unknown hash: %d", (int) hash_alg); + return false; + } + const EVP_MD *hash_tp = EVP_get_digestbyname(hash_name); + if (!hash_tp) { + RNP_LOG("Error creating hash object for '%s'", hash_name); + return false; + } + if (EVP_PKEY_CTX_set_signature_md(ctx, hash_tp) <= 0) { +- RNP_LOG("Failed to set digest: %lu", ERR_peek_last_error()); +- return false; ++ if ((hash_alg != PGP_HASH_SHA1)) { ++ RNP_LOG("Failed to set digest %s: %s", hash_name, ossl_latest_err()); ++ return false; ++ } ++ enc = &PKCS1_SHA1_ENCODING[0]; ++ enc_size = sizeof(PKCS1_SHA1_ENCODING); ++ } else { ++ enc = NULL; ++ enc_size = 0; + } + return true; + } + + rnp_result_t +@@ -241,17 +399,31 @@ rsa_verify_pkcs1(const pgp_rsa_signature + rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID; + EVP_PKEY_CTX *ctx = rsa_init_context(key, false); + if (!ctx) { + return ret; + } ++ const uint8_t *hash_enc = NULL; ++ size_t hash_enc_size = 0; ++ uint8_t hash_enc_buf[PGP_MAX_HASH_SIZE + 32] = {0}; ++ assert(hash_len + hash_enc_size <= sizeof(hash_enc_buf)); ++ + if (EVP_PKEY_verify_init(ctx) <= 0) { + RNP_LOG("Failed to initialize verification: %lu", ERR_peek_last_error()); + goto done; + } +- if (!rsa_setup_context(ctx, hash_alg)) { ++ if (!rsa_setup_context(ctx) || ++ !rsa_setup_signature_hash(ctx, hash_alg, hash_enc, hash_enc_size)) { + goto done; + } ++ /* Check whether we need to workaround on unsupported SHA1 for RSA signature verification ++ */ ++ if (hash_enc_size) { ++ memcpy(hash_enc_buf, hash_enc, hash_enc_size); ++ memcpy(&hash_enc_buf[hash_enc_size], hash, hash_len); ++ hash = hash_enc_buf; ++ hash_len += hash_enc_size; ++ } + int res; + if (sig->s.len < key->n.len) { + /* OpenSSL doesn't like signatures smaller then N */ + pgp_mpi_t sn; + sn.len = key->n.len; +@@ -263,12 +435,11 @@ rsa_verify_pkcs1(const pgp_rsa_signature + res = EVP_PKEY_verify(ctx, sig->s.mpi, sig->s.len, hash, hash_len); + } + if (res > 0) { + ret = RNP_SUCCESS; + } else { +- RNP_LOG("RSA verification failure: %s", +- ERR_reason_error_string(ERR_peek_last_error())); ++ RNP_LOG("RSA verification failure: %s", ossl_latest_err()); + } + done: + EVP_PKEY_CTX_free(ctx); + return ret; + } +@@ -288,17 +459,30 @@ rsa_sign_pkcs1(rnp::RNG * rng, + } + EVP_PKEY_CTX *ctx = rsa_init_context(key, true); + if (!ctx) { + return ret; + } ++ const uint8_t *hash_enc = NULL; ++ size_t hash_enc_size = 0; ++ uint8_t hash_enc_buf[PGP_MAX_HASH_SIZE + 32] = {0}; ++ assert(hash_len + hash_enc_size <= sizeof(hash_enc_buf)); + if (EVP_PKEY_sign_init(ctx) <= 0) { + RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error()); + goto done; + } +- if (!rsa_setup_context(ctx, hash_alg)) { ++ if (!rsa_setup_context(ctx) || ++ !rsa_setup_signature_hash(ctx, hash_alg, hash_enc, hash_enc_size)) { + goto done; + } ++ /* Check whether we need to workaround on unsupported SHA1 for RSA signature verification ++ */ ++ if (hash_enc_size) { ++ memcpy(hash_enc_buf, hash_enc, hash_enc_size); ++ memcpy(&hash_enc_buf[hash_enc_size], hash, hash_len); ++ hash = hash_enc_buf; ++ hash_len += hash_enc_size; ++ } + sig->s.len = PGP_MPINT_SIZE; + if (EVP_PKEY_sign(ctx, sig->s.mpi, &sig->s.len, hash, hash_len) <= 0) { + RNP_LOG("Encryption failed: %lu", ERR_peek_last_error()); + sig->s.len = 0; + goto done; +@@ -349,17 +533,16 @@ rsa_generate(rnp::RNG *rng, pgp_rsa_key_ + { + if ((numbits < 1024) || (numbits > PGP_MPINT_BITS)) { + return RNP_ERROR_BAD_PARAMETERS; + } + +- rnp_result_t ret = RNP_ERROR_GENERIC; +- RSA * rsa = NULL; +- EVP_PKEY * pkey = NULL; +- EVP_PKEY_CTX *ctx = NULL; +- bignum_t * u = NULL; +- bignum_t * nq = NULL; +- BN_CTX * bnctx = NULL; ++ rnp_result_t ret = RNP_ERROR_GENERIC; ++ const RSA * rsa = NULL; ++ EVP_PKEY * pkey = NULL; ++ EVP_PKEY_CTX * ctx = NULL; ++ const bignum_t *u = NULL; ++ BN_CTX * bnctx = NULL; + + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) { + RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error()); + return ret; +@@ -400,32 +583,39 @@ rsa_generate(rnp::RNG *rng, pgp_rsa_key_ + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; + } + /* OpenSSL doesn't care whether p < q */ + if (BN_cmp(p, q) > 0) { ++ /* In this case we have u, as iqmp is inverse of q mod p, and we exchange them */ + const bignum_t *tmp = p; + p = q; + q = tmp; +- } +- /* we need to calculate u, since we need inverse of p mod q, while OpenSSL has inverse of q +- * mod p, and doesn't care of p < q */ +- bnctx = BN_CTX_new(); +- u = BN_new(); +- nq = BN_new(); +- if (!ctx || !u || !nq) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; ++ u = RSA_get0_iqmp(rsa); ++ } else { ++ /* we need to calculate u, since we need inverse of p mod q, while OpenSSL has inverse ++ * of q mod p, and doesn't care of p < q */ ++ bnctx = BN_CTX_new(); ++ if (!bnctx) { ++ ret = RNP_ERROR_OUT_OF_MEMORY; ++ goto done; ++ } ++ BN_CTX_start(bnctx); ++ bignum_t *nu = BN_CTX_get(bnctx); ++ bignum_t *nq = BN_CTX_get(bnctx); ++ if (!nu || !nq) { ++ ret = RNP_ERROR_OUT_OF_MEMORY; ++ goto done; ++ } ++ BN_with_flags(nq, q, BN_FLG_CONSTTIME); ++ /* calculate inverse of p mod q */ ++ if (!BN_mod_inverse(nu, p, nq, bnctx)) { ++ RNP_LOG("Failed to calculate u"); ++ ret = RNP_ERROR_BAD_STATE; ++ goto done; ++ } ++ u = nu; + } +- BN_with_flags(nq, q, BN_FLG_CONSTTIME); +- /* calculate inverse of p mod q */ +- if (!BN_mod_inverse(u, p, nq, bnctx)) { +- bn_free(nq); +- RNP_LOG("Failed to calculate u"); +- ret = RNP_ERROR_BAD_STATE; +- goto done; +- } +- bn_free(nq); + bn2mpi(n, &key->n); + bn2mpi(e, &key->e); + bn2mpi(p, &key->p); + bn2mpi(q, &key->q); + bn2mpi(d, &key->d); +@@ -433,8 +623,7 @@ rsa_generate(rnp::RNG *rng, pgp_rsa_key_ + ret = RNP_SUCCESS; + done: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + BN_CTX_free(bnctx); +- bn_free(u); + return ret; + } +diff --git a/comm/third_party/rnp/src/lib/crypto/s2k.cpp b/third_party/rnp/src/lib/comm/crypto/s2k.cpp +--- a/comm/third_party/rnp/src/lib/crypto/s2k.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/s2k.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). +@@ -41,10 +41,11 @@ + #include "rnp.h" + #include "types.h" + #include "utils.h" + #ifdef CRYPTO_BACKEND_BOTAN + #include ++#include "hash_botan.hpp" + #endif + + bool + pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysize) + { +@@ -85,12 +86,14 @@ pgp_s2k_iterated(pgp_hash_alg_t alg, + const char * password, + const uint8_t *salt, + size_t iterations) + { + char s2k_algo_str[128]; +- snprintf( +- s2k_algo_str, sizeof(s2k_algo_str), "OpenPGP-S2K(%s)", rnp::Hash::name_backend(alg)); ++ snprintf(s2k_algo_str, ++ sizeof(s2k_algo_str), ++ "OpenPGP-S2K(%s)", ++ rnp::Hash_Botan::name_backend(alg)); + + return botan_pwdhash(s2k_algo_str, + iterations, + 0, + 0, +@@ -164,18 +167,18 @@ pgp_s2k_compute_iters(pgp_hash_alg_t alg + + uint64_t start = get_timestamp_usec(); + uint64_t end = start; + size_t bytes = 0; + try { +- rnp::Hash hash(alg); +- uint8_t buf[8192] = {0}; ++ auto hash = rnp::Hash::create(alg); ++ uint8_t buf[8192] = {0}; + while (end - start < trial_msec * 1000ull) { +- hash.add(buf, sizeof(buf)); ++ hash->add(buf, sizeof(buf)); + bytes += sizeof(buf); + end = get_timestamp_usec(); + } +- hash.finish(buf); ++ hash->finish(buf); + } catch (const std::exception &e) { + RNP_LOG("Failed to hash data: %s", e.what()); + return 0; + } + +diff --git a/comm/third_party/rnp/src/lib/crypto/s2k.h b/third_party/rnp/src/lib/comm/crypto/s2k.h +--- a/comm/third_party/rnp/src/lib/crypto/s2k.h ++++ b/comm/third_party/rnp/src/lib/crypto/s2k.h +@@ -30,11 +30,11 @@ + + #ifndef RNP_S2K_H_ + #define RNP_S2K_H_ + + #include +-#include "hash.h" ++#include "repgp/repgp_def.h" + + typedef struct pgp_s2k_t pgp_s2k_t; + + int pgp_s2k_iterated(pgp_hash_alg_t alg, + uint8_t * out, +diff --git a/comm/third_party/rnp/src/lib/crypto/s2k_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/s2k_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/s2k_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/s2k_ossl.cpp +@@ -26,10 +26,11 @@ + + #include + #include + #include + #include ++#include "hash.hpp" + #include "s2k.h" + #include "mem.h" + #include "logging.h" + + int +@@ -60,28 +61,28 @@ pgp_s2k_iterated(pgp_hash_alg_t alg, + memcpy(data.data() + salt_len, password, pswd_len); + size_t zeroes = 0; + + while (output_len) { + /* create hash context */ +- rnp::Hash hash(alg); ++ auto hash = rnp::Hash::create(alg); + /* add leading zeroes */ + for (size_t z = 0; z < zeroes; z++) { + uint8_t zero = 0; +- hash.add(&zero, 1); ++ hash->add(&zero, 1); + } + if (!data.empty()) { + /* if iteration is 1 then still hash the whole data chunk */ + size_t left = std::max(data.size(), iterations); + while (left) { + size_t to_hash = std::min(left, data.size()); +- hash.add(data.data(), to_hash); ++ hash->add(data.data(), to_hash); + left -= to_hash; + } + } + rnp::secure_vector dgst(hash_len); + size_t out_cpy = std::min(dgst.size(), output_len); +- if (hash.finish(dgst.data()) != dgst.size()) { ++ if (hash->finish(dgst.data()) != dgst.size()) { + RNP_LOG("Unexpected digest size."); + return 1; + } + memcpy(out, dgst.data(), out_cpy); + output_len -= out_cpy; +diff --git a/comm/third_party/rnp/src/lib/crypto/sha1cd/hash_sha1cd.cpp b/third_party/rnp/src/lib/comm/crypto/sha1cd/hash_sha1cd.cpp +deleted file mode 100644 +--- a/comm/third_party/rnp/src/lib/crypto/sha1cd/hash_sha1cd.cpp ++++ /dev/null +@@ -1,82 +0,0 @@ +-/* +- * Copyright (c) 2021 Ribose Inc. +- * All rights reserved. +- * +- * Redistribution and use in source and binary forms, with or without +- * modification, are permitted provided that the following conditions +- * are met: +- * 1. Redistributions of source code must retain the above copyright +- * notice, this list of conditions and the following disclaimer. +- * 2. Redistributions in binary form must reproduce the above copyright +- * notice, this list of conditions and the following disclaimer in the +- * documentation and/or other materials provided with the distribution. +- * +- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +- * POSSIBILITY OF SUCH DAMAGE. +- */ +- +-#include +-#include +-#include +-#include "hash_sha1cd.h" +-#include "sha1.h" +-#include "logging.h" +- +-void * +-hash_sha1cd_create() +-{ +- SHA1_CTX *res = (SHA1_CTX *) calloc(1, sizeof(SHA1_CTX)); +- if (res) { +- SHA1DCInit(res); +- } +- return res; +-} +- +-/* This produces runtime error: load of misaligned address 0x60d0000030a9 for type 'const +- * uint32_t' (aka 'const unsigned int'), which requires 4 byte alignment */ +-#if defined(__clang__) +-__attribute__((no_sanitize("undefined"))) +-#endif +-void +-hash_sha1cd_add(void *ctx, const void *buf, size_t len) +-{ +- SHA1DCUpdate((SHA1_CTX *) ctx, (const char *) buf, len); +-} +- +-void * +-hash_sha1cd_clone(void *ctx) +-{ +- SHA1_CTX *res = (SHA1_CTX *) calloc(1, sizeof(SHA1_CTX)); +- if (res) { +- *res = *((SHA1_CTX *) ctx); +- } +- return res; +-} +- +-#if defined(__clang__) +-__attribute__((no_sanitize("undefined"))) +-#endif +-int +-hash_sha1cd_finish(void *ctx, uint8_t *digest) +-{ +- unsigned char fixed_digest[20]; +- int res = 0; +- if ((res = SHA1DCFinal(fixed_digest, (SHA1_CTX *) ctx)) && digest) { +- /* Show warning only if digest is non-null */ +- RNP_LOG("Warning! SHA1 collision detected and mitigated."); +- } +- if (digest) { +- memcpy(digest, fixed_digest, 20); +- } +- free(ctx); +- return res; +-} +diff --git a/comm/third_party/rnp/src/lib/crypto/sha1cd/hash_sha1cd.h b/third_party/rnp/src/lib/comm/crypto/sha1cd/hash_sha1cd.h +deleted file mode 100644 +--- a/comm/third_party/rnp/src/lib/crypto/sha1cd/hash_sha1cd.h ++++ /dev/null +@@ -1,39 +0,0 @@ +-/* +- * Copyright (c) 2021 Ribose Inc. +- * All rights reserved. +- * +- * Redistribution and use in source and binary forms, with or without +- * modification, are permitted provided that the following conditions +- * are met: +- * 1. Redistributions of source code must retain the above copyright +- * notice, this list of conditions and the following disclaimer. +- * 2. Redistributions in binary form must reproduce the above copyright +- * notice, this list of conditions and the following disclaimer in the +- * documentation and/or other materials provided with the distribution. +- * +- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +- * POSSIBILITY OF SUCH DAMAGE. +- */ +- +-#ifndef CRYPTO_HASH_SHA1CD_H_ +-#define CRYPTO_HASH_SHA1CD_H_ +-#include +- +-void *hash_sha1cd_create(); +- +-void hash_sha1cd_add(void *ctx, const void *buf, size_t len); +- +-void *hash_sha1cd_clone(void *ctx); +- +-int hash_sha1cd_finish(void *ctx, uint8_t *digest); +- +-#endif +\ No newline at end of file +diff --git a/comm/third_party/rnp/src/lib/crypto/signatures.cpp b/third_party/rnp/src/lib/comm/crypto/signatures.cpp +--- a/comm/third_party/rnp/src/lib/crypto/signatures.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/signatures.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2018-2022, [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: +@@ -50,26 +50,27 @@ signature_hash_finish(const pgp_signatur + hash.add(trailer, 6); + } + hlen = hash.finish(hbuf); + } + +-void +-signature_init(const pgp_key_material_t &key, pgp_hash_alg_t hash_alg, rnp::Hash &hash) ++std::unique_ptr ++signature_init(const pgp_key_material_t &key, pgp_hash_alg_t hash_alg) + { +- hash = rnp::Hash(hash_alg); ++ auto hash = rnp::Hash::create(hash_alg); + if (key.alg == PGP_PKA_SM2) { + #if defined(ENABLE_SM2) +- rnp_result_t r = sm2_compute_za(key.ec, hash); ++ rnp_result_t r = sm2_compute_za(key.ec, *hash); + if (r != RNP_SUCCESS) { + RNP_LOG("failed to compute SM2 ZA field"); + throw rnp::rnp_exception(r); + } + #else + RNP_LOG("SM2 ZA computation not available"); + throw rnp::rnp_exception(RNP_ERROR_NOT_IMPLEMENTED); + #endif + } ++ return hash; + } + + void + signature_calculate(pgp_signature_t & sig, + pgp_key_material_t & seckey, +@@ -187,118 +188,30 @@ signature_calculate(pgp_signature_t & + RNP_LOG("%s", e.what()); + throw; + } + } + +-static bool is_hash_alg_allowed_in_sig(const pgp_hash_alg_t hash_alg) +-{ +- switch (hash_alg) { +- case PGP_HASH_SHA1: +- case PGP_HASH_RIPEMD: +- case PGP_HASH_SHA256: +- case PGP_HASH_SHA384: +- case PGP_HASH_SHA512: +- case PGP_HASH_SHA224: +- case PGP_HASH_SHA3_256: +- case PGP_HASH_SHA3_512: +- return true; +- +- case PGP_HASH_MD5: +- case PGP_HASH_SM3: +- case PGP_HASH_UNKNOWN: +- default: +- return false; +- } +-} +- +-static bool is_pubkey_alg_allowed_in_sig(const pgp_pubkey_alg_t pubkey_alg) { +- switch (pubkey_alg) { +- case PGP_PKA_RSA: +- case PGP_PKA_RSA_ENCRYPT_ONLY: +- case PGP_PKA_RSA_SIGN_ONLY: +- case PGP_PKA_ELGAMAL: +- case PGP_PKA_DSA: +- case PGP_PKA_ECDH: +- case PGP_PKA_ECDSA: +- case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: +- case PGP_PKA_EDDSA: +- return true; +- +- case PGP_PKA_RESERVED_DH: +- case PGP_PKA_NOTHING: +- case PGP_PKA_SM2: +- case PGP_PKA_PRIVATE00: +- case PGP_PKA_PRIVATE01: +- case PGP_PKA_PRIVATE02: +- case PGP_PKA_PRIVATE03: +- case PGP_PKA_PRIVATE04: +- case PGP_PKA_PRIVATE05: +- case PGP_PKA_PRIVATE06: +- case PGP_PKA_PRIVATE07: +- case PGP_PKA_PRIVATE08: +- case PGP_PKA_PRIVATE09: +- case PGP_PKA_PRIVATE10: +- default: +- return false; +- } +-} +- + rnp_result_t + signature_validate(const pgp_signature_t & sig, + const pgp_key_material_t & key, + rnp::Hash & hash, + const rnp::SecurityContext &ctx) + { +- if (!is_hash_alg_allowed_in_sig(hash.alg())) { +- return RNP_ERROR_SIGNATURE_INVALID; +- } +- +- if (!is_pubkey_alg_allowed_in_sig(sig.palg)) { +- return RNP_ERROR_SIGNATURE_INVALID; +- } +- + if (sig.palg != key.alg) { + RNP_LOG("Signature and key do not agree on algorithm type: %d vs %d", + (int) sig.palg, + (int) key.alg); + return RNP_ERROR_BAD_PARAMETERS; + } + +- bool check_security_level = true; +- if (hash.alg() == PGP_HASH_SHA1) { +- /* Check signature security */ +- switch (sig.type()) { +- /* key certifications */ +- case PGP_CERT_GENERIC: +- case PGP_CERT_PERSONA: +- case PGP_CERT_CASUAL: +- case PGP_CERT_POSITIVE: +- /* subkey binding signature */ +- case PGP_SIG_SUBKEY: +- case PGP_SIG_PRIMARY: +- /* direct-key signature */ +- case PGP_SIG_DIRECT: +- /* revocation signatures */ +- case PGP_SIG_REV_KEY: +- case PGP_SIG_REV_SUBKEY: +- case PGP_SIG_REV_CERT: +- /* Allow */ +- check_security_level = false; +- break; +- +- default: +- break; +- } +- } +- +- if (check_security_level) { +- /* Only allow if the additional check passes. */ +- if (ctx.profile.hash_level(sig.halg, sig.creation()) < rnp::SecurityLevel::Default) { +- RNP_LOG("Insecure hash algorithm %d, marking signature as invalid.", sig.halg); +- return RNP_ERROR_SIGNATURE_INVALID; +- +- } ++ /* Check signature security */ ++ auto action = ++ sig.is_document() ? rnp::SecurityAction::VerifyData : rnp::SecurityAction::VerifyKey; ++ if (ctx.profile.hash_level(sig.halg, sig.creation(), action) < ++ rnp::SecurityLevel::Default) { ++ RNP_LOG("Insecure hash algorithm %d, marking signature as invalid.", sig.halg); ++ return RNP_ERROR_SIGNATURE_INVALID; + } + + /* Finalize hash */ + uint8_t hval[PGP_MAX_HASH_SIZE]; + size_t hlen = 0; +diff --git a/comm/third_party/rnp/src/lib/crypto/signatures.h b/third_party/rnp/src/lib/comm/crypto/signatures.h +--- a/comm/third_party/rnp/src/lib/crypto/signatures.h ++++ b/comm/third_party/rnp/src/lib/crypto/signatures.h +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2018-2021, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2018-2022, [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: +@@ -25,20 +25,20 @@ + */ + + #ifndef RNP_SIGNATURES_H_ + #define RNP_SIGNATURES_H_ + +-#include "types.h" +-#include "crypto/hash.h" ++#include "crypto/hash.hpp" + + /** + * @brief Initialize a signature computation. + * @param key the key that will be used to sign or verify + * @param hash_alg the digest algo to be used + * @param hash digest object that will be initialized + */ +-void signature_init(const pgp_key_material_t &key, pgp_hash_alg_t hash_alg, rnp::Hash &hash); ++std::unique_ptr signature_init(const pgp_key_material_t &key, ++ pgp_hash_alg_t hash_alg); + + /** + * @brief Calculate signature with pre-populated hash + * @param sig signature to calculate + * @param seckey signing secret key material +diff --git a/comm/third_party/rnp/src/lib/crypto/sm2.cpp b/third_party/rnp/src/lib/comm/crypto/sm2.cpp +--- a/comm/third_party/rnp/src/lib/crypto/sm2.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/sm2.cpp +@@ -1,7 +1,7 @@ + /*- +- * Copyright (c) 2017 Ribose Inc. ++ * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: +@@ -24,12 +24,12 @@ + * POSSIBILITY OF SUCH DAMAGE. + */ + + #include + #include ++#include "hash_botan.hpp" + #include "sm2.h" +-#include "hash.h" + #include "utils.h" + #include "bn.h" + + static bool + sm2_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *keydata) +@@ -85,11 +85,11 @@ sm2_compute_za(const pgp_ec_key_t &key, + { + rnp_result_t result = RNP_ERROR_GENERIC; + botan_pubkey_t sm2_key = NULL; + int rc; + +- const char *hash_algo = rnp::Hash::name_backend(hash.alg()); ++ const char *hash_algo = rnp::Hash_Botan::name_backend(hash.alg()); + size_t digest_len = hash.size(); + + uint8_t *digest_buf = (uint8_t *) malloc(digest_len); + if (!digest_buf) { + return RNP_ERROR_OUT_OF_MEMORY; +@@ -319,11 +319,12 @@ sm2_encrypt(rnp::RNG * rng, + /* + SM2 encryption doesn't have any kind of format specifier because + it's an all in one scheme, only the hash (used for the integrity + check) is specified. + */ +- if (botan_pk_op_encrypt_create(&enc_op, sm2_key, rnp::Hash::name_backend(hash_algo), 0)) { ++ if (botan_pk_op_encrypt_create( ++ &enc_op, sm2_key, rnp::Hash_Botan::name_backend(hash_algo), 0)) { + goto done; + } + + out->m.len = sizeof(out->m.mpi); + if (botan_pk_op_encrypt(enc_op, rng->handle(), out->m.mpi, &out->m.len, in, in_len) == 0) { +@@ -360,11 +361,11 @@ sm2_decrypt(uint8_t * o + RNP_LOG("Can't load private key"); + goto done; + } + + hash_id = in->m.mpi[in_len - 1]; +- hash_name = rnp::Hash::name_backend((pgp_hash_alg_t) hash_id); ++ hash_name = rnp::Hash_Botan::name_backend((pgp_hash_alg_t) hash_id); + if (!hash_name) { + RNP_LOG("Unknown hash used in SM2 ciphertext"); + goto done; + } + +diff --git a/comm/third_party/rnp/src/lib/crypto/sm2.h b/third_party/rnp/src/lib/comm/crypto/sm2.h +--- a/comm/third_party/rnp/src/lib/crypto/sm2.h ++++ b/comm/third_party/rnp/src/lib/crypto/sm2.h +@@ -1,7 +1,7 @@ + /*- +- * Copyright (c) 2017 Ribose Inc. ++ * Copyright (c) 2017-2022 Ribose Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: +@@ -34,11 +34,11 @@ typedef struct pgp_sm2_encrypted_t { + pgp_mpi_t m; + } pgp_sm2_encrypted_t; + + namespace rnp { + class Hash; +-} ++} // namespace rnp + + #if defined(ENABLE_SM2) + rnp_result_t sm2_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret); + + /** +diff --git a/comm/third_party/rnp/src/lib/crypto/sm2_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/sm2_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/sm2_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/sm2_ossl.cpp +@@ -24,11 +24,10 @@ + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + #include + #include "sm2.h" +-#include "hash.h" + #include "utils.h" + + rnp_result_t + sm2_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret) + { +diff --git a/comm/third_party/rnp/src/lib/crypto/symmetric.cpp b/third_party/rnp/src/lib/comm/crypto/symmetric.cpp +--- a/comm/third_party/rnp/src/lib/crypto/symmetric.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/symmetric.cpp +@@ -61,11 +61,11 @@ + + static const char * + pgp_sa_to_botan_string(pgp_symm_alg_t alg) + { + switch (alg) { +-#if defined(BOTAN_HAS_IDEA) ++#if defined(BOTAN_HAS_IDEA) && defined(ENABLE_IDEA) + case PGP_SA_IDEA: + return "IDEA"; + #endif + + #if defined(BOTAN_HAS_DES) +diff --git a/comm/third_party/rnp/src/lib/crypto/symmetric_ossl.cpp b/third_party/rnp/src/lib/comm/crypto/symmetric_ossl.cpp +--- a/comm/third_party/rnp/src/lib/crypto/symmetric_ossl.cpp ++++ b/comm/third_party/rnp/src/lib/crypto/symmetric_ossl.cpp +@@ -37,12 +37,14 @@ + + static const char * + pgp_sa_to_openssl_string(pgp_symm_alg_t alg) + { + switch (alg) { ++#if defined(ENABLE_IDEA) + case PGP_SA_IDEA: + return "idea-ecb"; ++#endif + case PGP_SA_TRIPLEDES: + return "des-ede3"; + case PGP_SA_CAST5: + return "cast5-ecb"; + case PGP_SA_BLOWFISH: +diff --git a/comm/third_party/rnp/src/lib/defaults.h b/third_party/rnp/src/lib/comm/defaults.h +--- a/comm/third_party/rnp/src/lib/defaults.h ++++ b/comm/third_party/rnp/src/lib/defaults.h +@@ -42,14 +42,14 @@ + /* Default compression algorithm and level */ + #define DEFAULT_Z_ALG "ZIP" + #define DEFAULT_Z_LEVEL 6 + + /* Default AEAD algorithm */ +-#define DEFAULT_AEAD_ALG "EAX" ++#define DEFAULT_AEAD_ALG PGP_AEAD_EAX + +-/* Default AEAD chunk bits, equals to 100MB chunks */ +-#define DEFAULT_AEAD_CHUNK_BITS 21 ++/* Default AEAD chunk bits, equals to 256K chunks */ ++#define DEFAULT_AEAD_CHUNK_BITS 12 + + /* Default cipher mode for secret key encryption */ + #define DEFAULT_CIPHER_MODE "CFB" + + /* Default cipher mode for secret key encryption */ +@@ -73,6 +73,9 @@ + #define MAX_PASSWORD_ATTEMPTS 3 + + /* Infinite password request attempts */ + #define INFINITE_ATTEMPTS -1 + ++/* Default key expiration in seconds, 2 years */ ++#define DEFAULT_KEY_EXPIRATION (2 * 365 * 24 * 60 * 60) ++ + #endif +diff --git a/comm/third_party/rnp/src/lib/ffi-priv-types.h b/third_party/rnp/src/lib/comm/ffi-priv-types.h +--- a/comm/third_party/rnp/src/lib/ffi-priv-types.h ++++ b/comm/third_party/rnp/src/lib/ffi-priv-types.h +@@ -87,14 +87,22 @@ struct rnp_ffi_st { + }; + + struct rnp_input_st { + /* either src or src_directory are valid, not both */ + pgp_source_t src; +- char * src_directory; ++ std::string src_directory; + rnp_input_reader_t *reader; + rnp_input_closer_t *closer; + void * app_ctx; ++ ++ rnp_input_st(); ++ rnp_input_st(const rnp_input_st &) = delete; ++ rnp_input_st(rnp_input_st &&) = delete; ++ ~rnp_input_st(); ++ ++ rnp_input_st &operator=(const rnp_input_st &) = delete; ++ rnp_input_st &operator=(rnp_input_st &&src); + }; + + struct rnp_output_st { + /* either dst or dst_directory are valid, not both */ + pgp_dest_t dst; +@@ -162,10 +170,12 @@ struct rnp_op_verify_st { + bool encrypted{}; + bool mdc{}; + bool validated{}; + pgp_aead_alg_t aead{}; + pgp_symm_alg_t salg{}; ++ bool ignore_sigs{}; ++ bool require_all_sigs{}; + /* recipient/symenc information */ + rnp_recipient_handle_t recipients{}; + size_t recipient_count{}; + rnp_recipient_handle_t used_recipient{}; + rnp_symenc_handle_t symencs{}; +diff --git a/comm/third_party/rnp/src/lib/fingerprint.cpp b/third_party/rnp/src/lib/comm/fingerprint.cpp +--- a/comm/third_party/rnp/src/lib/fingerprint.cpp ++++ b/comm/third_party/rnp/src/lib/fingerprint.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2018, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2017-2022, [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and +@@ -29,11 +29,11 @@ + * POSSIBILITY OF SUCH DAMAGE. + */ + + #include + #include "fingerprint.h" +-#include "crypto/hash.h" ++#include "crypto/hash.hpp" + #include + #include + #include + #include "utils.h" + +@@ -44,35 +44,35 @@ pgp_fingerprint(pgp_fingerprint_t &fp, c + if (!is_rsa_key_alg(key.alg)) { + RNP_LOG("bad algorithm"); + return RNP_ERROR_NOT_SUPPORTED; + } + try { +- rnp::Hash hash(PGP_HASH_MD5); +- hash.add(key.material.rsa.n); +- hash.add(key.material.rsa.e); +- fp.length = hash.finish(fp.fingerprint); ++ auto hash = rnp::Hash::create(PGP_HASH_MD5); ++ hash->add(key.material.rsa.n); ++ hash->add(key.material.rsa.e); ++ fp.length = hash->finish(fp.fingerprint); + return RNP_SUCCESS; + } catch (const std::exception &e) { + RNP_LOG("Failed to calculate v3 fingerprint: %s", e.what()); + return RNP_ERROR_BAD_STATE; + } + } + +- if (key.version == PGP_V4) { +- try { +- rnp::Hash hash(PGP_HASH_SHA1); +- signature_hash_key(key, hash); +- fp.length = hash.finish(fp.fingerprint); +- return RNP_SUCCESS; +- } catch (const std::exception &e) { +- RNP_LOG("Failed to calculate v4 fingerprint: %s", e.what()); +- return RNP_ERROR_BAD_STATE; +- } ++ if (key.version != PGP_V4) { ++ RNP_LOG("unsupported key version"); ++ return RNP_ERROR_NOT_SUPPORTED; + } + +- RNP_LOG("unsupported key version"); +- return RNP_ERROR_NOT_SUPPORTED; ++ try { ++ auto hash = rnp::Hash::create(PGP_HASH_SHA1); ++ signature_hash_key(key, *hash); ++ fp.length = hash->finish(fp.fingerprint); ++ return RNP_SUCCESS; ++ } catch (const std::exception &e) { ++ RNP_LOG("Failed to calculate v4 fingerprint: %s", e.what()); ++ return RNP_ERROR_BAD_STATE; ++ } + } + + /** + * \ingroup Core_Keys + * \brief Calculate the Key ID from the public key. +diff --git a/comm/third_party/rnp/src/lib/generate-key.cpp b/third_party/rnp/src/lib/comm/generate-key.cpp +--- a/comm/third_party/rnp/src/lib/generate-key.cpp ++++ b/comm/third_party/rnp/src/lib/generate-key.cpp +@@ -73,82 +73,51 @@ load_generated_g10_key(pgp_key_t * + pgp_key_pkt_t * newkey, + pgp_key_t * primary_key, + pgp_key_t * pubkey, + rnp::SecurityContext &ctx) + { +- bool ok = false; +- pgp_dest_t memdst = {}; +- pgp_source_t memsrc = {}; +- rnp_key_store_t * key_store = NULL; +- std::vector key_ptrs; /* holds primary and pubkey, when used */ +- pgp_key_provider_t prov = {}; +- + // this should generally be zeroed + assert(dst->type() == 0); + // if a primary is provided, make sure it's actually a primary key + assert(!primary_key || primary_key->is_primary()); + // if a pubkey is provided, make sure it's actually a public key + assert(!pubkey || pubkey->is_public()); + // G10 always needs pubkey here + assert(pubkey); + +- if (init_mem_dest(&memdst, NULL, 0)) { +- goto end; +- } +- +- if (!g10_write_seckey(&memdst, newkey, NULL, ctx.rng)) { +- RNP_LOG("failed to write generated seckey"); +- goto end; ++ // this would be better on the stack but the key store does not allow it ++ std::unique_ptr key_store(new (std::nothrow) rnp_key_store_t(ctx)); ++ if (!key_store) { ++ return false; + } +- +- // this would be better on the stack but the key store does not allow it +- try { +- key_store = new rnp_key_store_t(ctx); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- goto end; ++ /* Write g10 seckey */ ++ rnp::MemoryDest memdst(NULL, 0); ++ if (!g10_write_seckey(&memdst.dst(), newkey, NULL, ctx)) { ++ RNP_LOG("failed to write generated seckey"); ++ return false; + } + ++ std::vector key_ptrs; /* holds primary and pubkey, when used */ + // if this is a subkey, add the primary in first +- try { +- if (primary_key) { +- key_ptrs.push_back(primary_key); +- } +- // G10 needs the pubkey for copying some attributes (key version, creation time, etc) +- key_ptrs.push_back(pubkey); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- goto end; ++ if (primary_key) { ++ key_ptrs.push_back(primary_key); + } ++ // G10 needs the pubkey for copying some attributes (key version, creation time, etc) ++ key_ptrs.push_back(pubkey); + +- prov.callback = rnp_key_provider_key_ptr_list; +- prov.userdata = &key_ptrs; +- +- if (init_mem_src(&memsrc, mem_dest_get_memory(&memdst), memdst.writeb, false)) { +- goto end; ++ rnp::MemorySource memsrc(memdst.memory(), memdst.writeb(), false); ++ pgp_key_provider_t prov(rnp_key_provider_key_ptr_list, &key_ptrs); ++ if (!rnp_key_store_g10_from_src(key_store.get(), &memsrc.src(), &prov)) { ++ return false; + } +- +- if (!rnp_key_store_g10_from_src(key_store, &memsrc, &prov)) { +- goto end; +- } +- if (rnp_key_store_get_key_count(key_store) != 1) { +- goto end; ++ if (rnp_key_store_get_key_count(key_store.get()) != 1) { ++ return false; + } + // if a primary key is provided, it should match the sub with regards to type + assert(!primary_key || (primary_key->is_secret() == key_store->keys.front().is_secret())); +- try { +- *dst = pgp_key_t(key_store->keys.front()); +- ok = true; +- } catch (const std::exception &e) { +- RNP_LOG("Failed to copy key: %s", e.what()); +- ok = false; +- } +-end: +- delete key_store; +- src_close(&memsrc); +- dst_close(&memdst, true); +- return ok; ++ *dst = pgp_key_t(key_store->keys.front()); ++ return true; + } + + static uint8_t + pk_alg_default_flags(pgp_pubkey_alg_t alg) + { +@@ -316,17 +285,19 @@ keygen_primary_merge_defaults(rnp_keygen + + if (!desc.cert.key_flags) { + // set some default key flags if none are provided + desc.cert.key_flags = pk_alg_default_flags(desc.crypto.key_alg); + } +- if (desc.cert.userid[0] == '\0') { +- snprintf((char *) desc.cert.userid, +- sizeof(desc.cert.userid), ++ if (desc.cert.userid.empty()) { ++ char uid[MAX_ID_LENGTH] = {0}; ++ snprintf(uid, ++ sizeof(uid), + "%s %d-bit key <%s@localhost>", + id_str_pair::lookup(pubkey_alg_map, desc.crypto.key_alg), + get_numbits(&desc.crypto), + getenv_logname()); ++ desc.cert.userid = uid; + } + } + + bool + pgp_generate_primary_key(rnp_keygen_primary_desc_t &desc, +@@ -492,56 +463,5 @@ pgp_generate_subkey(rnp_keygen_subkey_de + } catch (const std::exception &e) { + RNP_LOG("Subkey generation failed: %s", e.what()); + return false; + } + } +- +-static void +-keygen_merge_defaults(rnp_keygen_primary_desc_t &primary_desc, +- rnp_keygen_subkey_desc_t & subkey_desc) +-{ +- if (!primary_desc.cert.key_flags && !subkey_desc.binding.key_flags) { +- // if no flags are set for either the primary key nor subkey, +- // we can set up some typical defaults here (these are validated +- // later against the alg capabilities) +- primary_desc.cert.key_flags = PGP_KF_SIGN | PGP_KF_CERTIFY; +- subkey_desc.binding.key_flags = PGP_KF_ENCRYPT; +- } +-} +- +-bool +-pgp_generate_keypair(rnp_keygen_primary_desc_t &primary_desc, +- rnp_keygen_subkey_desc_t & subkey_desc, +- bool merge_defaults, +- pgp_key_t & primary_sec, +- pgp_key_t & primary_pub, +- pgp_key_t & subkey_sec, +- pgp_key_t & subkey_pub, +- pgp_key_store_format_t secformat) +-{ +- // merge some defaults in, if requested +- if (merge_defaults) { +- keygen_merge_defaults(primary_desc, subkey_desc); +- } +- +- // generate the primary key +- if (!pgp_generate_primary_key( +- primary_desc, merge_defaults, primary_sec, primary_pub, secformat)) { +- RNP_LOG("failed to generate primary key"); +- return false; +- } +- +- // generate the subkey +- pgp_password_provider_t prov = {}; +- if (!pgp_generate_subkey(subkey_desc, +- merge_defaults, +- primary_sec, +- primary_pub, +- subkey_sec, +- subkey_pub, +- prov, +- secformat)) { +- RNP_LOG("failed to generate subkey"); +- return false; +- } +- return true; +-} +diff --git a/comm/third_party/rnp/src/lib/json-utils.h b/third_party/rnp/src/lib/comm/json-utils.h +--- a/comm/third_party/rnp/src/lib/json-utils.h ++++ b/comm/third_party/rnp/src/lib/json-utils.h +@@ -68,6 +68,24 @@ bool obj_add_hex_json(json_object *obj, + * @brief Add element to JSON array. + * Note: this function follows convention of the obj_add_field_json. + */ + bool array_add_element_json(json_object *obj, json_object *val); + ++namespace rnp { ++class JSONObject { ++ json_object *obj_; ++ ++ public: ++ JSONObject(json_object *obj) : obj_(obj) ++ { ++ } ++ ++ ~JSONObject() ++ { ++ if (obj_) { ++ json_object_put(obj_); ++ } ++ } ++}; ++} // namespace rnp ++ + #endif +diff --git a/comm/third_party/rnp/src/lib/key-provider.h b/third_party/rnp/src/lib/comm/key-provider.h +--- a/comm/third_party/rnp/src/lib/key-provider.h ++++ b/comm/third_party/rnp/src/lib/key-provider.h +@@ -45,23 +45,36 @@ typedef struct pgp_key_search_t { + pgp_key_id_t keyid; + pgp_key_grip_t grip; + pgp_fingerprint_t fingerprint; + char userid[MAX_ID_LENGTH + 1]; + } by; ++ ++ pgp_key_search_t(pgp_key_search_type_t atype = PGP_KEY_SEARCH_UNKNOWN) : type(atype){}; + } pgp_key_search_t; + + typedef struct pgp_key_request_ctx_t { + pgp_op_t op; + bool secret; + pgp_key_search_t search; ++ ++ pgp_key_request_ctx_t(pgp_op_t anop = PGP_OP_UNKNOWN, ++ bool sec = false, ++ pgp_key_search_type_t tp = PGP_KEY_SEARCH_UNKNOWN) ++ : op(anop), secret(sec) ++ { ++ search.type = tp; ++ } + } pgp_key_request_ctx_t; + + typedef pgp_key_t *pgp_key_callback_t(const pgp_key_request_ctx_t *ctx, void *userdata); + + typedef struct pgp_key_provider_t { + pgp_key_callback_t *callback; + void * userdata; ++ ++ pgp_key_provider_t(pgp_key_callback_t *cb = NULL, void *ud = NULL) ++ : callback(cb), userdata(ud){}; + } pgp_key_provider_t; + + /** checks if a key matches search criteria + * + * Note that this does not do any check on the type of key (public/secret), +diff --git a/comm/third_party/rnp/src/lib/logging.cpp b/third_party/rnp/src/lib/comm/logging.cpp +--- a/comm/third_party/rnp/src/lib/logging.cpp ++++ b/comm/third_party/rnp/src/lib/logging.cpp +@@ -37,10 +37,13 @@ static int8_t _rnp_log_switch = + #else + 1 // always on in debug build + #endif + ; + ++/* Temporary disable logging */ ++static size_t _rnp_log_disable = 0; ++ + void + set_rnp_log_switch(int8_t value) + { + _rnp_log_switch = value; + } +@@ -50,7 +53,23 @@ rnp_log_switch() + { + if (_rnp_log_switch < 0) { + const char *var = getenv(RNP_LOG_CONSOLE); + _rnp_log_switch = (var && strcmp(var, "0")) ? 1 : 0; + } +- return !!_rnp_log_switch; ++ return !_rnp_log_disable && !!_rnp_log_switch; + } ++ ++void ++rnp_log_stop() ++{ ++ if (_rnp_log_disable < SIZE_MAX) { ++ _rnp_log_disable++; ++ } ++} ++ ++void ++rnp_log_continue() ++{ ++ if (_rnp_log_disable) { ++ _rnp_log_disable--; ++ } ++} +diff --git a/comm/third_party/rnp/src/lib/logging.h b/third_party/rnp/src/lib/comm/logging.h +--- a/comm/third_party/rnp/src/lib/logging.h ++++ b/comm/third_party/rnp/src/lib/logging.h +@@ -33,10 +33,26 @@ + /* environment variable name */ + static const char RNP_LOG_CONSOLE[] = "RNP_LOG_CONSOLE"; + + bool rnp_log_switch(); + void set_rnp_log_switch(int8_t); ++void rnp_log_stop(); ++void rnp_log_continue(); ++ ++namespace rnp { ++class LogStop { ++ public: ++ LogStop() ++ { ++ rnp_log_stop(); ++ } ++ ~LogStop() ++ { ++ rnp_log_continue(); ++ } ++}; ++} // namespace rnp + + #define RNP_LOG_FD(fd, ...) \ + do { \ + if (!rnp_log_switch()) \ + break; \ +diff --git a/comm/third_party/rnp/src/lib/pass-provider.h b/third_party/rnp/src/lib/comm/pass-provider.h +--- a/comm/third_party/rnp/src/lib/pass-provider.h ++++ b/comm/third_party/rnp/src/lib/pass-provider.h +@@ -32,20 +32,24 @@ + typedef struct pgp_key_t pgp_key_t; + + typedef struct pgp_password_ctx_t { + uint8_t op; + const pgp_key_t *key; ++ ++ pgp_password_ctx_t(uint8_t anop, const pgp_key_t *akey = NULL) : op(anop), key(akey){}; + } pgp_password_ctx_t; + + typedef bool pgp_password_callback_t(const pgp_password_ctx_t *ctx, + char * password, + size_t password_size, + void * userdata); + + typedef struct pgp_password_provider_t { + pgp_password_callback_t *callback; + void * userdata; ++ pgp_password_provider_t(pgp_password_callback_t *cb = NULL, void *ud = NULL) ++ : callback(cb), userdata(ud){}; + } pgp_password_provider_t; + + bool pgp_request_password(const pgp_password_provider_t *provider, + const pgp_password_ctx_t * ctx, + char * password, +diff --git a/comm/third_party/rnp/src/lib/pgp-key.cpp b/third_party/rnp/src/lib/comm/pgp-key.cpp +--- a/comm/third_party/rnp/src/lib/pgp-key.cpp ++++ b/comm/third_party/rnp/src/lib/pgp-key.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and +@@ -76,35 +76,21 @@ + pgp_key_pkt_t * + pgp_decrypt_seckey_pgp(const pgp_rawpacket_t &raw, + const pgp_key_pkt_t & pubkey, + const char * password) + { +- pgp_source_t src = {0}; +- pgp_key_pkt_t *res = NULL; +- +- if (init_mem_src(&src, raw.raw.data(), raw.raw.size(), false)) { +- return NULL; +- } + try { +- res = new pgp_key_pkt_t(); +- if (res->parse(src)) { +- goto error; ++ rnp::MemorySource src(raw.raw.data(), raw.raw.size(), false); ++ auto res = std::unique_ptr(new pgp_key_pkt_t()); ++ if (res->parse(src.src()) || decrypt_secret_key(res.get(), password)) { ++ return NULL; + } ++ return res.release(); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); +- goto error; +- } +- if (decrypt_secret_key(res, password)) { +- goto error; ++ return NULL; + } +- +- src_close(&src); +- return res; +-error: +- src_close(&src); +- delete res; +- return NULL; + } + + /* Note that this function essentially serves two purposes. + * - In the case of a protected key, it requests a password and + * uses it to decrypt the key and fill in key->key.seckey. +@@ -141,32 +127,27 @@ pgp_decrypt_seckey(const pgp_key_t & + } + + pgp_key_t * + pgp_sig_get_signer(const pgp_subsig_t &sig, rnp_key_store_t *keyring, pgp_key_provider_t *prov) + { +- pgp_key_request_ctx_t ctx = {}; ++ pgp_key_request_ctx_t ctx(PGP_OP_VERIFY, false, PGP_KEY_SEARCH_UNKNOWN); + /* if we have fingerprint let's check it */ + if (sig.sig.has_keyfp()) { + ctx.search.by.fingerprint = sig.sig.keyfp(); + ctx.search.type = PGP_KEY_SEARCH_FINGERPRINT; +- } +- if ((ctx.search.type == PGP_KEY_SEARCH_UNKNOWN) && sig.sig.has_keyid()) { ++ } else if (sig.sig.has_keyid()) { + ctx.search.by.keyid = sig.sig.keyid(); + ctx.search.type = PGP_KEY_SEARCH_KEYID; +- } +- if (ctx.search.type == PGP_KEY_SEARCH_UNKNOWN) { ++ } else { + RNP_LOG("No way to search for the signer."); + return NULL; + } + + pgp_key_t *key = rnp_key_store_search(keyring, &ctx.search, NULL); + if (key || !prov) { + return key; + } +- +- ctx.op = PGP_OP_VERIFY; +- ctx.secret = false; + return pgp_request_key(prov, &ctx); + } + + static const id_str_pair ss_rr_code_map[] = { + {PGP_REVOCATION_NO_REASON, "No reason specified"}, +@@ -248,62 +229,58 @@ done: + seckey.tag = oldtag; + return res; + } + + bool +-pgp_key_t::write_sec_rawpkt(pgp_key_pkt_t &seckey, const std::string &password, rnp::RNG &rng) ++pgp_key_t::write_sec_rawpkt(pgp_key_pkt_t & seckey, ++ const std::string & password, ++ rnp::SecurityContext &ctx) + { +- pgp_dest_t memdst = {}; +- if (init_mem_dest(&memdst, NULL, 0)) { +- return false; +- } +- +- bool ret = false; + // encrypt+write the key in the appropriate format + try { ++ rnp::MemoryDest memdst; + switch (format) { + case PGP_KEY_STORE_GPG: + case PGP_KEY_STORE_KBX: +- if (!write_sec_pgp(memdst, seckey, password, rng)) { ++ if (!write_sec_pgp(memdst.dst(), seckey, password, ctx.rng)) { + RNP_LOG("failed to write secret key"); +- goto done; ++ return false; + } + break; + case PGP_KEY_STORE_G10: +- if (!g10_write_seckey(&memdst, &seckey, password.c_str(), rng)) { ++ if (!g10_write_seckey(&memdst.dst(), &seckey, password.c_str(), ctx)) { + RNP_LOG("failed to write g10 secret key"); +- goto done; ++ return false; + } + break; + default: + RNP_LOG("invalid format"); +- goto done; ++ return false; + } + +- uint8_t *mem = (uint8_t *) mem_dest_get_memory(&memdst); +- rawpkt_ = pgp_rawpacket_t(mem, memdst.writeb, type()); ++ rawpkt_ = pgp_rawpacket_t((uint8_t *) memdst.memory(), memdst.writeb(), type()); ++ return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); +- goto done; ++ return false; + } +- ret = true; +-done: +- dst_close(&memdst, true); +- return ret; + } + + static bool +-update_sig_expiration(pgp_signature_t *dst, const pgp_signature_t *src, uint32_t expiry) ++update_sig_expiration(pgp_signature_t * dst, ++ const pgp_signature_t *src, ++ uint64_t create, ++ uint32_t expiry) + { + try { + *dst = *src; + if (!expiry) { + dst->remove_subpkt(dst->get_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY)); + } else { + dst->set_key_expiration(expiry); + } +- dst->set_creation(time(NULL)); ++ dst->set_creation(create); + return true; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } +@@ -353,11 +330,11 @@ pgp_key_set_expiration(pgp_key_t * + return false; + } + + pgp_signature_t newsig; + pgp_sig_id_t oldsigid = sigid; +- if (!update_sig_expiration(&newsig, &sig.sig, expiry)) { ++ if (!update_sig_expiration(&newsig, &sig.sig, ctx.time(), expiry)) { + return false; + } + try { + if (sig.is_cert()) { + if (sig.uid >= key->uid_count()) { +@@ -430,11 +407,11 @@ pgp_subkey_set_expiration(pgp_key_t * + + try { + /* update signature and re-sign */ + pgp_signature_t newsig; + pgp_sig_id_t oldsigid = subsig->sigid; +- if (!update_sig_expiration(&newsig, &subsig->sig, expiry)) { ++ if (!update_sig_expiration(&newsig, &subsig->sig, ctx.time(), expiry)) { + return false; + } + primsec->sign_subkey_binding(*secsub, newsig, ctx); + /* replace signature, first for the secret key since it may be replaced in public */ + if (secsub->has_sig(oldsigid)) { +@@ -466,12 +443,11 @@ find_suitable_key(pgp_op_t op + return NULL; + } + if (!no_primary && key->valid() && (key->flags() & desired_usage)) { + return key; + } +- pgp_key_request_ctx_t ctx{.op = op, .secret = key->is_secret()}; +- ctx.search.type = PGP_KEY_SEARCH_FINGERPRINT; ++ pgp_key_request_ctx_t ctx(op, key->is_secret(), PGP_KEY_SEARCH_FINGERPRINT); + + pgp_key_t *subkey = NULL; + for (auto &fp : key->subkey_fps()) { + ctx.search.by.fingerprint = fp; + pgp_key_t *cur = pgp_request_key(key_provider, &ctx); +@@ -504,24 +480,10 @@ pgp_hash_adjust_alg_to_key(pgp_hash_alg_ + } + return hash; + } + + static void +-mem_dest_to_vector(pgp_dest_t *dst, std::vector &vec) +-{ +- uint8_t *mem = (uint8_t *) mem_dest_get_memory(dst); +- try { +- vec = std::vector(mem, mem + dst->writeb); +- dst_close(dst, true); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- dst_close(dst, true); +- throw; +- } +-} +- +-static void + bytevec_append_uniq(std::vector &vec, uint8_t val) + { + if (std::find(vec.begin(), vec.end(), val) == vec.end()) { + vec.push_back(val); + } +@@ -575,57 +537,29 @@ pgp_user_prefs_t::add_ks_pref(pgp_key_se + bytevec_append_uniq(ks_prefs, pref); + } + + pgp_rawpacket_t::pgp_rawpacket_t(const pgp_signature_t &sig) + { +- pgp_dest_t dst = {}; +- +- if (init_mem_dest(&dst, NULL, 0)) { +- throw std::bad_alloc(); +- } +- +- try { +- sig.write(dst); +- } catch (const std::exception &e) { +- dst_close(&dst, true); +- throw; +- } +- mem_dest_to_vector(&dst, raw); ++ rnp::MemoryDest dst; ++ sig.write(dst.dst()); ++ raw = dst.to_vector(); + tag = PGP_PKT_SIGNATURE; + } + + pgp_rawpacket_t::pgp_rawpacket_t(pgp_key_pkt_t &key) + { +- pgp_dest_t dst = {}; +- +- if (init_mem_dest(&dst, NULL, 0)) { +- throw std::bad_alloc(); +- } +- try { +- key.write(dst); +- } catch (const std::exception &e) { +- dst_close(&dst, true); +- throw; +- } +- mem_dest_to_vector(&dst, raw); ++ rnp::MemoryDest dst; ++ key.write(dst.dst()); ++ raw = dst.to_vector(); + tag = key.tag; + } + + pgp_rawpacket_t::pgp_rawpacket_t(const pgp_userid_pkt_t &uid) + { +- pgp_dest_t dst = {}; +- +- if (init_mem_dest(&dst, NULL, 0)) { +- throw std::bad_alloc(); +- } +- try { +- uid.write(dst); +- } catch (const std::exception &e) { +- dst_close(&dst, true); +- throw; +- } +- mem_dest_to_vector(&dst, raw); ++ rnp::MemoryDest dst; ++ uid.write(dst.dst()); ++ raw = dst.to_vector(); + tag = uid.tag; + } + + void + pgp_rawpacket_t::write(pgp_dest_t &dst) const +@@ -693,19 +627,18 @@ pgp_subsig_t::is_cert() const + return (type == PGP_CERT_CASUAL) || (type == PGP_CERT_GENERIC) || + (type == PGP_CERT_PERSONA) || (type == PGP_CERT_POSITIVE); + } + + bool +-pgp_subsig_t::expired() const ++pgp_subsig_t::expired(uint64_t at) const + { + /* sig expiration: absence of subpkt or 0 means it never expires */ + uint64_t expiration = sig.expiration(); + if (!expiration) { + return false; + } +- uint64_t now = time(NULL); +- return expiration + sig.creation() < now; ++ return expiration + sig.creation() < at; + } + + pgp_userid_t::pgp_userid_t(const pgp_userid_pkt_t &uidpkt) + { + /* copy packet data */ +@@ -1499,11 +1432,11 @@ pgp_key_t::unlock(const pgp_password_pro + // see if it's already unlocked + if (!is_locked()) { + return true; + } + +- pgp_password_ctx_t ctx = {.op = (uint8_t) op, .key = this}; ++ pgp_password_ctx_t ctx(op, this); + pgp_key_pkt_t * decrypted_seckey = pgp_decrypt_seckey(*this, provider, ctx); + if (!decrypted_seckey) { + return false; + } + +@@ -1535,30 +1468,27 @@ pgp_key_t::lock() + } + + bool + pgp_key_t::protect(const rnp_key_protection_params_t &protection, + const pgp_password_provider_t & password_provider, +- rnp::RNG & rng) ++ rnp::SecurityContext & sctx) + { +- pgp_password_ctx_t ctx; +- memset(&ctx, 0, sizeof(ctx)); +- ctx.op = PGP_OP_PROTECT; +- ctx.key = this; ++ pgp_password_ctx_t ctx(PGP_OP_PROTECT, this); + + // ask the provider for a password + rnp::secure_array password; + if (!pgp_request_password(&password_provider, &ctx, password.data(), password.size())) { + return false; + } +- return protect(pkt_, protection, password.data(), rng); ++ return protect(pkt_, protection, password.data(), sctx); + } + + bool + pgp_key_t::protect(pgp_key_pkt_t & decrypted, + const rnp_key_protection_params_t &protection, + const std::string & new_password, +- rnp::RNG & rng) ++ rnp::SecurityContext & ctx) + { + if (!is_secret()) { + RNP_LOG("Warning: this is not a secret key"); + return false; + } +@@ -1578,25 +1508,25 @@ pgp_key_t::protect(pgp_key_pkt_t & + protection.cipher_mode ? protection.cipher_mode : DEFAULT_PGP_CIPHER_MODE; + pkt_.sec_protection.s2k.hash_alg = + protection.hash_alg ? protection.hash_alg : DEFAULT_PGP_HASH_ALG; + auto iter = protection.iterations; + if (!iter) { +- iter = pgp_s2k_compute_iters( +- pkt_.sec_protection.s2k.hash_alg, DEFAULT_S2K_MSEC, DEFAULT_S2K_TUNE_MSEC); ++ iter = ctx.s2k_iterations(pkt_.sec_protection.s2k.hash_alg); + } + pkt_.sec_protection.s2k.iterations = pgp_s2k_round_iterations(iter); + if (!ownpkt) { + /* decrypted is assumed to be temporary variable so we may modify it */ + decrypted.sec_protection = pkt_.sec_protection; + } + + /* write the protected key to raw packet */ +- return write_sec_rawpkt(decrypted, new_password, rng); ++ return write_sec_rawpkt(decrypted, new_password, ctx); + } + + bool +-pgp_key_t::unprotect(const pgp_password_provider_t &password_provider, rnp::RNG &rng) ++pgp_key_t::unprotect(const pgp_password_provider_t &password_provider, ++ rnp::SecurityContext & secctx) + { + /* sanity check */ + if (!is_secret()) { + RNP_LOG("Warning: this is not a secret key"); + return false; +@@ -1606,24 +1536,21 @@ pgp_key_t::unprotect(const pgp_password_ + return true; + } + /* simple case */ + if (!encrypted()) { + pkt_.sec_protection.s2k.usage = PGP_S2KU_NONE; +- return write_sec_rawpkt(pkt_, "", rng); ++ return write_sec_rawpkt(pkt_, "", secctx); + } + +- pgp_password_ctx_t ctx; +- memset(&ctx, 0, sizeof(ctx)); +- ctx.op = PGP_OP_UNPROTECT; +- ctx.key = this; ++ pgp_password_ctx_t ctx(PGP_OP_UNPROTECT, this); + + pgp_key_pkt_t *decrypted_seckey = pgp_decrypt_seckey(*this, password_provider, ctx); + if (!decrypted_seckey) { + return false; + } + decrypted_seckey->sec_protection.s2k.usage = PGP_S2KU_NONE; +- if (!write_sec_rawpkt(*decrypted_seckey, "", rng)) { ++ if (!write_sec_rawpkt(*decrypted_seckey, "", secctx)) { + delete decrypted_seckey; + return false; + } + pkt_ = std::move(*decrypted_seckey); + /* current logic is that unprotected key should be additionally unlocked */ +@@ -1698,41 +1625,35 @@ pgp_key_t::write_autocrypt(pgp_dest_t &d + pgp_subsig_t *binding = sub.latest_binding(); + if (!binding) { + RNP_LOG("No valid binding for subkey"); + return false; + } +- /* write all or nothing */ +- pgp_dest_t memdst = {}; +- if (init_mem_dest(&memdst, NULL, 0)) { +- RNP_LOG("Allocation failed"); +- return false; +- } + +- bool res = false; + try { ++ /* write all or nothing */ ++ rnp::MemoryDest memdst; + if (is_secret()) { + pgp_key_pkt_t pkt(pkt_, true); +- pkt.write(memdst); ++ pkt.write(memdst.dst()); + } else { +- pkt().write(memdst); ++ pkt().write(memdst.dst()); + } +- get_uid(uid).pkt.write(memdst); +- cert->sig.write(memdst); ++ get_uid(uid).pkt.write(memdst.dst()); ++ cert->sig.write(memdst.dst()); + if (sub.is_secret()) { + pgp_key_pkt_t pkt(sub.pkt(), true); +- pkt.write(memdst); ++ pkt.write(memdst.dst()); + } else { +- sub.pkt().write(memdst); ++ sub.pkt().write(memdst.dst()); + } +- binding->sig.write(memdst); +- dst_write(&dst, mem_dest_get_memory(&memdst), memdst.writeb); +- res = !dst.werr; ++ binding->sig.write(memdst.dst()); ++ dst_write(&dst, memdst.memory(), memdst.writeb()); ++ return !dst.werr; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); ++ return false; + } +- dst_close(&memdst, true); +- return res; + } + + /* look only for primary userids */ + #define PGP_UID_PRIMARY ((uint32_t) -2) + /* look for any uid, except PGP_UID_NONE) */ +@@ -1852,19 +1773,18 @@ pgp_key_t::is_signer(const pgp_subsig_t + } + return keyid() == sig.sig.keyid(); + } + + bool +-pgp_key_t::expired_with(const pgp_subsig_t &sig) const ++pgp_key_t::expired_with(const pgp_subsig_t &sig, uint64_t at) const + { + /* key expiration: absence of subpkt or 0 means it never expires */ + uint64_t expiration = sig.sig.key_expiration(); + if (!expiration) { + return false; + } +- uint64_t now = time(NULL); +- return expiration + creation() < now; ++ return expiration + creation() < at; + } + + bool + pgp_key_t::is_self_cert(const pgp_subsig_t &sig) const + { +@@ -1982,11 +1902,11 @@ pgp_key_t::validate_sig(pgp_signature_in + sinfo.valid = false; + RNP_LOG("invalid or untrusted key"); + } + + /* Check signature's expiration time */ +- uint32_t now = time(NULL); ++ uint32_t now = ctx.time(); + uint32_t create = sinfo.sig->creation(); + uint32_t expiry = sinfo.sig->expiration(); + if (create > now) { + /* signature created later then now */ + RNP_LOG("signature created %d seconds in future", (int) (create - now)); +@@ -2032,23 +1952,21 @@ void + pgp_key_t::validate_cert(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & key, + const pgp_userid_pkt_t & uid, + const rnp::SecurityContext &ctx) const + { +- rnp::Hash hash; +- signature_hash_certification(*sinfo.sig, key, uid, hash); +- validate_sig(sinfo, hash, ctx); ++ auto hash = signature_hash_certification(*sinfo.sig, key, uid); ++ validate_sig(sinfo, *hash, ctx); + } + + void + pgp_key_t::validate_binding(pgp_signature_info_t & sinfo, + const pgp_key_t & subkey, + const rnp::SecurityContext &ctx) const + { +- rnp::Hash hash; +- signature_hash_binding(*sinfo.sig, pkt(), subkey.pkt(), hash); +- validate_sig(sinfo, hash, ctx); ++ auto hash = signature_hash_binding(*sinfo.sig, pkt(), subkey.pkt()); ++ validate_sig(sinfo, *hash, ctx); + if (!sinfo.valid || !(sinfo.sig->key_flags() & PGP_KF_SIGN)) { + return; + } + + /* check primary key binding signature if any */ +@@ -2069,35 +1987,33 @@ pgp_key_t::validate_binding(pgp_signatur + if (subpkt->fields.sig->version < PGP_V4) { + RNP_LOG("invalid primary key binding signature version"); + return; + } + +- signature_hash_binding(*subpkt->fields.sig, pkt(), subkey.pkt(), hash); ++ hash = signature_hash_binding(*subpkt->fields.sig, pkt(), subkey.pkt()); + pgp_signature_info_t bindinfo = {}; + bindinfo.sig = subpkt->fields.sig; + bindinfo.signer_valid = true; + bindinfo.ignore_expiry = true; +- subkey.validate_sig(bindinfo, hash, ctx); ++ subkey.validate_sig(bindinfo, *hash, ctx); + sinfo.valid = bindinfo.valid && !bindinfo.expired; + } + + void + pgp_key_t::validate_sub_rev(pgp_signature_info_t & sinfo, + const pgp_key_pkt_t & subkey, + const rnp::SecurityContext &ctx) const + { +- rnp::Hash hash; +- signature_hash_binding(*sinfo.sig, pkt(), subkey, hash); +- validate_sig(sinfo, hash, ctx); ++ auto hash = signature_hash_binding(*sinfo.sig, pkt(), subkey); ++ validate_sig(sinfo, *hash, ctx); + } + + void + pgp_key_t::validate_direct(pgp_signature_info_t &sinfo, const rnp::SecurityContext &ctx) const + { +- rnp::Hash hash; +- signature_hash_direct(*sinfo.sig, pkt(), hash); +- validate_sig(sinfo, hash, ctx); ++ auto hash = signature_hash_direct(*sinfo.sig, pkt()); ++ validate_sig(sinfo, *hash, ctx); + } + + void + pgp_key_t::validate_self_signatures(const rnp::SecurityContext &ctx) + { +@@ -2150,25 +2066,26 @@ pgp_key_t::validate_primary(rnp_key_stor + if (is_revocation(sig)) { + return; + } + } + /* if we have direct-key signature, then it has higher priority for expiration check */ ++ uint64_t now = keyring.secctx.time(); + pgp_subsig_t *dirsig = latest_selfsig(PGP_UID_NONE); + if (dirsig) { +- has_expired = expired_with(*dirsig); ++ has_expired = expired_with(*dirsig, now); + has_cert = !has_expired; + } + /* if we have primary uid and it is more restrictive, then use it as well */ + pgp_subsig_t *prisig = NULL; + if (!has_expired && (prisig = latest_selfsig(PGP_UID_PRIMARY))) { +- has_expired = expired_with(*prisig); ++ has_expired = expired_with(*prisig, now); + has_cert = !has_expired; + } + /* if we don't have direct-key sig and primary uid, use the latest self-cert */ + pgp_subsig_t *latest = NULL; + if (!dirsig && !prisig && (latest = latest_selfsig(PGP_UID_ANY))) { +- has_expired = expired_with(*latest); ++ has_expired = expired_with(*latest, now); + has_cert = !has_expired; + } + + /* we have at least one non-expiring key self-signature */ + if (has_cert) { +@@ -2191,11 +2108,11 @@ pgp_key_t::validate_primary(rnp_key_stor + pgp_subsig_t *sig = sub->latest_binding(); + if (!sig) { + continue; + } + /* check whether subkey is expired - then do not mark key as valid */ +- if (sub->expired_with(*sig)) { ++ if (sub->expired_with(*sig, now)) { + continue; + } + validity_.valid = true; + return; + } +@@ -2222,11 +2139,11 @@ pgp_key_t::validate_subkey(pgp_key_t *pr + continue; + } + + if (is_binding(sig) && !has_binding) { + /* check whether subkey is expired */ +- if (expired_with(sig)) { ++ if (expired_with(sig, ctx.time())) { + has_expired = true; + continue; + } + has_binding = true; + } else if (is_revocation(sig)) { +@@ -2291,66 +2208,60 @@ pgp_key_t::mark_valid() + get_sig(i).validity.mark_valid(); + } + } + + void +-pgp_key_t::sign_init(pgp_signature_t &sig, pgp_hash_alg_t hash) const ++pgp_key_t::sign_init(pgp_signature_t &sig, pgp_hash_alg_t hash, uint64_t creation) const + { + sig.version = PGP_V4; + sig.halg = pgp_hash_adjust_alg_to_key(hash, &pkt_); + sig.palg = alg(); + sig.set_keyfp(fp()); +- sig.set_creation(time(NULL)); ++ sig.set_creation(creation); + sig.set_keyid(keyid()); + } + + void + pgp_key_t::sign_cert(const pgp_key_pkt_t & key, + const pgp_userid_pkt_t &uid, + pgp_signature_t & sig, + rnp::SecurityContext & ctx) + { +- rnp::Hash hash; + sig.fill_hashed_data(); +- signature_hash_certification(sig, key, uid, hash); +- signature_calculate(sig, pkt_.material, hash, ctx); ++ auto hash = signature_hash_certification(sig, key, uid); ++ signature_calculate(sig, pkt_.material, *hash, ctx); + } + + void + pgp_key_t::sign_direct(const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx) + { +- rnp::Hash hash; + sig.fill_hashed_data(); +- signature_hash_direct(sig, key, hash); +- signature_calculate(sig, pkt_.material, hash, ctx); ++ auto hash = signature_hash_direct(sig, key); ++ signature_calculate(sig, pkt_.material, *hash, ctx); + } + + void + pgp_key_t::sign_binding(const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx) + { +- rnp::Hash hash; + sig.fill_hashed_data(); +- if (is_primary()) { +- signature_hash_binding(sig, pkt(), key, hash); +- } else { +- signature_hash_binding(sig, key, pkt(), hash); +- } +- signature_calculate(sig, pkt_.material, hash, ctx); ++ auto hash = is_primary() ? signature_hash_binding(sig, pkt(), key) : ++ signature_hash_binding(sig, key, pkt()); ++ signature_calculate(sig, pkt_.material, *hash, ctx); + } + + void + pgp_key_t::gen_revocation(const pgp_revoke_t & revoke, + pgp_hash_alg_t hash, + const pgp_key_pkt_t & key, + pgp_signature_t & sig, + rnp::SecurityContext &ctx) + { +- sign_init(sig, hash); ++ sign_init(sig, hash, ctx.time()); + sig.set_type(is_primary_key_pkt(key.tag) ? PGP_SIG_REV_KEY : PGP_SIG_REV_SUBKEY); + sig.set_revocation_reason(revoke.code, revoke.reason); + + if (is_primary_key_pkt(key.tag)) { + sign_direct(key, sig, ctx); +@@ -2370,11 +2281,11 @@ pgp_key_t::sign_subkey_binding(pgp_key_t + } + sign_binding(sub.pkt(), sig, ctx); + /* add primary key binding subpacket if requested */ + if (subsign) { + pgp_signature_t embsig; +- sub.sign_init(embsig, sig.halg); ++ sub.sign_init(embsig, sig.halg, ctx.time()); + embsig.set_type(PGP_SIG_PRIMARY); + sub.sign_binding(pkt(), embsig, ctx); + sig.set_embedded_sig(embsig); + } + } +@@ -2383,22 +2294,22 @@ void + pgp_key_t::add_uid_cert(rnp_selfsig_cert_info_t &cert, + pgp_hash_alg_t hash, + rnp::SecurityContext & ctx, + pgp_key_t * pubkey) + { +- if (!cert.userid[0]) { ++ if (cert.userid.empty()) { + /* todo: why not to allow empty uid? */ + RNP_LOG("wrong parameters"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + // userids are only valid for primary keys, not subkeys + if (!is_primary()) { + RNP_LOG("cannot add a userid to a subkey"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + // see if the key already has this userid +- if (has_uid((const char *) cert.userid)) { ++ if (has_uid(cert.userid)) { + RNP_LOG("key already has this userid"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + // this isn't really valid for this format + if (format == PGP_KEY_STORE_G10) { +@@ -2417,11 +2328,11 @@ pgp_key_t::add_uid_cert(rnp_selfsig_cert + } + + /* Fill the transferable userid */ + pgp_userid_pkt_t uid; + pgp_signature_t sig; +- sign_init(sig, hash); ++ sign_init(sig, hash, ctx.time()); + cert.populate(uid, sig); + try { + sign_cert(pkt_, uid, sig, ctx); + } catch (const std::exception &e) { + RNP_LOG("Failed to certify: %s", e.what()); +@@ -2451,11 +2362,11 @@ pgp_key_t::add_sub_binding(pgp_key_t & + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + + /* populate signature */ + pgp_signature_t sig; +- sign_init(sig, hash); ++ sign_init(sig, hash, ctx.time()); + sig.set_type(PGP_SIG_SUBKEY); + if (binding.key_expiration) { + sig.set_key_expiration(binding.key_expiration); + } + if (binding.key_flags) { +@@ -2547,11 +2458,11 @@ pgp_key_t::refresh_data(const rnp::Secur + get_uid(i).valid = false; + } + for (size_t i = 0; i < sig_count(); i++) { + pgp_subsig_t &sig = get_sig(i); + /* consider userid as valid if it has at least one non-expired self-sig */ +- if (!sig.valid() || !sig.is_cert() || !is_signer(sig) || sig.expired()) { ++ if (!sig.valid() || !sig.is_cert() || !is_signer(sig) || sig.expired(ctx.time())) { + continue; + } + if (sig.uid >= uid_count()) { + continue; + } +@@ -2562,11 +2473,11 @@ pgp_key_t::refresh_data(const rnp::Secur + pgp_userid_t &uid = get_uid(i); + if (uid.revoked) { + uid.valid = false; + } + } +- /* primary userid: use latest one which is not overriden by later non-primary selfsig */ ++ /* primary userid: use latest one which is not overridden by later non-primary selfsig */ + uid0_set_ = false; + if (prisig && get_uid(prisig->uid).valid) { + uid0_ = prisig->uid; + uid0_set_ = true; + } +diff --git a/comm/third_party/rnp/src/lib/pgp-key.h b/third_party/rnp/src/lib/comm/pgp-key.h +--- a/comm/third_party/rnp/src/lib/pgp-key.h ++++ b/comm/third_party/rnp/src/lib/pgp-key.h +@@ -98,11 +98,11 @@ typedef struct pgp_subsig_t { + bool validated() const; + bool valid() const; + /** @brief Returns true if signature is certification */ + bool is_cert() const; + /** @brief Returns true if signature is expired */ +- bool expired() const; ++ bool expired(uint64_t at) const; + } pgp_subsig_t; + + typedef std::unordered_map pgp_sig_map_t; + + /* userid, built on top of userid packet structure */ +@@ -276,11 +276,13 @@ struct pgp_key_t { + size_t rawpkt_count() const; + pgp_rawpacket_t & rawpkt(); + const pgp_rawpacket_t &rawpkt() const; + void set_rawpkt(const pgp_rawpacket_t &src); + /** @brief write secret key data to the rawpkt, optionally encrypting with password */ +- bool write_sec_rawpkt(pgp_key_pkt_t &seckey, const std::string &password, rnp::RNG &rng); ++ bool write_sec_rawpkt(pgp_key_pkt_t & seckey, ++ const std::string & password, ++ rnp::SecurityContext &ctx); + + /** @brief Unlock a key, i.e. decrypt its secret data so it can be used for + * signing/decryption. + * Note: Key locking does not apply to unprotected keys. + * +@@ -298,18 +300,19 @@ struct pgp_key_t { + bool lock(); + /** @brief Add protection to an unlocked key, i.e. encrypt its secret data with specified + * parameters. */ + bool protect(const rnp_key_protection_params_t &protection, + const pgp_password_provider_t & password_provider, +- rnp::RNG & rng); ++ rnp::SecurityContext & ctx); + /** @brief Add/change protection of a key */ + bool protect(pgp_key_pkt_t & decrypted, + const rnp_key_protection_params_t &protection, + const std::string & new_password, +- rnp::RNG & rng); ++ rnp::SecurityContext & ctx); + /** @brief Remove protection from a key, i.e. leave secret fields unencrypted */ +- bool unprotect(const pgp_password_provider_t &password_provider, rnp::RNG &rng); ++ bool unprotect(const pgp_password_provider_t &password_provider, ++ rnp::SecurityContext & ctx); + + /** @brief Write key's packets to the output. */ + void write(pgp_dest_t &dst) const; + /** + * @brief Write OpenPGP key packets (including subkeys) to the specified stream +@@ -353,11 +356,11 @@ struct pgp_key_t { + + /** @brief Returns true if signature is produced by the key itself. */ + bool is_signer(const pgp_subsig_t &sig) const; + + /** @brief Returns true if key is expired according to sig. */ +- bool expired_with(const pgp_subsig_t &sig) const; ++ bool expired_with(const pgp_subsig_t &sig, uint64_t at) const; + + /** @brief Check whether signature is key's self certification. */ + bool is_self_cert(const pgp_subsig_t &sig) const; + + /** @brief Check whether signature is key's direct-key self-signature */ +@@ -442,12 +445,13 @@ struct pgp_key_t { + /** + * @brief Fill common signature parameters, assuming that current key is a signing one. + * @param sig signature to init. + * @param hash hash algorithm to use (may be changed if it is not suitable for public key + * algorithm). ++ * @param creation signature's creation time. + */ +- void sign_init(pgp_signature_t &sig, pgp_hash_alg_t hash) const; ++ void sign_init(pgp_signature_t &sig, pgp_hash_alg_t hash, uint64_t creation) const; + /** + * @brief Calculate a certification and fill signature material. + * Note: secret key must be unlocked before calling this function. + * + * @param key key packet to sign. May be both public and secret. Could be signing key's +diff --git a/comm/third_party/rnp/src/lib/rnp.cpp b/third_party/rnp/src/lib/comm/rnp.cpp +--- a/comm/third_party/rnp/src/lib/rnp.cpp ++++ b/comm/third_party/rnp/src/lib/rnp.cpp +@@ -132,10 +132,12 @@ ffi_key_provider(const pgp_key_request_c + static void + rnp_ctx_init_ffi(rnp_ctx_t &ctx, rnp_ffi_t ffi) + { + ctx.ctx = &ffi->context; + ctx.ealg = DEFAULT_PGP_SYMM_ALG; ++ ctx.aalg = PGP_AEAD_NONE; ++ ctx.abits = DEFAULT_AEAD_CHUNK_BITS; + } + + static const id_str_pair sig_type_map[] = {{PGP_SIG_BINARY, "binary"}, + {PGP_SIG_TEXT, "text"}, + {PGP_SIG_STANDALONE, "standalone"}, +@@ -298,10 +300,15 @@ str_to_cipher(const char *str, pgp_symm_ + #if !defined(ENABLE_TWOFISH) + if (alg == PGP_SA_TWOFISH) { + return false; + } + #endif ++#if !defined(ENABLE_IDEA) ++ if (alg == PGP_SA_IDEA) { ++ return false; ++ } ++#endif + *cipher = alg; + return true; + } + + static bool +@@ -583,11 +590,11 @@ try { + if (!ffi) { + return RNP_ERROR_NULL_POINTER; + } + + // open +- FILE *errs = fdopen(fd, "a"); ++ FILE *errs = rnp_fdopen(fd, "a"); + if (!errs) { + return RNP_ERROR_ACCESS; + } + // close previous streams and replace them + close_io_file(&ffi->errs); +@@ -715,13 +722,24 @@ rnp_result_to_string(rnp_result_t result + return "Key not found"; + case RNP_ERROR_NO_SUITABLE_KEY: + return "No suitable key"; + case RNP_ERROR_DECRYPT_FAILED: + return "Decryption failed"; ++ case RNP_ERROR_RNG: ++ return "Failure of random number generator"; ++ case RNP_ERROR_SIGNING_FAILED: ++ return "Signing failed"; + case RNP_ERROR_NO_SIGNATURES_FOUND: + return "No signatures found cannot verify"; + ++ case RNP_ERROR_SIGNATURE_EXPIRED: ++ return "Expired signature"; ++ case RNP_ERROR_VERIFICATION_FAILED: ++ return "Signature verification failed cannot verify"; ++ case RNP_ERROR_SIGNATURE_UNKNOWN: ++ return "Unknown signature"; ++ + case RNP_ERROR_NOT_ENOUGH_DATA: + return "Not enough data"; + case RNP_ERROR_UNKNOWN_TAG: + return "Unknown tag"; + case RNP_ERROR_PACKET_NOT_CONSUMED: +@@ -730,11 +748,11 @@ rnp_result_to_string(rnp_result_t result + return "No userid"; + case RNP_ERROR_EOF: + return "EOF detected"; + } + +- return "Unknown error"; ++ return "Unsupported error code"; + } + + const char * + rnp_version_string() + { +@@ -813,29 +831,26 @@ try { + if (!homedir) { + return RNP_ERROR_NULL_POINTER; + } + + // get the users home dir +- char *home = getenv("HOME"); +- if (!home) { ++ auto home = rnp::path::HOME(".rnp"); ++ if (home.empty()) { + return RNP_ERROR_NOT_SUPPORTED; + } +- if (!rnp_compose_path_ex(homedir, NULL, home, ".rnp", NULL)) { ++ *homedir = strdup(home.c_str()); ++ if (!*homedir) { + return RNP_ERROR_OUT_OF_MEMORY; + } + return RNP_SUCCESS; + } + FFI_GUARD + + rnp_result_t + rnp_detect_homedir_info( + const char *homedir, char **pub_format, char **pub_path, char **sec_format, char **sec_path) + try { +- rnp_result_t ret = RNP_ERROR_GENERIC; +- char * path = NULL; +- size_t path_size = 0; +- + // checks + if (!homedir || !pub_format || !pub_path || !sec_format || !sec_path) { + return RNP_ERROR_NULL_POINTER; + } + +@@ -845,83 +860,48 @@ try { + *pub_format = NULL; + *pub_path = NULL; + *sec_format = NULL; + *sec_path = NULL; + +- const char *pub_format_guess = NULL; +- const char *pub_path_guess = NULL; +- const char *sec_format_guess = NULL; +- const char *sec_path_guess = NULL; +- // check for pubring.kbx file +- if (!rnp_compose_path_ex(&path, &path_size, homedir, "pubring.kbx", NULL)) { +- goto done; +- } +- if (rnp_file_exists(path)) { +- // we have a pubring.kbx, now check for private-keys-v1.d dir +- if (!rnp_compose_path_ex(&path, &path_size, homedir, "private-keys-v1.d", NULL)) { +- goto done; +- } +- if (rnp_dir_exists(path)) { +- pub_format_guess = "KBX"; +- pub_path_guess = "pubring.kbx"; +- sec_format_guess = "G10"; +- sec_path_guess = "private-keys-v1.d"; +- } ++ // check for pubring.kbx file and for private-keys-v1.d dir ++ std::string pub = rnp::path::append(homedir, "pubring.kbx"); ++ std::string sec = rnp::path::append(homedir, "private-keys-v1.d"); ++ if (rnp::path::exists(pub) && rnp::path::exists(sec, true)) { ++ *pub_format = strdup("KBX"); ++ *sec_format = strdup("G10"); + } else { +- // check for pubring.gpg +- if (!rnp_compose_path_ex(&path, &path_size, homedir, "pubring.gpg", NULL)) { +- goto done; +- } +- if (rnp_file_exists(path)) { +- // we have a pubring.gpg, now check for secring.gpg +- if (!rnp_compose_path_ex(&path, &path_size, homedir, "secring.gpg", NULL)) { +- goto done; +- } +- if (rnp_file_exists(path)) { +- pub_format_guess = "GPG"; +- pub_path_guess = "pubring.gpg"; +- sec_format_guess = "GPG"; +- sec_path_guess = "secring.gpg"; +- } +- } +- } +- +- // set our results +- if (pub_format_guess) { +- *pub_format = strdup(pub_format_guess); +- *pub_path = rnp_compose_path(homedir, pub_path_guess, NULL); +- if (!*pub_format || !*pub_path) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- } +- if (sec_format_guess) { +- *sec_format = strdup(sec_format_guess); +- *sec_path = rnp_compose_path(homedir, sec_path_guess, NULL); +- if (!*sec_format || !*sec_path) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- } +- // we leave the *formats as NULL if we were not able to determine the format +- // (but no error occurred) +- +- ret = RNP_SUCCESS; +-done: +- if (ret) { +- free(*pub_format); +- *pub_format = NULL; +- free(*pub_path); +- *pub_path = NULL; +- +- free(*sec_format); +- *sec_format = NULL; +- free(*sec_path); +- *sec_path = NULL; +- } +- free(path); +- return ret; ++ // check for pubring.gpg and secring.gpg ++ pub = rnp::path::append(homedir, "pubring.gpg"); ++ sec = rnp::path::append(homedir, "secring.gpg"); ++ if (rnp::path::exists(pub) && rnp::path::exists(sec)) { ++ *pub_format = strdup("GPG"); ++ *sec_format = strdup("GPG"); ++ } else { ++ // we leave the *formats as NULL if we were not able to determine the format ++ // (but no error occurred) ++ return RNP_SUCCESS; ++ } ++ } ++ ++ // set pathes ++ *pub_path = strdup(pub.c_str()); ++ *sec_path = strdup(sec.c_str()); ++ ++ // check for allocation failures ++ if (*pub_format && *pub_path && *sec_format && *sec_path) { ++ return RNP_SUCCESS; ++ } ++ ++ free(*pub_format); ++ *pub_format = NULL; ++ free(*pub_path); ++ *pub_path = NULL; ++ free(*sec_format); ++ *sec_format = NULL; ++ free(*sec_path); ++ *sec_path = NULL; ++ return RNP_ERROR_OUT_OF_MEMORY; + } + FFI_GUARD + + rnp_result_t + rnp_detect_key_format(const uint8_t buf[], size_t buf_len, char **format) +@@ -1045,11 +1025,14 @@ try { + } + + rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS; + + if (rnp::str_case_eq(type, RNP_FEATURE_SYMM_ALG)) { +- ret = json_array_add_id_str(features, symm_alg_map, PGP_SA_IDEA, PGP_SA_AES_256); ++#if defined(ENABLE_IDEA) ++ ret = json_array_add_id_str(features, symm_alg_map, PGP_SA_IDEA, PGP_SA_IDEA); ++#endif ++ ret = json_array_add_id_str(features, symm_alg_map, PGP_SA_TRIPLEDES, PGP_SA_AES_256); + #if defined(ENABLE_TWOFISH) + ret = json_array_add_id_str(features, symm_alg_map, PGP_SA_TWOFISH, PGP_SA_TWOFISH); + #endif + ret = json_array_add_id_str( + features, symm_alg_map, PGP_SA_CAMELLIA_128, PGP_SA_CAMELLIA_256); +@@ -1177,21 +1160,50 @@ try { + return RNP_ERROR_BAD_PARAMETERS; + } + /* check flags */ + bool rule_override = flags & RNP_SECURITY_OVERRIDE; + flags &= ~RNP_SECURITY_OVERRIDE; ++ bool verify_key = flags & RNP_SECURITY_VERIFY_KEY; ++ flags &= ~RNP_SECURITY_VERIFY_KEY; ++ bool verify_data = flags & RNP_SECURITY_VERIFY_DATA; ++ flags &= ~RNP_SECURITY_VERIFY_DATA; + if (flags) { + FFI_LOG(ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* add rule */ + rnp::SecurityRule newrule(ftype, fvalue, sec_level, from); + newrule.override = rule_override; +- ffi->profile().add_rule(newrule); +- return RNP_SUCCESS; +-} +-FFI_GUARD ++ /* Add rule for any action */ ++ if (!verify_key && !verify_data) { ++ ffi->profile().add_rule(newrule); ++ return RNP_SUCCESS; ++ } ++ /* Add rule for each specified key usage */ ++ if (verify_key) { ++ newrule.action = rnp::SecurityAction::VerifyKey; ++ ffi->profile().add_rule(newrule); ++ } ++ if (verify_data) { ++ newrule.action = rnp::SecurityAction::VerifyData; ++ ffi->profile().add_rule(newrule); ++ } ++ return RNP_SUCCESS; ++} ++FFI_GUARD ++ ++static rnp::SecurityAction ++get_security_action(uint32_t flags) ++{ ++ if (flags & RNP_SECURITY_VERIFY_KEY) { ++ return rnp::SecurityAction::VerifyKey; ++ } ++ if (flags & RNP_SECURITY_VERIFY_DATA) { ++ return rnp::SecurityAction::VerifyData; ++ } ++ return rnp::SecurityAction::Any; ++} + + rnp_result_t + rnp_get_security_rule(rnp_ffi_t ffi, + const char *type, + const char *name, +@@ -1209,17 +1221,29 @@ try { + if (!get_feature_sec_value(ffi, type, name, ftype, fvalue)) { + return RNP_ERROR_BAD_PARAMETERS; + } + /* init default rule */ + rnp::SecurityRule rule(ftype, fvalue, ffi->profile().def_level()); ++ /* Check whether limited usage is requested */ ++ auto action = get_security_action(flags ? *flags : 0); + /* check whether rule exists */ +- if (ffi->profile().has_rule(ftype, fvalue, time)) { +- rule = ffi->profile().get_rule(ftype, fvalue, time); ++ if (ffi->profile().has_rule(ftype, fvalue, time, action)) { ++ rule = ffi->profile().get_rule(ftype, fvalue, time, action); + } + /* fill the results */ + if (flags) { + *flags = rule.override ? RNP_SECURITY_OVERRIDE : 0; ++ switch (rule.action) { ++ case rnp::SecurityAction::VerifyKey: ++ *flags |= RNP_SECURITY_VERIFY_KEY; ++ break; ++ case rnp::SecurityAction::VerifyData: ++ *flags |= RNP_SECURITY_VERIFY_DATA; ++ break; ++ default: ++ break; ++ } + } + if (from) { + *from = rule.from; + } + switch (rule.level) { +@@ -1255,10 +1279,13 @@ try { + /* check flags */ + bool remove_all = flags & RNP_SECURITY_REMOVE_ALL; + flags &= ~RNP_SECURITY_REMOVE_ALL; + bool rule_override = flags & RNP_SECURITY_OVERRIDE; + flags &= ~RNP_SECURITY_OVERRIDE; ++ rnp::SecurityAction action = get_security_action(flags); ++ flags &= ~RNP_SECURITY_VERIFY_DATA; ++ flags &= ~RNP_SECURITY_VERIFY_KEY; + if (flags) { + FFI_LOG(ffi, "Unknown flags: %" PRIu32, flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* remove all rules */ +@@ -1282,11 +1309,11 @@ try { + if (remove_all) { + /* remove all rules for the specified type and name */ + ffi->profile().clear_rules(ftype, fvalue); + } else { + /* remove specific rule */ +- rnp::SecurityRule rule(ftype, fvalue, flevel, from); ++ rnp::SecurityRule rule(ftype, fvalue, flevel, from, action); + rule.override = rule_override; + ffi->profile().del_rule(rule); + } + success: + if (removed) { +@@ -1296,11 +1323,11 @@ success: + } + FFI_GUARD + + rnp_result_t + rnp_request_password(rnp_ffi_t ffi, rnp_key_handle_t key, const char *context, char **password) +-{ ++try { + if (!ffi || !password || !ffi->getpasscb) { + return RNP_ERROR_NULL_POINTER; + } + + rnp::secure_vector pass(MAX_PASSWORD_LENGTH, '\0'); +@@ -1315,49 +1342,44 @@ rnp_request_password(rnp_ffi_t ffi, rnp_ + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(*password, pass.data(), pass_len); + return RNP_SUCCESS; + } ++FFI_GUARD ++ ++rnp_result_t ++rnp_set_timestamp(rnp_ffi_t ffi, uint64_t time) ++try { ++ if (!ffi) { ++ return RNP_ERROR_NULL_POINTER; ++ } ++ ffi->context.set_time(time); ++ return RNP_SUCCESS; ++} ++FFI_GUARD + + static rnp_result_t + load_keys_from_input(rnp_ffi_t ffi, rnp_input_t input, rnp_key_store_t *store) + { +- rnp_result_t ret = RNP_ERROR_GENERIC; +- +- pgp_key_provider_t chained; +- chained.callback = rnp_key_provider_store; +- chained.userdata = store; +- ++ pgp_key_provider_t chained(rnp_key_provider_store, store); + const pgp_key_provider_t *key_providers[] = {&chained, &ffi->key_provider, NULL}; +- +- const pgp_key_provider_t key_provider = {.callback = rnp_key_provider_chained, +- .userdata = key_providers}; +- +- if (input->src_directory) { ++ const pgp_key_provider_t key_provider(rnp_key_provider_chained, key_providers); ++ ++ if (!input->src_directory.empty()) { + // load the keys +- try { +- store->path = input->src_directory; +- } catch (const std::exception &e) { +- FFI_LOG(ffi, "%s", e.what()); +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } ++ store->path = input->src_directory; + if (!rnp_key_store_load_from_path(store, &key_provider)) { +- ret = RNP_ERROR_BAD_FORMAT; +- goto done; +- } +- } else { +- // load the keys +- if (!rnp_key_store_load_from_src(store, &input->src, &key_provider)) { +- ret = RNP_ERROR_BAD_FORMAT; +- goto done; +- } +- } +- +- ret = RNP_SUCCESS; +-done: +- return ret; ++ return RNP_ERROR_BAD_FORMAT; ++ } ++ return RNP_SUCCESS; ++ } ++ ++ // load the keys ++ if (!rnp_key_store_load_from_src(store, &input->src, &key_provider)) { ++ return RNP_ERROR_BAD_FORMAT; ++ } ++ return RNP_SUCCESS; + } + + static bool + key_needs_conversion(const pgp_key_t *key, const rnp_key_store_t *store) + { +@@ -1383,88 +1405,73 @@ static rnp_result_t + do_load_keys(rnp_ffi_t ffi, + rnp_input_t input, + pgp_key_store_format_t format, + key_type_t key_type) + { +- rnp_result_t ret = RNP_ERROR_GENERIC; +- rnp_key_store_t *tmp_store = NULL; +- pgp_key_t keycp; +- rnp_result_t tmpret; +- + // create a temporary key store to hold the keys ++ std::unique_ptr tmp_store; + try { +- tmp_store = new rnp_key_store_t(format, "", ffi->context); ++ tmp_store = ++ std::unique_ptr(new rnp_key_store_t(format, "", ffi->context)); + } catch (const std::invalid_argument &e) { + FFI_LOG(ffi, "Failed to create key store of format: %d", (int) format); + return RNP_ERROR_BAD_PARAMETERS; +- } catch (const std::exception &e) { +- FFI_LOG(ffi, "%s", e.what()); +- return RNP_ERROR_OUT_OF_MEMORY; + } + + // load keys into our temporary store +- tmpret = load_keys_from_input(ffi, input, tmp_store); ++ rnp_result_t tmpret = load_keys_from_input(ffi, input, tmp_store.get()); + if (tmpret) { +- ret = tmpret; +- goto done; ++ return tmpret; + } + // go through all the loaded keys + for (auto &key : tmp_store->keys) { + // check that the key is the correct type and has not already been loaded + // add secret key part if it is and we need it + if (key.is_secret() && ((key_type == KEY_TYPE_SECRET) || (key_type == KEY_TYPE_ANY))) { + if (key_needs_conversion(&key, ffi->secring)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); +- ret = RNP_ERROR_NOT_IMPLEMENTED; +- goto done; ++ return RNP_ERROR_NOT_IMPLEMENTED; + } + + if (!rnp_key_store_add_key(ffi->secring, &key)) { + FFI_LOG(ffi, "Failed to add secret key"); +- ret = RNP_ERROR_GENERIC; +- goto done; ++ return RNP_ERROR_GENERIC; + } + } + + // add public key part if needed + if ((key.format == PGP_KEY_STORE_G10) || + ((key_type != KEY_TYPE_ANY) && (key_type != KEY_TYPE_PUBLIC))) { + continue; + } + ++ pgp_key_t keycp; + try { + keycp = pgp_key_t(key, true); + } catch (const std::exception &e) { + RNP_LOG("Failed to copy public key part: %s", e.what()); +- ret = RNP_ERROR_GENERIC; +- goto done; ++ return RNP_ERROR_GENERIC; + } + + /* TODO: We could do this a few different ways. There isn't an obvious reason + * to restrict what formats we load, so we don't necessarily need to require a + * conversion just to load and use a G10 key when using GPG keyrings, for + * example. We could just convert when saving. + */ + + if (key_needs_conversion(&key, ffi->pubring)) { + FFI_LOG(ffi, "This key format conversion is not yet supported"); +- ret = RNP_ERROR_NOT_IMPLEMENTED; +- goto done; ++ return RNP_ERROR_NOT_IMPLEMENTED; + } + + if (!rnp_key_store_add_key(ffi->pubring, &keycp)) { + FFI_LOG(ffi, "Failed to add public key"); +- ret = RNP_ERROR_GENERIC; +- goto done; +- } +- } +- ++ return RNP_ERROR_GENERIC; ++ } ++ } + // success, even if we didn't actually load any +- ret = RNP_SUCCESS; +-done: +- delete tmp_store; +- return ret; ++ return RNP_SUCCESS; + } + + static key_type_t + flags_to_key_type(uint32_t *flags) + { +@@ -1531,55 +1538,52 @@ try { + return RNP_SUCCESS; + } + FFI_GUARD + + static rnp_result_t +-rnp_input_dearmor_if_needed(rnp_input_t input) ++rnp_input_dearmor_if_needed(rnp_input_t input, bool noheaders = false) + { + if (!input) { + return RNP_ERROR_NULL_POINTER; + } +- if (input->src_directory) { ++ if (!input->src_directory.empty()) { + return RNP_ERROR_BAD_PARAMETERS; + } + bool require_armor = false; + /* check whether we already have armored stream */ + if (input->src.type == PGP_STREAM_ARMORED) { + if (!src_eof(&input->src)) { +- return RNP_SUCCESS; ++ /* be ready for the case of damaged armoring */ ++ return src_error(&input->src) ? RNP_ERROR_READ : RNP_SUCCESS; + } + /* eof - probably next we have another armored message */ + src_close(&input->src); +- void *app_ctx = input->app_ctx; +- *input = *(rnp_input_t) app_ctx; +- free(app_ctx); ++ rnp_input_st *base = (rnp_input_st *) input->app_ctx; ++ *input = std::move(*base); ++ delete base; + /* we should not mix armored data with binary */ + require_armor = true; + } + if (src_eof(&input->src)) { + return RNP_ERROR_EOF; + } +- if (!is_armored_source(&input->src)) { ++ /* check whether input is armored only if base64 is not forced */ ++ if (!noheaders && !is_armored_source(&input->src)) { + return require_armor ? RNP_ERROR_BAD_FORMAT : RNP_SUCCESS; + } + +- rnp_input_t app_ctx = (rnp_input_t) calloc(1, sizeof(*input)); +- if (!app_ctx) { +- return RNP_ERROR_OUT_OF_MEMORY; +- } +- *app_ctx = *input; +- +- pgp_source_t armored; +- rnp_result_t ret = init_armored_src(&armored, &app_ctx->src); ++ /* Store original input in app_ctx and replace src/app_ctx with armored data */ ++ rnp_input_t app_ctx = new rnp_input_st(); ++ *app_ctx = std::move(*input); ++ ++ rnp_result_t ret = init_armored_src(&input->src, &app_ctx->src, noheaders); + if (ret) { + /* original src may be changed during init_armored_src call, so copy it back */ +- input->src = app_ctx->src; +- free(app_ctx); ++ *input = std::move(*app_ctx); ++ delete app_ctx; + return ret; + } +- +- input->src = armored; + input->app_ctx = app_ctx; + return RNP_SUCCESS; + } + + static const char * +@@ -1643,110 +1647,104 @@ try { + bool single = false; + if (flags & RNP_LOAD_SAVE_SINGLE) { + single = true; + flags &= ~RNP_LOAD_SAVE_SINGLE; + } ++ bool base64 = false; ++ if (flags & RNP_LOAD_SAVE_BASE64) { ++ base64 = true; ++ flags &= ~RNP_LOAD_SAVE_BASE64; ++ } + if (flags) { + FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + +- rnp_result_t ret = RNP_ERROR_GENERIC; +- rnp_key_store_t *tmp_store = NULL; +- rnp_result_t tmpret; +- json_object * jsores = NULL; +- json_object * jsokeys = NULL; ++ rnp_result_t ret = RNP_ERROR_GENERIC; ++ rnp_key_store_t tmp_store(PGP_KEY_STORE_GPG, "", ffi->context); ++ ++ /* check whether input is base64 */ ++ if (base64 && is_base64_source(input->src)) { ++ ret = rnp_input_dearmor_if_needed(input, true); ++ if (ret) { ++ return ret; ++ } ++ } + + // load keys to temporary keystore. +- try { +- tmp_store = new rnp_key_store_t(PGP_KEY_STORE_GPG, "", ffi->context); +- } catch (const std::exception &e) { +- FFI_LOG(ffi, "Failed to create key store: %s.", e.what()); +- return RNP_ERROR_OUT_OF_MEMORY; +- } +- + if (single) { + /* we need to init and handle dearmor on this layer since it may be used for the next + * keys import */ + ret = rnp_input_dearmor_if_needed(input); + if (ret == RNP_ERROR_EOF) { +- goto done; ++ return ret; + } + if (ret) { + FFI_LOG(ffi, "Failed to init/check dearmor."); +- goto done; +- } +- ret = rnp_key_store_pgp_read_key_from_src(*tmp_store, input->src, skipbad); ++ return ret; ++ } ++ ret = rnp_key_store_pgp_read_key_from_src(tmp_store, input->src, skipbad); + if (ret) { +- goto done; ++ return ret; + } + } else { +- ret = rnp_key_store_pgp_read_from_src(tmp_store, &input->src, skipbad); ++ ret = rnp_key_store_pgp_read_from_src(&tmp_store, &input->src, skipbad); + if (ret) { +- goto done; +- } +- } +- jsores = json_object_new_object(); ++ return ret; ++ } ++ } ++ ++ json_object *jsores = json_object_new_object(); + if (!jsores) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- jsokeys = json_object_new_array(); ++ return RNP_ERROR_OUT_OF_MEMORY; ++ } ++ rnp::JSONObject jsowrap(jsores); ++ json_object * jsokeys = json_object_new_array(); + if (!obj_add_field_json(jsores, "keys", jsokeys)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; ++ return RNP_ERROR_OUT_OF_MEMORY; + } + + // import keys to the main keystore. +- for (auto &key : tmp_store->keys) { ++ for (auto &key : tmp_store.keys) { + pgp_key_import_status_t pub_status = PGP_KEY_IMPORT_STATUS_UNKNOWN; + pgp_key_import_status_t sec_status = PGP_KEY_IMPORT_STATUS_UNKNOWN; + if (!pub && key.is_public()) { + continue; + } + // if we got here then we add public key itself or public part of the secret key + if (!rnp_key_store_import_key(ffi->pubring, &key, true, &pub_status)) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; ++ return RNP_ERROR_BAD_PARAMETERS; + } + // import secret key part if available and requested + if (sec && key.is_secret()) { + if (!rnp_key_store_import_key(ffi->secring, &key, false, &sec_status)) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; ++ return RNP_ERROR_BAD_PARAMETERS; + } + // add uids, certifications and other stuff from the public key if any + pgp_key_t *expub = rnp_key_store_get_key_by_fpr(ffi->pubring, key.fp()); + if (expub && !rnp_key_store_import_key(ffi->secring, expub, true, NULL)) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; ++ return RNP_ERROR_BAD_PARAMETERS; + } + } + // now add key fingerprint to json based on statuses +- if ((tmpret = add_key_status(jsokeys, &key, pub_status, sec_status))) { +- ret = tmpret; +- goto done; ++ rnp_result_t tmpret = add_key_status(jsokeys, &key, pub_status, sec_status); ++ if (tmpret) { ++ return tmpret; + } + } + + if (results) { + *results = (char *) json_object_to_json_string_ext(jsores, JSON_C_TO_STRING_PRETTY); + if (!*results) { +- goto done; ++ return RNP_ERROR_GENERIC; + } + *results = strdup(*results); + if (!*results) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- } +- +- ret = RNP_SUCCESS; +-done: +- delete tmp_store; +- json_object_put(jsores); +- return ret; ++ return RNP_ERROR_OUT_OF_MEMORY; ++ } ++ } ++ return RNP_SUCCESS; + } + FFI_GUARD + + static const char * + sig_status_to_str(pgp_sig_import_status_t status) +@@ -1801,60 +1799,49 @@ try { + if (flags) { + FFI_LOG(ffi, "wrong flags: %d", (int) flags); + return RNP_ERROR_BAD_PARAMETERS; + } + +- rnp_result_t ret = RNP_ERROR_GENERIC; +- json_object * jsores = NULL; +- json_object * jsosigs = NULL; + pgp_signature_list_t sigs; +- rnp_result_t sigret = process_pgp_signatures(&input->src, sigs); ++ rnp_result_t sigret = process_pgp_signatures(input->src, sigs); + if (sigret) { +- ret = sigret; + FFI_LOG(ffi, "failed to parse signature(s)"); +- goto done; +- } +- +- jsores = json_object_new_object(); ++ return sigret; ++ } ++ ++ json_object *jsores = json_object_new_object(); + if (!jsores) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- jsosigs = json_object_new_array(); ++ return RNP_ERROR_OUT_OF_MEMORY; ++ } ++ rnp::JSONObject jsowrap(jsores); ++ json_object * jsosigs = json_object_new_array(); + if (!obj_add_field_json(jsores, "sigs", jsosigs)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; ++ return RNP_ERROR_OUT_OF_MEMORY; + } + + for (auto &sig : sigs) { + pgp_sig_import_status_t pub_status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + pgp_sig_import_status_t sec_status = PGP_SIG_IMPORT_STATUS_UNKNOWN; + pgp_key_t *pkey = rnp_key_store_import_signature(ffi->pubring, &sig, &pub_status); + pgp_key_t *skey = rnp_key_store_import_signature(ffi->secring, &sig, &sec_status); + sigret = add_sig_status(jsosigs, pkey ? pkey : skey, pub_status, sec_status); + if (sigret) { +- ret = sigret; +- goto done; ++ return sigret; + } + } + + if (results) { + *results = (char *) json_object_to_json_string_ext(jsores, JSON_C_TO_STRING_PRETTY); + if (!*results) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; ++ return RNP_ERROR_OUT_OF_MEMORY; + } + *results = strdup(*results); + if (!*results) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- } +- ret = RNP_SUCCESS; +-done: +- json_object_put(jsores); +- return ret; ++ return RNP_ERROR_OUT_OF_MEMORY; ++ } ++ } ++ return RNP_SUCCESS; + } + FFI_GUARD + + static bool + copy_store_keys(rnp_ffi_t ffi, rnp_key_store_t *dest, rnp_key_store_t *src) +@@ -1984,74 +1971,111 @@ try { + *count = rnp_key_store_get_key_count(ffi->secring); + return RNP_SUCCESS; + } + FFI_GUARD + ++rnp_input_st::rnp_input_st() : reader(NULL), closer(NULL), app_ctx(NULL) ++{ ++ memset(&src, 0, sizeof(src)); ++} ++ ++rnp_input_st & ++rnp_input_st::operator=(rnp_input_st &&input) ++{ ++ src_close(&src); ++ src = std::move(input.src); ++ memset(&input.src, 0, sizeof(input.src)); ++ reader = input.reader; ++ input.reader = NULL; ++ closer = input.closer; ++ input.closer = NULL; ++ app_ctx = input.app_ctx; ++ input.app_ctx = NULL; ++ src_directory = std::move(input.src_directory); ++ return *this; ++} ++ ++rnp_input_st::~rnp_input_st() ++{ ++ bool armored = src.type == PGP_STREAM_ARMORED; ++ src_close(&src); ++ if (armored) { ++ rnp_input_t armored = (rnp_input_t) app_ctx; ++ delete armored; ++ app_ctx = NULL; ++ } ++} ++ + rnp_result_t + rnp_input_from_path(rnp_input_t *input, const char *path) + try { +- struct rnp_input_st *ob = NULL; +- struct stat st = {0}; +- + if (!input || !path) { + return RNP_ERROR_NULL_POINTER; + } +- ob = (rnp_input_st *) calloc(1, sizeof(*ob)); +- if (!ob) { +- return RNP_ERROR_OUT_OF_MEMORY; +- } ++ rnp_input_st *ob = new rnp_input_st(); ++ struct stat st = {0}; + if (rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + // a bit hacky, just save the directory path +- ob->src_directory = strdup(path); +- if (!ob->src_directory) { +- free(ob); +- return RNP_ERROR_OUT_OF_MEMORY; +- } ++ ob->src_directory = path; + // return error on attempt to read from this source + (void) init_null_src(&ob->src); + } else { + // simple input from a file + rnp_result_t ret = init_file_src(&ob->src, path); + if (ret) { +- free(ob); ++ delete ob; + return ret; + } + } + *input = ob; + return RNP_SUCCESS; + } + FFI_GUARD + + rnp_result_t ++rnp_input_from_stdin(rnp_input_t *input) ++try { ++ if (!input) { ++ return RNP_ERROR_NULL_POINTER; ++ } ++ *input = new rnp_input_st(); ++ rnp_result_t ret = init_stdin_src(&(*input)->src); ++ if (ret) { ++ delete *input; ++ *input = NULL; ++ return ret; ++ } ++ return RNP_SUCCESS; ++} ++FFI_GUARD ++ ++rnp_result_t + rnp_input_from_memory(rnp_input_t *input, const uint8_t buf[], size_t buf_len, bool do_copy) + try { + if (!input || !buf) { + return RNP_ERROR_NULL_POINTER; + } + if (!buf_len) { + return RNP_ERROR_SHORT_BUFFER; + } +- *input = (rnp_input_t) calloc(1, sizeof(**input)); +- if (!*input) { +- return RNP_ERROR_OUT_OF_MEMORY; +- } ++ *input = new rnp_input_st(); + uint8_t *data = (uint8_t *) buf; + if (do_copy) { + data = (uint8_t *) malloc(buf_len); + if (!data) { +- free(*input); ++ delete *input; + *input = NULL; + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(data, buf, buf_len); + } + rnp_result_t ret = init_mem_src(&(*input)->src, data, buf_len, do_copy); + if (ret) { + if (do_copy) { + free(data); + } +- free(*input); ++ delete *input; + *input = NULL; + return ret; + } + return RNP_SUCCESS; + } +@@ -2080,26 +2104,24 @@ rnp_result_t + rnp_input_from_callback(rnp_input_t * input, + rnp_input_reader_t *reader, + rnp_input_closer_t *closer, + void * app_ctx) + try { +- struct rnp_input_st *obj = NULL; +- + // checks + if (!input || !reader) { + return RNP_ERROR_NULL_POINTER; + } +- obj = (rnp_input_st *) calloc(1, sizeof(*obj)); ++ rnp_input_st *obj = new rnp_input_st(); + if (!obj) { + return RNP_ERROR_OUT_OF_MEMORY; + } + pgp_source_t *src = &obj->src; + obj->reader = reader; + obj->closer = closer; + obj->app_ctx = app_ctx; + if (!init_src_common(src, 0)) { +- free(obj); ++ delete obj; + return RNP_ERROR_OUT_OF_MEMORY; + } + src->param = obj; + src->read = input_reader_bounce; + src->close = input_closer_bounce; +@@ -2110,19 +2132,11 @@ try { + FFI_GUARD + + rnp_result_t + rnp_input_destroy(rnp_input_t input) + try { +- if (input) { +- bool armored = input->src.type == PGP_STREAM_ARMORED; +- src_close(&input->src); +- if (armored) { +- rnp_input_destroy((rnp_input_t) input->app_ctx); +- } +- free(input->src_directory); +- free(input); +- } ++ delete input; + return RNP_SUCCESS; + } + FFI_GUARD + + rnp_result_t +@@ -2195,10 +2209,30 @@ try { + return RNP_SUCCESS; + } + FFI_GUARD + + rnp_result_t ++rnp_output_to_stdout(rnp_output_t *output) ++try { ++ if (!output) { ++ return RNP_ERROR_NULL_POINTER; ++ } ++ rnp_output_t res = (rnp_output_t) calloc(1, sizeof(*res)); ++ if (!res) { ++ return RNP_ERROR_OUT_OF_MEMORY; ++ } ++ rnp_result_t ret = init_stdout_dest(&res->dst); ++ if (ret) { ++ free(res); ++ return ret; ++ } ++ *output = res; ++ return RNP_SUCCESS; ++} ++FFI_GUARD ++ ++rnp_result_t + rnp_output_to_memory(rnp_output_t *output, size_t max_alloc) + try { + // checks + if (!output) { + return RNP_ERROR_NULL_POINTER; +@@ -2405,12 +2439,11 @@ rnp_op_add_signature(rnp_ffi_t + } + + pgp_key_t *signkey = find_suitable_key( + PGP_OP_SIGN, get_key_prefer_public(key), &key->ffi->key_provider, PGP_KF_SIGN); + if (signkey && !signkey->is_secret()) { +- pgp_key_request_ctx_t keyctx = {.op = PGP_OP_SIGN, .secret = true}; +- keyctx.search.type = PGP_KEY_SEARCH_GRIP; ++ pgp_key_request_ctx_t keyctx(PGP_OP_SIGN, true, PGP_KEY_SEARCH_GRIP); + keyctx.search.by.grip = signkey->grip(); + signkey = pgp_request_key(&key->ffi->key_provider, &keyctx); + } + if (!signkey) { + return RNP_ERROR_NO_SUITABLE_KEY; +@@ -2486,10 +2519,26 @@ rnp_op_set_expiration_time(rnp_ctx_t &ct + ctx.sigexpire = expire; + return RNP_SUCCESS; + } + + static rnp_result_t ++rnp_op_set_flags(rnp_ffi_t ffi, rnp_ctx_t &ctx, uint32_t flags) ++{ ++ if (flags & RNP_ENCRYPT_NOWRAP) { ++ ctx.no_wrap = true; ++ flags &= ~RNP_ENCRYPT_NOWRAP; ++ } else { ++ ctx.no_wrap = false; ++ } ++ if (flags) { ++ FFI_LOG(ffi, "Unknown operation flags: %x", flags); ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ return RNP_SUCCESS; ++} ++ ++static rnp_result_t + rnp_op_set_file_name(rnp_ctx_t &ctx, const char *filename) + { + ctx.filename = filename ? filename : ""; + return RNP_SUCCESS; + } +@@ -2616,26 +2665,20 @@ try { + pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN; + if (!str_to_cipher(s2k_cipher, &symm_alg)) { + FFI_LOG(op->ffi, "Invalid cipher: %s", s2k_cipher); + return RNP_ERROR_BAD_PARAMETERS; + } +- try { +- rnp::secure_vector ask_pass(MAX_PASSWORD_LENGTH, '\0'); +- if (!password) { +- pgp_password_ctx_t pswdctx = {.op = PGP_OP_ENCRYPT_SYM, .key = NULL}; +- if (!pgp_request_password( +- &op->ffi->pass_provider, &pswdctx, ask_pass.data(), ask_pass.size())) { +- return RNP_ERROR_BAD_PASSWORD; +- } +- password = ask_pass.data(); +- } +- return rnp_ctx_add_encryption_password( +- op->rnpctx, password, hash_alg, symm_alg, iterations); +- } catch (const std::exception &e) { +- FFI_LOG(op->ffi, "%s", e.what()); +- return RNP_ERROR_OUT_OF_MEMORY; +- } ++ rnp::secure_vector ask_pass(MAX_PASSWORD_LENGTH, '\0'); ++ if (!password) { ++ pgp_password_ctx_t pswdctx(PGP_OP_ENCRYPT_SYM); ++ if (!pgp_request_password( ++ &op->ffi->pass_provider, &pswdctx, ask_pass.data(), ask_pass.size())) { ++ return RNP_ERROR_BAD_PASSWORD; ++ } ++ password = ask_pass.data(); ++ } ++ return op->rnpctx.add_encryption_password(password, hash_alg, symm_alg, iterations); + } + FFI_GUARD + + rnp_result_t + rnp_op_encrypt_set_armor(rnp_op_encrypt_t op, bool armored) +@@ -2702,10 +2745,21 @@ try { + return rnp_op_set_compression(op->ffi, op->rnpctx, compression, level); + } + FFI_GUARD + + rnp_result_t ++rnp_op_encrypt_set_flags(rnp_op_encrypt_t op, uint32_t flags) ++try { ++ // checks ++ if (!op) { ++ return RNP_ERROR_NULL_POINTER; ++ } ++ return rnp_op_set_flags(op->ffi, op->rnpctx, flags); ++} ++FFI_GUARD ++ ++rnp_result_t + rnp_op_encrypt_set_file_name(rnp_op_encrypt_t op, const char *filename) + try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } +@@ -2775,18 +2829,14 @@ try { + } + pgp_write_handler_t handler = + pgp_write_handler(&op->ffi->pass_provider, &op->rnpctx, NULL, &op->ffi->key_provider); + + rnp_result_t ret; +- if (!op->signatures.empty()) { +- if ((ret = rnp_op_add_signatures(op->signatures, op->rnpctx))) { +- return ret; +- } +- ret = rnp_encrypt_sign_src(&handler, &op->input->src, &op->output->dst); +- } else { +- ret = rnp_encrypt_src(&handler, &op->input->src, &op->output->dst); +- } ++ if (!op->signatures.empty() && (ret = rnp_op_add_signatures(op->signatures, op->rnpctx))) { ++ return ret; ++ } ++ ret = rnp_encrypt_sign_src(&handler, &op->input->src, &op->output->dst); + + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + op->input = NULL; + op->output = NULL; +@@ -3029,11 +3079,11 @@ rnp_op_verify_on_signatures(const std::v + FFI_LOG(op->ffi, "%s", e.what()); + } + } + + if (sinfo.unknown) { +- res->verify_status = RNP_ERROR_SIGNATURE_INVALID; ++ res->verify_status = RNP_ERROR_SIGNATURE_UNKNOWN; + } else if (sinfo.valid) { + res->verify_status = sinfo.expired ? RNP_ERROR_SIGNATURE_EXPIRED : RNP_SUCCESS; + } else { + res->verify_status = + sinfo.no_signer ? RNP_ERROR_KEY_NOT_FOUND : RNP_ERROR_SIGNATURE_INVALID; +@@ -3225,10 +3275,38 @@ try { + return RNP_SUCCESS; + } + FFI_GUARD + + rnp_result_t ++rnp_op_verify_set_flags(rnp_op_verify_t op, uint32_t flags) ++try { ++ if (!op) { ++ return RNP_ERROR_NULL_POINTER; ++ } ++ /* Allow to decrypt without valid signatures */ ++ if (flags & RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT) { ++ op->ignore_sigs = true; ++ flags &= ~RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT; ++ } else { ++ op->ignore_sigs = false; ++ } ++ /* Strict mode: require all signatures to be valid */ ++ if (flags & RNP_VERIFY_REQUIRE_ALL_SIGS) { ++ op->require_all_sigs = true; ++ flags &= ~RNP_VERIFY_REQUIRE_ALL_SIGS; ++ } else { ++ op->require_all_sigs = false; ++ } ++ if (flags) { ++ FFI_LOG(op->ffi, "Unknown operation flags: %x", flags); ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ return RNP_SUCCESS; ++} ++FFI_GUARD ++ ++rnp_result_t + rnp_op_verify_execute(rnp_op_verify_t op) + try { + if (!op) { + return RNP_ERROR_NULL_POINTER; + } +@@ -3246,10 +3324,23 @@ try { + handler.on_decryption_done = rnp_verify_on_decryption_done; + handler.param = op; + handler.ctx = &op->rnpctx; + + rnp_result_t ret = process_pgp_source(&handler, op->input->src); ++ /* Allow to decrypt data ignoring the signatures check if requested */ ++ if (op->ignore_sigs && op->validated && (ret == RNP_ERROR_SIGNATURE_INVALID)) { ++ ret = RNP_SUCCESS; ++ } ++ /* Allow to require all signatures be valid */ ++ if (op->require_all_sigs && !ret) { ++ for (size_t i = 0; i < op->signature_count; i++) { ++ if (op->signatures[i].verify_status) { ++ ret = RNP_ERROR_SIGNATURE_INVALID; ++ break; ++ } ++ } ++ } + if (op->output) { + dst_flush(&op->output->dst); + op->output->keep = ret == RNP_SUCCESS; + } + return ret; +@@ -3573,19 +3664,17 @@ try { + FFI_GUARD + + rnp_result_t + rnp_op_verify_signature_get_key(rnp_op_verify_signature_t sig, rnp_key_handle_t *key) + try { +- rnp_ffi_t ffi = sig->ffi; +- pgp_key_search_t search = {}; +- + if (!sig->sig_pkt.has_keyid()) { + return RNP_ERROR_BAD_PARAMETERS; + } +- search.by.keyid = sig->sig_pkt.keyid(); ++ rnp_ffi_t ffi = sig->ffi; + // create a search (since we'll use this later anyways) +- search.type = PGP_KEY_SEARCH_KEYID; ++ pgp_key_search_t search(PGP_KEY_SEARCH_KEYID); ++ search.by.keyid = sig->sig_pkt.keyid(); + + // search the stores + pgp_key_t *pub = rnp_key_store_search(ffi->pubring, &search, NULL); + pgp_key_t *sec = rnp_key_store_search(ffi->secring, &search, NULL); + if (!pub && !sec) { +@@ -3619,43 +3708,28 @@ try { + + return RNP_SUCCESS; + } + FFI_GUARD + +-static bool +-rnp_decrypt_dest_provider(pgp_parse_handler_t *handler, +- pgp_dest_t ** dst, +- bool * closedst, +- const char * filename, +- uint32_t mtime) +-{ +- *dst = &((rnp_output_t) handler->param)->dst; +- *closedst = false; +- return true; +-} +- + rnp_result_t + rnp_decrypt(rnp_ffi_t ffi, rnp_input_t input, rnp_output_t output) + try { + // checks + if (!ffi || !input || !output) { + return RNP_ERROR_NULL_POINTER; + } + +- rnp_ctx_t rnpctx; +- rnp_ctx_init_ffi(rnpctx, ffi); +- pgp_parse_handler_t handler; +- memset(&handler, 0, sizeof(handler)); +- handler.password_provider = &ffi->pass_provider; +- handler.key_provider = &ffi->key_provider; +- handler.dest_provider = rnp_decrypt_dest_provider; +- handler.param = output; +- handler.ctx = &rnpctx; +- +- rnp_result_t ret = process_pgp_source(&handler, input->src); +- dst_flush(&output->dst); +- output->keep = (ret == RNP_SUCCESS); ++ rnp_op_verify_t op = NULL; ++ rnp_result_t ret = rnp_op_verify_create(&op, ffi, input, output); ++ if (ret) { ++ return ret; ++ } ++ ret = rnp_op_verify_set_flags(op, RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT); ++ if (!ret) { ++ ret = rnp_op_verify_execute(op); ++ } ++ rnp_op_verify_destroy(op); + return ret; + } + FFI_GUARD + + static rnp_result_t +@@ -3805,11 +3879,11 @@ try { + if (!ffi || !identifier_type || !identifier || !handle) { + return RNP_ERROR_NULL_POINTER; + } + + // figure out the identifier type +- pgp_key_search_t locator = {(pgp_key_search_type_t) 0}; ++ pgp_key_search_t locator; + rnp_result_t ret = str_to_locator(ffi, &locator, identifier_type, identifier); + if (ret) { + return ret; + } + +@@ -3925,11 +3999,17 @@ rnp_key_export_autocrypt(rnp_key_handle_ + uint32_t flags) + try { + if (!key || !output) { + return RNP_ERROR_NULL_POINTER; + } ++ bool base64 = false; ++ if (flags & RNP_KEY_EXPORT_BASE64) { ++ base64 = true; ++ flags &= ~RNP_KEY_EXPORT_BASE64; ++ } + if (flags) { ++ FFI_LOG(key->ffi, "Unknown flags remaining: 0x%X", flags); + return RNP_ERROR_BAD_PARAMETERS; + } + /* Get the primary key */ + pgp_key_t *primary = get_key_prefer_public(key); + if (!primary || !primary->is_primary() || !primary->valid() || !primary->can_sign()) { +@@ -3971,14 +4051,19 @@ try { + if (uididx >= primary->uid_count()) { + FFI_LOG(key->ffi, "Userid not found"); + return RNP_ERROR_BAD_PARAMETERS; + } + +- if (!primary->write_autocrypt(output->dst, *sub, uididx)) { +- return RNP_ERROR_BAD_PARAMETERS; +- } +- return RNP_SUCCESS; ++ /* Check whether base64 is requested */ ++ bool res = false; ++ if (base64) { ++ rnp::ArmoredDest armor(output->dst, PGP_ARMORED_BASE64); ++ res = primary->write_autocrypt(armor.dst(), *sub, uididx); ++ } else { ++ res = primary->write_autocrypt(output->dst, *sub, uididx); ++ } ++ return res ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS; + } + FFI_GUARD + + static pgp_key_t * + rnp_key_get_revoker(rnp_key_handle_t key) +@@ -4052,10 +4137,12 @@ rnp_key_export_revocation(rnp_key_handle + const char * reason) + try { + if (!key || !key->ffi || !output) { + return RNP_ERROR_NULL_POINTER; + } ++ bool need_armor = flags & RNP_KEY_EXPORT_ARMORED; ++ flags &= ~RNP_KEY_EXPORT_ARMORED; + if (flags) { + return RNP_ERROR_BAD_PARAMETERS; + } + + pgp_key_t *exkey = get_key_prefer_public(key); +@@ -4073,13 +4160,20 @@ try { + rnp_key_get_revocation(key->ffi, exkey, revoker, hash, code, reason, sig); + if (ret) { + return ret; + } + +- sig.write(output->dst); +- ret = output->dst.werr; +- dst_flush(&output->dst); ++ if (need_armor) { ++ rnp::ArmoredDest armor(output->dst, PGP_ARMORED_PUBLIC_KEY); ++ sig.write(armor.dst()); ++ ret = armor.werr(); ++ dst_flush(&armor.dst()); ++ } else { ++ sig.write(output->dst); ++ ret = output->dst.werr; ++ dst_flush(&output->dst); ++ } + output->keep = !ret; + return ret; + } + FFI_GUARD + +@@ -4156,11 +4250,11 @@ try { + } + if (!x25519_tweak_bits(seckey->pkt().material.ec)) { + FFI_LOG(key->ffi, "Failed to tweak 25519 key bits."); + return RNP_ERROR_BAD_STATE; + } +- if (!seckey->write_sec_rawpkt(seckey->pkt(), "", key->ffi->rng())) { ++ if (!seckey->write_sec_rawpkt(seckey->pkt(), "", key->ffi->context)) { + FFI_LOG(key->ffi, "Failed to update rawpkt."); + return RNP_ERROR_BAD_STATE; + } + return RNP_SUCCESS; + } +@@ -4460,11 +4554,11 @@ parse_preferences(json_object *jso, pgp_ + } + return true; + } + + static bool +-parse_keygen_crypto(json_object *jso, rnp_keygen_crypto_params_t *crypto) ++parse_keygen_crypto(json_object *jso, rnp_keygen_crypto_params_t &crypto) + { + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"type", json_type_string}, +@@ -4483,37 +4577,37 @@ parse_keygen_crypto(json_object *jso, rn + if (!json_object_is_type(value, properties[i].type)) { + return false; + } + // TODO: make sure there are no duplicate keys in the JSON + if (rnp::str_case_eq(key, "type")) { +- if (!str_to_pubkey_alg(json_object_get_string(value), &crypto->key_alg)) { ++ if (!str_to_pubkey_alg(json_object_get_string(value), &crypto.key_alg)) { + return false; + } + } else if (rnp::str_case_eq(key, "length")) { + int length = json_object_get_int(value); +- switch (crypto->key_alg) { ++ switch (crypto.key_alg) { + case PGP_PKA_RSA: +- crypto->rsa.modulus_bit_len = length; ++ crypto.rsa.modulus_bit_len = length; + break; + case PGP_PKA_DSA: +- crypto->dsa.p_bitlen = length; ++ crypto.dsa.p_bitlen = length; + break; + case PGP_PKA_ELGAMAL: +- crypto->elgamal.key_bitlen = length; ++ crypto.elgamal.key_bitlen = length; + break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "curve")) { +- if (!pk_alg_allows_custom_curve(crypto->key_alg)) { ++ if (!pk_alg_allows_custom_curve(crypto.key_alg)) { + return false; + } +- if (!curve_str_to_type(json_object_get_string(value), &crypto->ecc.curve)) { ++ if (!curve_str_to_type(json_object_get_string(value), &crypto.ecc.curve)) { + return false; + } + } else if (rnp::str_case_eq(key, "hash")) { +- if (!str_to_hash_alg(json_object_get_string(value), &crypto->hash_alg)) { ++ if (!str_to_hash_alg(json_object_get_string(value), &crypto.hash_alg)) { + return false; + } + } else { + // shouldn't happen + return false; +@@ -4523,11 +4617,11 @@ parse_keygen_crypto(json_object *jso, rn + } + return true; + } + + static bool +-parse_protection(json_object *jso, rnp_key_protection_params_t *protection) ++parse_protection(json_object *jso, rnp_key_protection_params_t &protection) + { + static const struct { + const char * key; + enum json_type type; + } properties[] = {{"cipher", json_type_string}, +@@ -4546,21 +4640,21 @@ parse_protection(json_object *jso, rnp_k + if (!json_object_is_type(value, properties[i].type)) { + return false; + } + // TODO: make sure there are no duplicate keys in the JSON + if (rnp::str_case_eq(key, "cipher")) { +- if (!str_to_cipher(json_object_get_string(value), &protection->symm_alg)) { ++ if (!str_to_cipher(json_object_get_string(value), &protection.symm_alg)) { + return false; + } + } else if (rnp::str_case_eq(key, "mode")) { +- if (!str_to_cipher_mode(json_object_get_string(value), &protection->cipher_mode)) { ++ if (!str_to_cipher_mode(json_object_get_string(value), &protection.cipher_mode)) { + return false; + } + } else if (rnp::str_case_eq(key, "iterations")) { +- protection->iterations = json_object_get_int(value); ++ protection.iterations = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "hash")) { +- if (!str_to_hash_alg(json_object_get_string(value), &protection->hash_alg)) { ++ if (!str_to_hash_alg(json_object_get_string(value), &protection.hash_alg)) { + return false; + } + } else { + // shouldn't happen + return false; +@@ -4570,17 +4664,19 @@ parse_protection(json_object *jso, rnp_k + } + return true; + } + + static bool +-parse_keygen_primary(json_object *jso, rnp_action_keygen_t *desc) ++parse_keygen_primary(json_object * jso, ++ rnp_keygen_primary_desc_t & desc, ++ rnp_key_protection_params_t &prot) + { + static const char *properties[] = { + "userid", "usage", "expiration", "preferences", "protection"}; +- rnp_selfsig_cert_info_t *cert = &desc->primary.keygen.cert; +- +- if (!parse_keygen_crypto(jso, &desc->primary.keygen.crypto)) { ++ auto &cert = desc.cert; ++ ++ if (!parse_keygen_crypto(jso, desc.crypto)) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i]; +@@ -4590,16 +4686,15 @@ parse_keygen_primary(json_object *jso, r + } + if (rnp::str_case_eq(key, "userid")) { + if (!json_object_is_type(value, json_type_string)) { + return false; + } +- const char *userid = json_object_get_string(value); +- size_t userid_len = strlen(userid); +- if (userid_len >= sizeof(cert->userid)) { ++ auto uid = json_object_get_string(value); ++ if (strlen(uid) > MAX_ID_LENGTH) { + return false; + } +- memcpy(cert->userid, userid, userid_len + 1); ++ cert.userid = json_object_get_string(value); + } else if (rnp::str_case_eq(key, "usage")) { + switch (json_object_get_type(value)) { + case json_type_array: { + int length = json_object_array_length(value); + for (int j = 0; j < length; j++) { +@@ -4610,63 +4705,65 @@ parse_keygen_primary(json_object *jso, r + uint8_t flag = 0; + if (!str_to_key_flag(json_object_get_string(item), &flag)) { + return false; + } + // check for duplicate +- if (cert->key_flags & flag) { ++ if (cert.key_flags & flag) { + return false; + } +- cert->key_flags |= flag; ++ cert.key_flags |= flag; + } + } break; + case json_type_string: { +- if (!str_to_key_flag(json_object_get_string(value), &cert->key_flags)) { ++ if (!str_to_key_flag(json_object_get_string(value), &cert.key_flags)) { + return false; + } + } break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "expiration")) { + if (!json_object_is_type(value, json_type_int)) { + return false; + } +- cert->key_expiration = json_object_get_int(value); ++ cert.key_expiration = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "preferences")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } +- if (!parse_preferences(value, cert->prefs)) { ++ if (!parse_preferences(value, cert.prefs)) { + return false; + } +- if (json_object_object_length(value) != 0) { ++ if (json_object_object_length(value)) { + return false; + } + } else if (rnp::str_case_eq(key, "protection")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } +- if (!parse_protection(value, &desc->primary.protection)) { ++ if (!parse_protection(value, prot)) { + return false; + } +- if (json_object_object_length(value) != 0) { ++ if (json_object_object_length(value)) { + return false; + } + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } +- return json_object_object_length(jso) == 0; ++ return !json_object_object_length(jso); + } + + static bool +-parse_keygen_sub(json_object *jso, rnp_action_keygen_t *desc) +-{ +- static const char * properties[] = {"usage", "expiration", "protection"}; +- rnp_selfsig_binding_info_t *binding = &desc->subkey.keygen.binding; +- +- if (!parse_keygen_crypto(jso, &desc->subkey.keygen.crypto)) { ++parse_keygen_sub(json_object * jso, ++ rnp_keygen_subkey_desc_t & desc, ++ rnp_key_protection_params_t &prot) ++{ ++ static const char *properties[] = {"usage", "expiration", "protection"}; ++ auto & binding = desc.binding; ++ ++ if (!parse_keygen_crypto(jso, desc.crypto)) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(properties); i++) { + json_object *value = NULL; + const char * key = properties[i]; +@@ -4685,132 +4782,196 @@ parse_keygen_sub(json_object *jso, rnp_a + } + uint8_t flag = 0; + if (!str_to_key_flag(json_object_get_string(item), &flag)) { + return false; + } +- if (binding->key_flags & flag) { ++ if (binding.key_flags & flag) { + return false; + } +- binding->key_flags |= flag; ++ binding.key_flags |= flag; + } + } break; + case json_type_string: { +- if (!str_to_key_flag(json_object_get_string(value), &binding->key_flags)) { ++ if (!str_to_key_flag(json_object_get_string(value), &binding.key_flags)) { + return false; + } + } break; + default: + return false; + } + } else if (rnp::str_case_eq(key, "expiration")) { + if (!json_object_is_type(value, json_type_int)) { + return false; + } +- binding->key_expiration = json_object_get_int(value); ++ binding.key_expiration = json_object_get_int(value); + } else if (rnp::str_case_eq(key, "protection")) { + if (!json_object_is_type(value, json_type_object)) { + return false; + } +- if (!parse_protection(value, &desc->subkey.protection)) { ++ if (!parse_protection(value, prot)) { + return false; + } +- if (json_object_object_length(value) != 0) { ++ if (json_object_object_length(value)) { + return false; + } + } + // delete this field since it has been handled + json_object_object_del(jso, key); + } +- return json_object_object_length(jso) == 0; ++ return !json_object_object_length(jso); + } + + static bool + gen_json_grips(char **result, const pgp_key_t *primary, const pgp_key_t *sub) + { +- bool ret = false; +- json_object *jso = NULL; +- char grip[PGP_KEY_GRIP_SIZE * 2 + 1]; +- + if (!result) { +- return false; +- } +- +- jso = json_object_new_object(); ++ return true; ++ } ++ ++ json_object *jso = json_object_new_object(); + if (!jso) { + return false; + } +- ++ rnp::JSONObject jsowrap(jso); ++ ++ char grip[PGP_KEY_GRIP_SIZE * 2 + 1]; + if (primary) { + json_object *jsoprimary = json_object_new_object(); + if (!jsoprimary) { +- goto done; ++ return false; + } + json_object_object_add(jso, "primary", jsoprimary); + if (!rnp::hex_encode( + primary->grip().data(), primary->grip().size(), grip, sizeof(grip))) { +- goto done; ++ return false; + } + json_object *jsogrip = json_object_new_string(grip); + if (!jsogrip) { +- goto done; ++ return false; + } + json_object_object_add(jsoprimary, "grip", jsogrip); + } + if (sub) { + json_object *jsosub = json_object_new_object(); + if (!jsosub) { +- goto done; ++ return false; + } + json_object_object_add(jso, "sub", jsosub); + if (!rnp::hex_encode(sub->grip().data(), sub->grip().size(), grip, sizeof(grip))) { +- goto done; ++ return false; + } + json_object *jsogrip = json_object_new_string(grip); + if (!jsogrip) { +- goto done; ++ return false; + } + json_object_object_add(jsosub, "grip", jsogrip); + } + *result = strdup(json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY)); +- +- ret = true; +-done: +- json_object_put(jso); +- return ret; ++ return *result; ++} ++ ++static rnp_result_t ++gen_json_primary_key(rnp_ffi_t ffi, ++ json_object * jsoparams, ++ rnp_key_protection_params_t &prot, ++ pgp_fingerprint_t & fp, ++ bool protect) ++{ ++ rnp_keygen_primary_desc_t desc = {}; ++ ++ desc.cert.key_expiration = DEFAULT_KEY_EXPIRATION; ++ if (!parse_keygen_primary(jsoparams, desc, prot)) { ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ ++ pgp_key_t pub; ++ pgp_key_t sec; ++ desc.crypto.ctx = &ffi->context; ++ if (!pgp_generate_primary_key(desc, true, sec, pub, ffi->secring->format)) { ++ return RNP_ERROR_GENERIC; ++ } ++ if (!rnp_key_store_add_key(ffi->pubring, &pub)) { ++ return RNP_ERROR_OUT_OF_MEMORY; ++ } ++ /* encrypt secret key if specified */ ++ if (protect && prot.symm_alg && !sec.protect(prot, ffi->pass_provider, ffi->context)) { ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ if (!rnp_key_store_add_key(ffi->secring, &sec)) { ++ return RNP_ERROR_OUT_OF_MEMORY; ++ } ++ fp = pub.fp(); ++ return RNP_SUCCESS; ++} ++ ++static rnp_result_t ++gen_json_subkey(rnp_ffi_t ffi, ++ json_object * jsoparams, ++ pgp_key_t & prim_pub, ++ pgp_key_t & prim_sec, ++ pgp_fingerprint_t &fp) ++{ ++ rnp_keygen_subkey_desc_t desc = {}; ++ rnp_key_protection_params_t prot = {}; ++ ++ desc.binding.key_expiration = DEFAULT_KEY_EXPIRATION; ++ if (!parse_keygen_sub(jsoparams, desc, prot)) { ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ if (!desc.binding.key_flags) { ++ /* Generate encrypt-only subkeys by default */ ++ desc.binding.key_flags = PGP_KF_ENCRYPT; ++ } ++ pgp_key_t pub; ++ pgp_key_t sec; ++ desc.crypto.ctx = &ffi->context; ++ if (!pgp_generate_subkey(desc, ++ true, ++ prim_sec, ++ prim_pub, ++ sec, ++ pub, ++ ffi->pass_provider, ++ ffi->secring->format)) { ++ return RNP_ERROR_GENERIC; ++ } ++ if (!rnp_key_store_add_key(ffi->pubring, &pub)) { ++ return RNP_ERROR_OUT_OF_MEMORY; ++ } ++ /* encrypt subkey if specified */ ++ if (prot.symm_alg && !sec.protect(prot, ffi->pass_provider, ffi->context)) { ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ if (!rnp_key_store_add_key(ffi->secring, &sec)) { ++ return RNP_ERROR_OUT_OF_MEMORY; ++ } ++ fp = pub.fp(); ++ return RNP_SUCCESS; + } + + rnp_result_t + rnp_generate_key_json(rnp_ffi_t ffi, const char *json, char **results) + try { +- rnp_result_t ret = RNP_ERROR_GENERIC; +- json_object * jso = NULL; +- rnp_action_keygen_t keygen_desc = {}; +- char * identifier_type = NULL; +- char * identifier = NULL; +- pgp_key_t primary_pub; +- pgp_key_t primary_sec; +- pgp_key_t sub_pub; +- pgp_key_t sub_sec; +- json_object * jsoprimary = NULL; +- json_object * jsosub = NULL; +- json_tokener_error error; +- + // checks + if (!ffi || !ffi->secring || !json) { + return RNP_ERROR_NULL_POINTER; + } + + // parse the JSON +- jso = json_tokener_parse_verbose(json, &error); ++ json_tokener_error error; ++ json_object * jso = json_tokener_parse_verbose(json, &error); + if (!jso) { + // syntax error or some other issue + FFI_LOG(ffi, "Invalid JSON: %s", json_tokener_error_desc(error)); +- ret = RNP_ERROR_BAD_FORMAT; +- goto done; +- } ++ return RNP_ERROR_BAD_FORMAT; ++ } ++ rnp::JSONObject jsowrap(jso); + + // locate the appropriate sections ++ rnp_result_t ret = RNP_ERROR_GENERIC; ++ json_object *jsoprimary = NULL; ++ json_object *jsosub = NULL; + { + json_object_object_foreach(jso, key, value) + { + json_object **dest = NULL; + +@@ -4819,199 +4980,99 @@ try { + } else if (rnp::str_case_eq(key, "sub")) { + dest = &jsosub; + } else { + // unrecognized key in the object + FFI_LOG(ffi, "Unexpected key in JSON: %s", key); +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; ++ return RNP_ERROR_BAD_PARAMETERS; + } + + // duplicate "primary"/"sub" + if (*dest) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; ++ return RNP_ERROR_BAD_PARAMETERS; + } + *dest = value; + } + } + +- if (jsoprimary && jsosub) { // generating primary+sub +- if (!parse_keygen_primary(jsoprimary, &keygen_desc) || +- !parse_keygen_sub(jsosub, &keygen_desc)) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; +- } +- keygen_desc.primary.keygen.crypto.ctx = &ffi->context; +- keygen_desc.subkey.keygen.crypto.ctx = &ffi->context; +- if (!pgp_generate_keypair(keygen_desc.primary.keygen, +- keygen_desc.subkey.keygen, +- true, +- primary_sec, +- primary_pub, +- sub_sec, +- sub_pub, +- ffi->secring->format)) { +- goto done; +- } +- if (results && !gen_json_grips(results, &primary_pub, &sub_pub)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- if (ffi->pubring) { +- if (!rnp_key_store_add_key(ffi->pubring, &primary_pub)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- if (!rnp_key_store_add_key(ffi->pubring, &sub_pub)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; ++ if (!jsoprimary && !jsosub) { ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ ++ // generate primary key ++ pgp_key_t * prim_pub = NULL; ++ pgp_key_t * prim_sec = NULL; ++ rnp_key_protection_params_t prim_prot = {}; ++ pgp_fingerprint_t fp; ++ if (jsoprimary) { ++ ret = gen_json_primary_key(ffi, jsoprimary, prim_prot, fp, !jsosub); ++ if (ret) { ++ return ret; ++ } ++ prim_pub = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); ++ if (!jsosub) { ++ if (!gen_json_grips(results, prim_pub, NULL)) { ++ return RNP_ERROR_OUT_OF_MEMORY; + } +- } +- /* add key/subkey protection */ +- if (keygen_desc.primary.protection.symm_alg && +- !primary_sec.protect( +- keygen_desc.primary.protection, ffi->pass_provider, ffi->rng())) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; +- } +- +- if (keygen_desc.subkey.protection.symm_alg && +- !sub_sec.protect(keygen_desc.subkey.protection, ffi->pass_provider, ffi->rng())) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; +- } +- +- if (!rnp_key_store_add_key(ffi->secring, &primary_sec)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- if (!rnp_key_store_add_key(ffi->secring, &sub_sec)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- } else if (jsoprimary && !jsosub) { // generating primary only +- keygen_desc.primary.keygen.crypto.ctx = &ffi->context; +- if (!parse_keygen_primary(jsoprimary, &keygen_desc)) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; +- } +- if (!pgp_generate_primary_key(keygen_desc.primary.keygen, +- true, +- primary_sec, +- primary_pub, +- ffi->secring->format)) { +- goto done; +- } +- if (results && !gen_json_grips(results, &primary_pub, NULL)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- if (ffi->pubring) { +- if (!rnp_key_store_add_key(ffi->pubring, &primary_pub)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- } +- /* encrypt secret key if specified */ +- if (keygen_desc.primary.protection.symm_alg && +- !primary_sec.protect( +- keygen_desc.primary.protection, ffi->pass_provider, ffi->rng())) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; +- } +- +- if (!rnp_key_store_add_key(ffi->secring, &primary_sec)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- } else if (jsosub) { // generating subkey only +- if (!ffi->pubring) { +- ret = RNP_ERROR_NULL_POINTER; +- goto done; +- } ++ return RNP_SUCCESS; ++ } ++ prim_sec = rnp_key_store_get_key_by_fpr(ffi->secring, fp); ++ } else { ++ /* generate subkey only - find primary key via JSON params */ + json_object *jsoparent = NULL; + if (!json_object_object_get_ex(jsosub, "primary", &jsoparent) || + json_object_object_length(jsoparent) != 1) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; +- } ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ const char *identifier_type = NULL; ++ const char *identifier = NULL; + json_object_object_foreach(jsoparent, key, value) + { + if (!json_object_is_type(value, json_type_string)) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; ++ return RNP_ERROR_BAD_PARAMETERS; + } +- identifier_type = strdup(key); +- identifier = strdup(json_object_get_string(value)); ++ identifier_type = key; ++ identifier = json_object_get_string(value); + } + if (!identifier_type || !identifier) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- json_object_object_del(jsosub, "primary"); +- +- pgp_key_search_t locator = {(pgp_key_search_type_t) 0}; ++ return RNP_ERROR_BAD_STATE; ++ } ++ ++ pgp_key_search_t locator; + rnp_result_t tmpret = str_to_locator(ffi, &locator, identifier_type, identifier); + if (tmpret) { +- ret = tmpret; +- goto done; +- } +- +- pgp_key_t *primary_pub = rnp_key_store_search(ffi->pubring, &locator, NULL); +- pgp_key_t *primary_sec = rnp_key_store_search(ffi->secring, &locator, NULL); +- if (!primary_sec || !primary_pub) { +- ret = RNP_ERROR_KEY_NOT_FOUND; +- goto done; +- } +- if (!parse_keygen_sub(jsosub, &keygen_desc)) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; +- } +- keygen_desc.subkey.keygen.crypto.ctx = &ffi->context; +- if (!pgp_generate_subkey(keygen_desc.subkey.keygen, +- true, +- *primary_sec, +- *primary_pub, +- sub_sec, +- sub_pub, +- ffi->pass_provider, +- ffi->secring->format)) { +- goto done; +- } +- if (results && !gen_json_grips(results, NULL, &sub_pub)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- if (ffi->pubring) { +- if (!rnp_key_store_add_key(ffi->pubring, &sub_pub)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- } +- /* encrypt subkey if specified */ +- if (keygen_desc.subkey.protection.symm_alg && +- !sub_sec.protect(keygen_desc.subkey.protection, ffi->pass_provider, ffi->rng())) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; +- } +- +- if (!rnp_key_store_add_key(ffi->secring, &sub_sec)) { +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto done; +- } +- } else { +- // nothing to generate... +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; +- } +- +- ret = RNP_SUCCESS; +-done: +- json_object_put(jso); +- free(identifier_type); +- free(identifier); +- return ret; ++ return tmpret; ++ } ++ ++ prim_pub = rnp_key_store_search(ffi->pubring, &locator, NULL); ++ prim_sec = rnp_key_store_search(ffi->secring, &locator, NULL); ++ if (!prim_sec || !prim_pub) { ++ return RNP_ERROR_KEY_NOT_FOUND; ++ } ++ json_object_object_del(jsosub, "primary"); ++ } ++ ++ /* Generate subkey */ ++ ret = gen_json_subkey(ffi, jsosub, *prim_pub, *prim_sec, fp); ++ if (ret) { ++ if (jsoprimary) { ++ /* do not leave generated primary key in keyring */ ++ rnp_key_store_remove_key(ffi->pubring, prim_pub, false); ++ rnp_key_store_remove_key(ffi->secring, prim_sec, false); ++ } ++ return ret; ++ } ++ /* Protect the primary key now */ ++ if (prim_prot.symm_alg && ++ !prim_sec->protect(prim_prot, ffi->pass_provider, ffi->context)) { ++ rnp_key_store_remove_key(ffi->pubring, prim_pub, true); ++ rnp_key_store_remove_key(ffi->secring, prim_sec, true); ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ ++ pgp_key_t *sub_pub = rnp_key_store_get_key_by_fpr(ffi->pubring, fp); ++ bool res = gen_json_grips(results, jsoprimary ? prim_pub : NULL, sub_pub); ++ return res ? RNP_SUCCESS : RNP_ERROR_OUT_OF_MEMORY; + } + FFI_GUARD + + rnp_result_t + rnp_generate_key_ex(rnp_ffi_t ffi, +@@ -5234,10 +5295,11 @@ try { + (*op)->ffi = ffi; + (*op)->primary = true; + (*op)->crypto.key_alg = key_alg; + (*op)->crypto.ctx = &ffi->context; + (*op)->cert.key_flags = default_key_flags(key_alg, false); ++ (*op)->cert.key_expiration = DEFAULT_KEY_EXPIRATION; + + return RNP_SUCCESS; + } + FFI_GUARD + +@@ -5278,10 +5340,11 @@ try { + (*op)->ffi = ffi; + (*op)->primary = false; + (*op)->crypto.key_alg = key_alg; + (*op)->crypto.ctx = &ffi->context; + (*op)->binding.key_flags = default_key_flags(key_alg, true); ++ (*op)->binding.key_expiration = DEFAULT_KEY_EXPIRATION; + (*op)->primary_sec = primary->sec; + (*op)->primary_pub = primary->pub; + + return RNP_SUCCESS; + } +@@ -5474,15 +5537,14 @@ try { + return RNP_ERROR_NULL_POINTER; + } + if (!op->primary) { + return RNP_ERROR_BAD_PARAMETERS; + } +- size_t userid_len = strlen(userid); +- if (userid_len >= sizeof(op->cert.userid)) { +- return RNP_ERROR_BAD_PARAMETERS; +- } +- memcpy(op->cert.userid, userid, userid_len + 1); ++ if (strlen(userid) > MAX_ID_LENGTH) { ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ op->cert.userid = userid; + return RNP_SUCCESS; + } + FFI_GUARD + + rnp_result_t +@@ -5618,11 +5680,11 @@ try { + } + + rnp_result_t ret = RNP_ERROR_GENERIC; + pgp_key_t pub; + pgp_key_t sec; +- pgp_password_provider_t prov = {.callback = NULL}; ++ pgp_password_provider_t prov; + + if (op->primary) { + rnp_keygen_primary_desc_t keygen = {}; + keygen.crypto = op->crypto; + keygen.cert = op->cert; +@@ -5654,16 +5716,15 @@ try { + goto done; + } + + /* encrypt secret key if requested */ + if (!op->password.empty()) { +- prov = {.callback = rnp_password_provider_string, +- .userdata = (void *) op->password.data()}; ++ prov = {rnp_password_provider_string, (void *) op->password.data()}; + } else if (op->request_password) { +- prov = {.callback = rnp_password_cb_bounce, .userdata = op->ffi}; +- } +- if (prov.callback && !sec.protect(op->protection, prov, op->ffi->rng())) { ++ prov = {rnp_password_cb_bounce, op->ffi}; ++ } ++ if (prov.callback && !sec.protect(op->protection, prov, op->ffi->context)) { + FFI_LOG(op->ffi, "failed to encrypt the key"); + ret = RNP_ERROR_BAD_PARAMETERS; + goto done; + } + +@@ -5740,11 +5801,11 @@ rnp_buffer_clear(void *ptr, size_t size) + } + + static pgp_key_t * + get_key_require_public(rnp_key_handle_t handle) + { +- if (!handle->pub) { ++ if (!handle->pub && handle->sec) { + pgp_key_request_ctx_t request; + request.secret = false; + + // try fingerprint + request.search.type = PGP_KEY_SEARCH_FINGERPRINT; +@@ -5770,11 +5831,11 @@ get_key_prefer_public(rnp_key_handle_t h + } + + static pgp_key_t * + get_key_require_secret(rnp_key_handle_t handle) + { +- if (!handle->sec) { ++ if (!handle->sec && handle->pub) { + pgp_key_request_ctx_t request; + request.secret = true; + + // try fingerprint + request.search.type = PGP_KEY_SEARCH_FINGERPRINT; +@@ -5827,17 +5888,16 @@ try { + if (!str_to_hash_alg(hash, &hash_alg)) { + FFI_LOG(handle->ffi, "Invalid hash: %s", hash); + return RNP_ERROR_BAD_PARAMETERS; + } + +- rnp_selfsig_cert_info_t info = {}; +- size_t uid_len = strlen(uid); +- if (uid_len >= sizeof(info.userid)) { ++ if (strlen(uid) > MAX_ID_LENGTH) { + FFI_LOG(handle->ffi, "UserID too long"); + return RNP_ERROR_BAD_PARAMETERS; + } +- memcpy(info.userid, uid, uid_len + 1); ++ rnp_selfsig_cert_info_t info; ++ info.userid = uid; + info.key_flags = key_flags; + info.key_expiration = expiration; + info.primary = primary; + + /* obtain and unlok secret key */ +@@ -6203,21 +6263,38 @@ try { + return hex_encode_value(keyid.data(), keyid.size(), result); + } + FFI_GUARD + + rnp_result_t ++rnp_signature_get_key_fprint(rnp_signature_handle_t handle, char **result) ++try { ++ if (!handle || !result) { ++ return RNP_ERROR_NULL_POINTER; ++ } ++ if (!handle->sig) { ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ if (!handle->sig->sig.has_keyfp()) { ++ *result = NULL; ++ return RNP_SUCCESS; ++ } ++ pgp_fingerprint_t keyfp = handle->sig->sig.keyfp(); ++ return hex_encode_value(keyfp.fingerprint, keyfp.length, result); ++} ++FFI_GUARD ++ ++rnp_result_t + rnp_signature_get_signer(rnp_signature_handle_t sig, rnp_key_handle_t *key) + try { + if (!sig || !sig->sig) { + return RNP_ERROR_BAD_PARAMETERS; + } + if (!sig->sig->sig.has_keyid()) { + *key = NULL; + return RNP_SUCCESS; + } +- pgp_key_search_t locator = {}; +- locator.type = PGP_KEY_SEARCH_KEYID; ++ pgp_key_search_t locator(PGP_KEY_SEARCH_KEYID); + locator.by.keyid = sig->sig->sig.keyid(); + return rnp_locate_key_int(sig->ffi, locator, key); + } + FFI_GUARD + +@@ -6255,33 +6332,15 @@ rnp_signature_packet_to_json(rnp_signatu + try { + if (!sig || !json) { + return RNP_ERROR_NULL_POINTER; + } + +- pgp_dest_t memdst = {}; +- if (init_mem_dest(&memdst, NULL, 0)) { +- return RNP_ERROR_OUT_OF_MEMORY; +- } +- try { +- sig->sig->sig.write(memdst); +- } catch (const std::exception &e) { +- FFI_LOG(sig->ffi, "%s", e.what()); +- dst_close(&memdst, true); +- return RNP_ERROR_BAD_PARAMETERS; +- } +- +- pgp_source_t memsrc = {}; +- rnp_result_t ret = RNP_ERROR_BAD_STATE; +- if (init_mem_src(&memsrc, mem_dest_get_memory(&memdst), memdst.writeb, false)) { +- goto done; +- } +- +- ret = rnp_dump_src_to_json(&memsrc, flags, json); +-done: +- dst_close(&memdst, true); +- src_close(&memsrc); +- return ret; ++ rnp::MemoryDest memdst; ++ sig->sig->sig.write(memdst.dst()); ++ auto vec = memdst.to_vector(); ++ rnp::MemorySource memsrc(vec); ++ return rnp_dump_src_to_json(&memsrc.src(), flags, json); + } + FFI_GUARD + + rnp_result_t + rnp_signature_remove(rnp_key_handle_t key, rnp_signature_handle_t sig) +@@ -6423,12 +6482,11 @@ try { + } + pgp_key_t *key = get_key_prefer_public(handle); + if (idx >= key->subkey_count()) { + return RNP_ERROR_BAD_PARAMETERS; + } +- pgp_key_search_t locator = {}; +- locator.type = PGP_KEY_SEARCH_FINGERPRINT; ++ pgp_key_search_t locator(PGP_KEY_SEARCH_FINGERPRINT); + locator.by.fingerprint = key->get_subkey_fp(idx); + return rnp_locate_key_int(handle->ffi, locator, subkey); + } + FFI_GUARD + +@@ -6463,12 +6521,11 @@ try { + if (!defkey) { + *default_key = NULL; + return RNP_ERROR_NO_SUITABLE_KEY; + } + +- pgp_key_search_t search = {(pgp_key_search_type_t) 0}; +- search.type = PGP_KEY_SEARCH_FINGERPRINT; ++ pgp_key_search_t search(PGP_KEY_SEARCH_FINGERPRINT); + search.by.fingerprint = defkey->fp(); + + bool require_secret = keyflag != PGP_KF_ENCRYPT; + rnp_result_t ret = + rnp_locate_key_int(primary_key->ffi, search, default_key, require_secret); +@@ -6821,12 +6878,11 @@ try { + if (!pkey->has_primary_fp()) { + FFI_LOG(key->ffi, "Primary key fp not available."); + return RNP_ERROR_BAD_PARAMETERS; + } + +- pgp_key_search_t search = {}; +- search.type = PGP_KEY_SEARCH_FINGERPRINT; ++ pgp_key_search_t search(PGP_KEY_SEARCH_FINGERPRINT); + search.by.fingerprint = pkey->primary_fp(); + pgp_key_t *prim_sec = find_key(key->ffi, &search, KEY_TYPE_SECRET, true); + if (!prim_sec) { + FFI_LOG(key->ffi, "Primary secret key not found."); + return RNP_ERROR_KEY_NOT_FOUND; +@@ -6898,10 +6954,25 @@ try { + return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_RETIRED); + } + FFI_GUARD + + rnp_result_t ++rnp_key_is_expired(rnp_key_handle_t handle, bool *result) ++try { ++ if (!handle || !result) { ++ return RNP_ERROR_NULL_POINTER; ++ } ++ pgp_key_t *key = get_key_prefer_public(handle); ++ if (!key) { ++ return RNP_ERROR_BAD_PARAMETERS; ++ } ++ *result = key->expired(); ++ return RNP_SUCCESS; ++} ++FFI_GUARD ++ ++rnp_result_t + rnp_key_get_protection_type(rnp_key_handle_t key, char **type) + try { + if (!key || !type) { + return RNP_ERROR_NULL_POINTER; + } +@@ -7060,13 +7131,12 @@ try { + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + bool ok = false; + if (password) { +- pgp_password_provider_t prov = { +- .callback = rnp_password_provider_string, +- .userdata = reinterpret_cast(const_cast(password))}; ++ pgp_password_provider_t prov(rnp_password_provider_string, ++ reinterpret_cast(const_cast(password))); + ok = key->unlock(prov); + } else { + ok = key->unlock(handle->ffi->pass_provider); + } + if (!ok) { +@@ -7127,18 +7197,18 @@ try { + return RNP_ERROR_NO_SUITABLE_KEY; + } + pgp_key_pkt_t * decrypted_key = NULL; + const std::string pass = password; + if (key->encrypted()) { +- pgp_password_ctx_t ctx = {.op = PGP_OP_PROTECT, .key = key}; ++ pgp_password_ctx_t ctx(PGP_OP_PROTECT, key); + decrypted_key = pgp_decrypt_seckey(*key, handle->ffi->pass_provider, ctx); + if (!decrypted_key) { + return RNP_ERROR_GENERIC; + } + } + bool res = key->protect( +- decrypted_key ? *decrypted_key : key->pkt(), protection, pass, handle->ffi->rng()); ++ decrypted_key ? *decrypted_key : key->pkt(), protection, pass, handle->ffi->context); + delete decrypted_key; + return res ? RNP_SUCCESS : RNP_ERROR_GENERIC; + } + FFI_GUARD + +@@ -7155,16 +7225,15 @@ try { + if (!key) { + return RNP_ERROR_NO_SUITABLE_KEY; + } + bool ok = false; + if (password) { +- pgp_password_provider_t prov = { +- .callback = rnp_password_provider_string, +- .userdata = reinterpret_cast(const_cast(password))}; +- ok = key->unprotect(prov, handle->ffi->rng()); ++ pgp_password_provider_t prov(rnp_password_provider_string, ++ reinterpret_cast(const_cast(password))); ++ ok = key->unprotect(prov, handle->ffi->context); + } else { +- ok = key->unprotect(handle->ffi->pass_provider, handle->ffi->rng()); ++ ok = key->unprotect(handle->ffi->pass_provider, handle->ffi->context); + } + if (!ok) { + // likely a bad password + return RNP_ERROR_BAD_PASSWORD; + } +@@ -7226,28 +7295,17 @@ try { + FFI_GUARD + + static rnp_result_t + key_to_bytes(pgp_key_t *key, uint8_t **buf, size_t *buf_len) + { +- pgp_dest_t memdst = {}; +- +- if (init_mem_dest(&memdst, NULL, 0)) { ++ auto vec = rnp_key_to_vec(*key); ++ *buf = (uint8_t *) calloc(1, vec.size()); ++ if (!*buf) { + return RNP_ERROR_OUT_OF_MEMORY; + } +- +- key->write(memdst); +- if (memdst.werr) { +- dst_close(&memdst, true); +- return RNP_ERROR_OUT_OF_MEMORY; +- } +- +- *buf_len = memdst.writeb; +- *buf = (uint8_t *) mem_dest_own_memory(&memdst); +- dst_close(&memdst, true); +- if (*buf_len && !*buf) { +- return RNP_ERROR_OUT_OF_MEMORY; +- } ++ memcpy(*buf, vec.data(), vec.size()); ++ *buf_len = vec.size(); + return RNP_SUCCESS; + } + + rnp_result_t + rnp_get_public_key_data(rnp_key_handle_t handle, uint8_t **buf, size_t *buf_len) +@@ -7977,44 +8035,22 @@ done: + } + + rnp_result_t + rnp_key_packets_to_json(rnp_key_handle_t handle, bool secret, uint32_t flags, char **result) + try { +- pgp_key_t * key = NULL; +- rnp_result_t ret = RNP_ERROR_GENERIC; +- pgp_dest_t memdst = {}; +- pgp_source_t memsrc = {}; +- + if (!handle || !result) { + return RNP_ERROR_NULL_POINTER; + } + +- key = secret ? handle->sec : handle->pub; ++ pgp_key_t *key = secret ? handle->sec : handle->pub; + if (!key || (key->format == PGP_KEY_STORE_G10)) { + return RNP_ERROR_BAD_PARAMETERS; + } + +- if (init_mem_dest(&memdst, NULL, 0)) { +- return RNP_ERROR_OUT_OF_MEMORY; +- } +- +- key->write(memdst); +- if (memdst.werr) { +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto done; +- } +- +- if (init_mem_src(&memsrc, mem_dest_get_memory(&memdst), memdst.writeb, false)) { +- ret = RNP_ERROR_BAD_STATE; +- goto done; +- } +- +- ret = rnp_dump_src_to_json(&memsrc, flags, result); +-done: +- dst_close(&memdst, true); +- src_close(&memsrc); +- return ret; ++ auto vec = rnp_key_to_vec(*key); ++ rnp::MemorySource mem(vec); ++ return rnp_dump_src_to_json(&mem.src(), flags, result); + } + FFI_GUARD + + rnp_result_t + rnp_dump_packets_to_json(rnp_input_t input, uint32_t flags, char **result) +diff --git a/comm/third_party/rnp/src/lib/sec_profile.cpp b/third_party/rnp/src/lib/comm/sec_profile.cpp +--- a/comm/third_party/rnp/src/lib/sec_profile.cpp ++++ b/comm/third_party/rnp/src/lib/sec_profile.cpp +@@ -24,26 +24,41 @@ + * POSSIBILITY OF SUCH DAMAGE. + */ + + #include "sec_profile.hpp" + #include "types.h" ++#include "defaults.h" ++#include + #include + + namespace rnp { + bool + SecurityRule::operator==(const SecurityRule &src) const + { + return (type == src.type) && (feature == src.feature) && (from == src.from) && +- (level == src.level) && (override == src.override); ++ (level == src.level) && (override == src.override) && (action == src.action); + } + + bool + SecurityRule::operator!=(const SecurityRule &src) const + { + return !(*this == src); + } + ++bool ++SecurityRule::matches(FeatureType ftype, ++ int fval, ++ uint64_t ftime, ++ SecurityAction faction) const noexcept ++{ ++ if ((type != ftype) || (feature != fval) || (from > ftime)) { ++ return false; ++ } ++ return (action == SecurityAction::Any) || (faction == SecurityAction::Any) || ++ (action == faction); ++} ++ + size_t + SecurityProfile::size() const noexcept + { + return rules_.size(); + } +@@ -99,26 +114,32 @@ SecurityProfile::clear_rules() + { + rules_.clear(); + } + + bool +-SecurityProfile::has_rule(FeatureType type, int value, uint64_t time) const noexcept ++SecurityProfile::has_rule(FeatureType type, ++ int value, ++ uint64_t time, ++ SecurityAction action) const noexcept + { + for (auto &rule : rules_) { +- if ((rule.type == type) && (rule.feature == value) && (rule.from <= time)) { ++ if (rule.matches(type, value, time, action)) { + return true; + } + } + return false; + } + + const SecurityRule & +-SecurityProfile::get_rule(FeatureType type, int value, uint64_t time) const ++SecurityProfile::get_rule(FeatureType type, ++ int value, ++ uint64_t time, ++ SecurityAction action) const + { + const SecurityRule *res = nullptr; + for (auto &rule : rules_) { +- if ((rule.type != type) || (rule.feature != value) || (rule.from > time)) { ++ if (!rule.matches(type, value, time, action)) { + continue; + } + if (rule.override) { + return rule; + } +@@ -131,30 +152,77 @@ SecurityProfile::get_rule(FeatureType ty + } + return *res; + } + + SecurityLevel +-SecurityProfile::hash_level(pgp_hash_alg_t hash, uint64_t time) const noexcept ++SecurityProfile::hash_level(pgp_hash_alg_t hash, ++ uint64_t time, ++ SecurityAction action) const noexcept + { +- if (has_rule(FeatureType::Hash, hash, time)) { +- return get_rule(FeatureType::Hash, hash, time).level; ++ if (!has_rule(FeatureType::Hash, hash, time, action)) { ++ return def_level(); + } +- return def_level(); ++ ++ try { ++ return get_rule(FeatureType::Hash, hash, time, action).level; ++ } catch (const std::exception &e) { ++ /* this should never happen however we need to satisfy noexcept specifier */ ++ return def_level(); ++ } + } + + SecurityLevel + SecurityProfile::def_level() const + { + return SecurityLevel::Default; + }; + +-SecurityContext::SecurityContext() : rng(RNG::Type::DRBG) ++SecurityContext::SecurityContext() : time_(0), prov_state_(NULL), rng(RNG::Type::DRBG) + { +- /* Mark SHA-1 insecure since 2019-01-19, as GnuPG does */ +- profile.add_rule( +- SecurityRule(FeatureType::Hash, PGP_HASH_SHA1, SecurityLevel::Insecure, 1547856000)); ++ /* Initialize crypto provider if needed (currently only for OpenSSL 3.0) */ ++ if (!rnp::backend_init(&prov_state_)) { ++ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); ++ } ++ /* Mark SHA-1 data signature insecure since 2019-01-19, as GnuPG does */ ++ profile.add_rule({FeatureType::Hash, ++ PGP_HASH_SHA1, ++ SecurityLevel::Insecure, ++ 1547856000, ++ SecurityAction::VerifyData}); ++ /* Mark SHA-1 key signature insecure since 2024-01-19 by default */ ++ profile.add_rule({FeatureType::Hash, ++ PGP_HASH_SHA1, ++ SecurityLevel::Insecure, ++ 1705629600, ++ SecurityAction::VerifyKey}); + /* Mark MD5 insecure since 2012-01-01 */ +- profile.add_rule( +- SecurityRule(FeatureType::Hash, PGP_HASH_MD5, SecurityLevel::Insecure, 1325376000)); ++ profile.add_rule({FeatureType::Hash, PGP_HASH_MD5, SecurityLevel::Insecure, 1325376000}); + } + +-} // namespace rnp +\ No newline at end of file ++SecurityContext::~SecurityContext() ++{ ++ rnp::backend_finish(prov_state_); ++} ++ ++size_t ++SecurityContext::s2k_iterations(pgp_hash_alg_t halg) ++{ ++ if (!s2k_iterations_.count(halg)) { ++ s2k_iterations_[halg] = ++ pgp_s2k_compute_iters(halg, DEFAULT_S2K_MSEC, DEFAULT_S2K_TUNE_MSEC); ++ } ++ return s2k_iterations_[halg]; ++} ++ ++void ++SecurityContext::set_time(uint64_t time) noexcept ++{ ++ time_ = time; ++} ++ ++uint64_t ++SecurityContext::time() const noexcept ++{ ++ return time_ ? time_ : ::time(NULL); ++} ++ ++} // namespace rnp +diff --git a/comm/third_party/rnp/src/lib/sec_profile.hpp b/third_party/rnp/src/lib/comm/sec_profile.hpp +--- a/comm/third_party/rnp/src/lib/sec_profile.hpp ++++ b/comm/third_party/rnp/src/lib/sec_profile.hpp +@@ -26,30 +26,43 @@ + #ifndef RNP_SEC_PROFILE_H_ + #define RNP_SEC_PROFILE_H_ + + #include + #include ++#include + #include "repgp/repgp_def.h" + #include "crypto/rng.h" + + namespace rnp { + + enum class FeatureType { Hash, Cipher, PublicKey }; + enum class SecurityLevel { Disabled, Insecure, Default }; ++enum class SecurityAction { Any, VerifyKey, VerifyData }; + + struct SecurityRule { +- FeatureType type; +- int feature; +- SecurityLevel level; +- uint64_t from; +- bool override; ++ FeatureType type; ++ int feature; ++ SecurityLevel level; ++ uint64_t from; ++ bool override; ++ SecurityAction action; + +- SecurityRule(FeatureType ftype, int fval, SecurityLevel flevel, uint64_t ffrom = 0) +- : type(ftype), feature(fval), level(flevel), from(ffrom), override(false){}; ++ SecurityRule(FeatureType ftype, ++ int fval, ++ SecurityLevel flevel, ++ uint64_t ffrom = 0, ++ SecurityAction faction = SecurityAction::Any) ++ : type(ftype), feature(fval), level(flevel), from(ffrom), override(false), ++ action(faction){}; + + bool operator==(const SecurityRule &src) const; + bool operator!=(const SecurityRule &src) const; ++ ++ bool matches(FeatureType ftype, ++ int fval, ++ uint64_t ftime, ++ SecurityAction faction) const noexcept; + }; + + class SecurityProfile { + private: + std::vector rules_; +@@ -61,21 +74,39 @@ class SecurityProfile { + bool del_rule(const SecurityRule &rule); + void clear_rules(FeatureType type, int feature); + void clear_rules(FeatureType type); + void clear_rules(); + +- bool has_rule(FeatureType type, int value, uint64_t time) const noexcept; +- const SecurityRule &get_rule(FeatureType type, int value, uint64_t time) const; +- SecurityLevel hash_level(pgp_hash_alg_t hash, uint64_t time) const noexcept; ++ bool has_rule(FeatureType type, ++ int value, ++ uint64_t time, ++ SecurityAction action = SecurityAction::Any) const noexcept; ++ const SecurityRule &get_rule(FeatureType type, ++ int value, ++ uint64_t time, ++ SecurityAction action = SecurityAction::Any) const; ++ SecurityLevel hash_level(pgp_hash_alg_t hash, ++ uint64_t time, ++ SecurityAction action = SecurityAction::Any) const noexcept; + SecurityLevel def_level() const; + }; + + class SecurityContext { ++ std::unordered_map s2k_iterations_; ++ uint64_t time_; ++ void * prov_state_; ++ + public: + SecurityProfile profile; + RNG rng; + + SecurityContext(); ++ ~SecurityContext(); ++ ++ size_t s2k_iterations(pgp_hash_alg_t halg); ++ ++ void set_time(uint64_t time) noexcept; ++ uint64_t time() const noexcept; + }; + } // namespace rnp + + #endif +diff --git a/comm/third_party/rnp/src/lib/types.h b/third_party/rnp/src/lib/comm/types.h +--- a/comm/third_party/rnp/src/lib/types.h ++++ b/comm/third_party/rnp/src/lib/types.h +@@ -366,14 +366,14 @@ typedef enum { + + /* user revocation info */ + typedef struct pgp_subsig_t pgp_subsig_t; + + typedef struct pgp_revoke_t { +- uint32_t uid{}; /* index in uid array */ +- pgp_revocation_type_t code{}; /* revocation code */ +- std::string reason; /* revocation reason */ +- pgp_sig_id_t sigid; /* id of the corresponding subsig */ ++ uint32_t uid{}; /* index in uid array */ ++ pgp_revocation_type_t code{}; /* revocation code */ ++ std::string reason; /* revocation reason */ ++ pgp_sig_id_t sigid{}; /* id of the corresponding subsig */ + + pgp_revoke_t() = default; + pgp_revoke_t(pgp_subsig_t &sig); + } pgp_revoke_t; + +@@ -435,15 +435,15 @@ typedef struct rnp_keygen_crypto_params_ + struct rnp_keygen_elgamal_params_t elgamal; + }; + } rnp_keygen_crypto_params_t; + + typedef struct rnp_selfsig_cert_info_t { +- uint8_t userid[MAX_ID_LENGTH]{}; /* userid, required */ +- uint8_t key_flags{}; /* key flags */ ++ std::string userid; /* userid, required */ ++ uint8_t key_flags{}; /* key flags */ + uint32_t key_expiration{}; /* key expiration time (sec), 0 = no expiration */ + pgp_user_prefs_t prefs{}; /* user preferences, optional */ +- bool primary : 1; /* mark this as the primary user id */ ++ bool primary; /* mark this as the primary user id */ + + /** + * @brief Populate uid and sig packet with data stored in this struct. + * At some point we should get rid of it. + */ +@@ -470,17 +470,6 @@ typedef struct rnp_key_protection_params + pgp_cipher_mode_t cipher_mode; + unsigned iterations; + pgp_hash_alg_t hash_alg; + } rnp_key_protection_params_t; + +-typedef struct rnp_action_keygen_t { +- struct { +- rnp_keygen_primary_desc_t keygen; +- rnp_key_protection_params_t protection; +- } primary; +- struct { +- rnp_keygen_subkey_desc_t keygen; +- rnp_key_protection_params_t protection; +- } subkey; +-} rnp_action_keygen_t; +- + #endif /* TYPES_H_ */ +diff --git a/comm/third_party/rnp/src/lib/version.h b/third_party/rnp/src/lib/comm/version.h +--- a/comm/third_party/rnp/src/lib/version.h ++++ b/comm/third_party/rnp/src/lib/version.h +@@ -23,16 +23,16 @@ + * POSSIBILITY OF SUCH DAMAGE. + */ + + #define RNP_VERSION_MAJOR 0 + #define RNP_VERSION_MINOR 16 +-#define RNP_VERSION_PATCH 0 ++#define RNP_VERSION_PATCH 2 + +-#define RNP_VERSION_STRING "0.16" +-#define RNP_VERSION_STRING_FULL "0.16+git20220124.f06439f7.MZLA" ++#define RNP_VERSION_STRING "0.16.2" ++#define RNP_VERSION_STRING_FULL "0.16.2+git20220922.298ad98b.MZLA" + +-#define RNP_VERSION_COMMIT_TIMESTAMP 1643016550 ++#define RNP_VERSION_COMMIT_TIMESTAMP 1663838874 + + // 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 +diff --git a/comm/third_party/rnp/src/librekey/g10_sexp.hpp b/third_party/rnp/src/lib/commrekey/g10_sexp.hpp +--- a/comm/third_party/rnp/src/librekey/g10_sexp.hpp ++++ b/comm/third_party/rnp/src/librekey/g10_sexp.hpp +@@ -106,13 +106,13 @@ class s_exp_t : public s_exp_element_t { + bool read_curve(const std::string &name, pgp_ec_key_t &key) noexcept; + void add_mpi(const std::string &name, const pgp_mpi_t &val); + void add_curve(const std::string &name, const pgp_ec_key_t &key); + void add_pubkey(const pgp_key_pkt_t &key); + void add_seckey(const pgp_key_pkt_t &key); +- void add_protected_seckey(pgp_key_pkt_t & seckey, +- const std::string &password, +- rnp::RNG & rng); ++ void add_protected_seckey(pgp_key_pkt_t & seckey, ++ const std::string & password, ++ rnp::SecurityContext &ctx); + + void clear(); + }; + + #endif +diff --git a/comm/third_party/rnp/src/librekey/key_store_g10.cpp b/third_party/rnp/src/lib/commrekey/key_store_g10.cpp +--- a/comm/third_party/rnp/src/librekey/key_store_g10.cpp ++++ b/comm/third_party/rnp/src/librekey/key_store_g10.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2017-2022, [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: +@@ -639,11 +639,11 @@ parse_protected_seckey(pgp_key_pkt_t &se + RNP_LOG("missing protected section"); + return false; + } + if (protected_key->size() != 4 || !protected_key->at(1).is_block() || + protected_key->at(2).is_block() || !protected_key->at(3).is_block()) { +- RNP_LOG("Wrong protected format, expected: (protected mode (parms) " ++ RNP_LOG("Wrong protected format, expected: (protected mode (params) " + "encrypted_octet_string)\n"); + return false; + } + + // lookup the protection format +@@ -930,73 +930,51 @@ copy_secret_fields(pgp_key_pkt_t &dst, c + bool + rnp_key_store_g10_from_src(rnp_key_store_t * key_store, + pgp_source_t * src, + const pgp_key_provider_t *key_provider) + { +- const pgp_key_t *pubkey = NULL; +- pgp_key_t key; +- pgp_key_pkt_t seckey; +- pgp_source_t memsrc = {}; +- bool ret = false; +- +- if (read_mem_src(&memsrc, src)) { +- goto done; +- } +- +- /* parse secret key: fills material and sec_protection only */ +- if (!g10_parse_seckey( +- seckey, (uint8_t *) mem_src_get_memory(&memsrc), memsrc.size, NULL)) { +- goto done; +- } +- +- /* copy public key fields if any */ +- if (key_provider) { +- pgp_key_search_t search = {.type = PGP_KEY_SEARCH_GRIP}; +- if (!rnp_key_store_get_key_grip(&seckey.material, search.by.grip)) { +- goto done; +- } +- +- pgp_key_request_ctx_t req_ctx; +- memset(&req_ctx, 0, sizeof(req_ctx)); +- req_ctx.op = PGP_OP_MERGE_INFO; +- req_ctx.secret = false; +- req_ctx.search = search; +- +- if (!(pubkey = pgp_request_key(key_provider, &req_ctx))) { +- goto done; ++ try { ++ /* read src to the memory */ ++ rnp::MemorySource memsrc(*src); ++ /* parse secret key: fills material and sec_protection only */ ++ pgp_key_pkt_t seckey; ++ if (!g10_parse_seckey(seckey, (uint8_t *) memsrc.memory(), memsrc.size(), NULL)) { ++ return false; + } ++ /* copy public key fields if any */ ++ pgp_key_t key; ++ if (key_provider) { ++ pgp_key_request_ctx_t req_ctx(PGP_OP_MERGE_INFO, false, PGP_KEY_SEARCH_GRIP); ++ if (!rnp_key_store_get_key_grip(&seckey.material, req_ctx.search.by.grip)) { ++ return false; ++ } + +- /* public key packet has some more info then the secret part */ +- try { +- key = pgp_key_t(*pubkey, true); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- goto done; +- } +- +- if (!copy_secret_fields(key.pkt(), seckey)) { +- goto done; +- } +- } else { +- key.set_pkt(std::move(seckey)); +- } ++ const pgp_key_t *pubkey = pgp_request_key(key_provider, &req_ctx); ++ if (!pubkey) { ++ return false; ++ } + +- try { +- key.set_rawpkt(pgp_rawpacket_t( +- (uint8_t *) mem_src_get_memory(&memsrc), memsrc.size, PGP_PKT_RESERVED)); ++ /* public key packet has some more info then the secret part */ ++ key = pgp_key_t(*pubkey, true); ++ if (!copy_secret_fields(key.pkt(), seckey)) { ++ return false; ++ } ++ } else { ++ key.set_pkt(std::move(seckey)); ++ } ++ /* set rawpkt */ ++ key.set_rawpkt( ++ pgp_rawpacket_t((uint8_t *) memsrc.memory(), memsrc.size(), PGP_PKT_RESERVED)); ++ key.format = PGP_KEY_STORE_G10; ++ if (!rnp_key_store_add_key(key_store, &key)) { ++ return false; ++ } ++ return true; + } catch (const std::exception &e) { +- RNP_LOG("failed to add packet: %s", e.what()); +- goto done; ++ RNP_LOG("%s", e.what()); ++ return false; + } +- key.format = PGP_KEY_STORE_G10; +- if (!rnp_key_store_add_key(key_store, &key)) { +- goto done; +- } +- ret = true; +-done: +- src_close(&memsrc); +- return ret; + } + + #define MAX_SIZE_T_LEN ((3 * sizeof(size_t) * CHAR_BIT / 8) + 2) + + bool +@@ -1100,47 +1078,35 @@ s_exp_t::add_seckey(const pgp_key_pkt_t + } + + rnp::secure_vector + s_exp_t::write_padded(size_t padblock) const + { +- pgp_dest_t raw = {0}; +- if (init_mem_dest(&raw, NULL, 0)) { +- RNP_LOG("mem dst alloc failed"); +- throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); ++ rnp::MemoryDest raw; ++ raw.set_secure(true); ++ ++ if (!write(raw.dst())) { ++ RNP_LOG("failed to serialize s_exp"); ++ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } +- mem_dest_secure_memory(&raw, true); +- +- try { +- if (!write(raw)) { +- RNP_LOG("failed to serialize s_exp"); +- throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); +- } + +- // add padding! +- size_t padding = padblock - raw.writeb % padblock; +- for (size_t i = 0; i < padding; i++) { +- dst_write(&raw, "X", 1); +- } +- if (raw.werr) { +- RNP_LOG("failed to write padding"); +- throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); +- } +- +- uint8_t * mem = (uint8_t *) mem_dest_get_memory(&raw); +- rnp::secure_vector res(mem, mem + raw.writeb); +- dst_close(&raw, true); +- return res; +- } catch (const std::exception &e) { +- dst_close(&raw, true); +- throw; ++ // add padding! ++ size_t padding = padblock - raw.writeb() % padblock; ++ for (size_t i = 0; i < padding; i++) { ++ raw.write("X", 1); + } ++ if (raw.werr()) { ++ RNP_LOG("failed to write padding"); ++ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); ++ } ++ const uint8_t *mem = (uint8_t *) raw.memory(); ++ return rnp::secure_vector(mem, mem + raw.writeb()); + } + + void +-s_exp_t::add_protected_seckey(pgp_key_pkt_t & seckey, +- const std::string &password, +- rnp::RNG & rng) ++s_exp_t::add_protected_seckey(pgp_key_pkt_t & seckey, ++ const std::string & password, ++ rnp::SecurityContext &ctx) + { + pgp_key_protection_t &prot = seckey.sec_protection; + if (prot.s2k.specifier != PGP_S2KS_ITERATED_AND_SALTED) { + RNP_LOG("Bad s2k specifier: %d", (int) prot.s2k.specifier); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); +@@ -1151,21 +1117,20 @@ s_exp_t::add_protected_seckey(pgp_key_pk + RNP_LOG("Unknown protection format."); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + + // randomize IV and salt +- rng.get(prot.iv, sizeof(prot.iv)); +- rng.get(prot.s2k.salt, sizeof(prot.s2k.salt)); ++ ctx.rng.get(prot.iv, sizeof(prot.iv)); ++ ctx.rng.get(prot.s2k.salt, sizeof(prot.s2k.salt)); + + // write seckey + s_exp_t raw_s_exp; + s_exp_t *psub_s_exp = &raw_s_exp.add_sub(); + psub_s_exp->add_seckey(seckey); + + // calculate hash +- time_t now; +- time(&now); ++ time_t now = ctx.time(); + char protected_at[G10_PROTECTED_AT_SIZE + 1]; + uint8_t checksum[G10_SHA1_HASH_SIZE]; + // TODO: how critical is it if we have a skewed timestamp here due to y2k38 problem? + strftime(protected_at, sizeof(protected_at), "%Y%m%dT%H%M%S", gmtime(&now)); + if (!g10_calculated_hash(seckey, protected_at, checksum)) { +@@ -1237,11 +1202,14 @@ s_exp_t::add_protected_seckey(pgp_key_pk + psub_s_exp->add("protected-at"); + psub_s_exp->add((uint8_t *) protected_at, G10_PROTECTED_AT_SIZE); + } + + bool +-g10_write_seckey(pgp_dest_t *dst, pgp_key_pkt_t *seckey, const char *password, rnp::RNG &rng) ++g10_write_seckey(pgp_dest_t * dst, ++ pgp_key_pkt_t * seckey, ++ const char * password, ++ rnp::SecurityContext &ctx) + { + bool is_protected = true; + + switch (seckey->sec_protection.s2k.usage) { + case PGP_S2KU_NONE: +@@ -1264,11 +1232,11 @@ g10_write_seckey(pgp_dest_t *dst, pgp_ke + s_exp.add(is_protected ? "protected-private-key" : "private-key"); + s_exp_t &pkey = s_exp.add_sub(); + pkey.add_pubkey(*seckey); + + if (is_protected) { +- pkey.add_protected_seckey(*seckey, password, rng); ++ pkey.add_protected_seckey(*seckey, password, ctx); + } else { + pkey.add_seckey(*seckey); + } + return s_exp.write(*dst) && !dst->werr; + } catch (const std::exception &e) { +@@ -1278,37 +1246,31 @@ g10_write_seckey(pgp_dest_t *dst, pgp_ke + } + + static bool + g10_calculated_hash(const pgp_key_pkt_t &key, const char *protected_at, uint8_t *checksum) + { +- pgp_dest_t memdst = {}; + try { + /* populate s_exp */ + s_exp_t s_exp; + s_exp.add_pubkey(key); + s_exp.add_seckey(key); + s_exp_t &s_sub_exp = s_exp.add_sub(); + s_sub_exp.add("protected-at"); + s_sub_exp.add((uint8_t *) protected_at, G10_PROTECTED_AT_SIZE); + /* write it to memdst */ +- if (init_mem_dest(&memdst, NULL, 0)) { ++ rnp::MemoryDest memdst; ++ memdst.set_secure(true); ++ if (!s_exp.write(memdst.dst())) { ++ RNP_LOG("Failed to write s_exp"); + return false; + } +- mem_dest_secure_memory(&memdst, true); +- if (!s_exp.write(memdst)) { +- RNP_LOG("Failed to write s_exp"); +- dst_close(&memdst, true); +- return false; +- } +- rnp::Hash hash(PGP_HASH_SHA1); +- hash.add(mem_dest_get_memory(&memdst), memdst.writeb); +- hash.finish(checksum); +- dst_close(&memdst, true); ++ auto hash = rnp::Hash::create(PGP_HASH_SHA1); ++ hash->add(memdst.memory(), memdst.writeb()); ++ hash->finish(checksum); + return true; + } catch (const std::exception &e) { + RNP_LOG("Failed to build s_exp: %s", e.what()); +- dst_close(&memdst, true); + return false; + } + } + + bool +diff --git a/comm/third_party/rnp/src/librekey/key_store_g10.h b/third_party/rnp/src/lib/commrekey/key_store_g10.h +--- a/comm/third_party/rnp/src/librekey/key_store_g10.h ++++ b/comm/third_party/rnp/src/librekey/key_store_g10.h +@@ -29,14 +29,14 @@ + + #include + + bool rnp_key_store_g10_from_src(rnp_key_store_t *, pgp_source_t *, const pgp_key_provider_t *); + bool rnp_key_store_g10_key_to_dst(pgp_key_t *, pgp_dest_t *); +-bool g10_write_seckey(pgp_dest_t * dst, +- pgp_key_pkt_t *seckey, +- const char * password, +- rnp::RNG & rng); ++bool g10_write_seckey(pgp_dest_t * dst, ++ pgp_key_pkt_t * seckey, ++ const char * password, ++ rnp::SecurityContext &ctx); + pgp_key_pkt_t *g10_decrypt_seckey(const pgp_rawpacket_t &raw, + const pgp_key_pkt_t & pubkey, + const char * password); + + #endif // RNP_KEY_STORE_G10_H +diff --git a/comm/third_party/rnp/src/librekey/key_store_kbx.cpp b/third_party/rnp/src/librekey/key_store_kb/commx.cpp +--- a/comm/third_party/rnp/src/librekey/key_store_kbx.cpp ++++ b/comm/third_party/rnp/src/librekey/key_store_kbx.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2017-2022, [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: +@@ -377,83 +377,65 @@ rnp_key_store_kbx_parse_blob(const uint8 + bool + rnp_key_store_kbx_from_src(rnp_key_store_t * key_store, + pgp_source_t * src, + const pgp_key_provider_t *key_provider) + { +- pgp_source_t memsrc = {}; +- if (read_mem_src(&memsrc, src)) { +- RNP_LOG("failed to get data to memory source"); ++ try { ++ rnp::MemorySource mem(*src); ++ size_t has_bytes = mem.size(); ++ uint8_t * buf = (uint8_t *) mem.memory(); ++ ++ while (has_bytes > 4) { ++ size_t blob_length = read_uint32(buf); ++ if (blob_length > BLOB_SIZE_LIMIT) { ++ RNP_LOG("Blob size is %zu bytes but limit is %d bytes", ++ blob_length, ++ (int) BLOB_SIZE_LIMIT); ++ return false; ++ } ++ if (blob_length < BLOB_HEADER_SIZE) { ++ RNP_LOG("Too small blob header size"); ++ return false; ++ } ++ if (has_bytes < blob_length) { ++ RNP_LOG("Blob have size %zu bytes but file contains only %zu bytes", ++ blob_length, ++ has_bytes); ++ return false; ++ } ++ auto blob = rnp_key_store_kbx_parse_blob(buf, blob_length); ++ if (!blob.get()) { ++ RNP_LOG("Failed to parse blob"); ++ return false; ++ } ++ kbx_blob_t *pblob = blob.get(); ++ key_store->blobs.push_back(std::move(blob)); ++ ++ if (pblob->type() == KBX_PGP_BLOB) { ++ // parse keyblock if it existed ++ kbx_pgp_blob_t &pgp_blob = dynamic_cast(*pblob); ++ if (!pgp_blob.keyblock_length()) { ++ RNP_LOG("PGP blob have zero size"); ++ return false; ++ } ++ ++ rnp::MemorySource blsrc(pgp_blob.image().data() + pgp_blob.keyblock_offset(), ++ pgp_blob.keyblock_length(), ++ false); ++ if (rnp_key_store_pgp_read_from_src(key_store, &blsrc.src())) { ++ return false; ++ } ++ } ++ ++ has_bytes -= blob_length; ++ buf += blob_length; ++ } ++ return true; ++ } catch (const std::exception &e) { ++ RNP_LOG("%s", e.what()); + return false; + } +- +- size_t has_bytes = memsrc.size; +- /* complications below are because of memsrc uses malloc instead of new */ +- std::unique_ptr mem( +- (uint8_t *) mem_src_get_memory(&memsrc, true), free); +- src_close(&memsrc); +- uint8_t *buf = mem.get(); +- +- while (has_bytes > 4) { +- size_t blob_length = read_uint32(buf); +- if (blob_length > BLOB_SIZE_LIMIT) { +- RNP_LOG("Blob size is %zu bytes but limit is %d bytes", +- blob_length, +- (int) BLOB_SIZE_LIMIT); +- return false; +- } +- if (blob_length < BLOB_HEADER_SIZE) { +- RNP_LOG("Too small blob header size"); +- return false; +- } +- if (has_bytes < blob_length) { +- RNP_LOG("Blob have size %zu bytes but file contains only %zu bytes", +- blob_length, +- has_bytes); +- return false; +- } +- auto blob = rnp_key_store_kbx_parse_blob(buf, blob_length); +- if (!blob.get()) { +- RNP_LOG("Failed to parse blob"); +- return false; +- } +- kbx_blob_t *pblob = blob.get(); +- try { +- key_store->blobs.push_back(std::move(blob)); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- return false; +- } +- +- if (pblob->type() == KBX_PGP_BLOB) { +- // parse keyblock if it existed +- kbx_pgp_blob_t &pgp_blob = dynamic_cast(*pblob); +- if (!pgp_blob.keyblock_length()) { +- RNP_LOG("PGP blob have zero size"); +- return false; +- } +- +- pgp_source_t blsrc = {}; +- if (init_mem_src(&blsrc, +- pgp_blob.image().data() + pgp_blob.keyblock_offset(), +- pgp_blob.keyblock_length(), +- false)) { +- RNP_LOG("memory src allocation failed"); +- return false; +- } +- +- if (rnp_key_store_pgp_read_from_src(key_store, &blsrc)) { +- src_close(&blsrc); +- return false; +- } +- src_close(&blsrc); +- } +- +- has_bytes -= blob_length; +- buf += blob_length; +- } +- +- return true; + } + + static bool + pbuf(pgp_dest_t *dst, const void *buf, size_t len) + { +@@ -486,222 +468,196 @@ pu32(pgp_dest_t *dst, uint32_t f) + + static bool + rnp_key_store_kbx_write_header(rnp_key_store_t *key_store, pgp_dest_t *dst) + { + uint16_t flags = 0; +- uint32_t file_created_at = time(NULL); ++ uint32_t file_created_at = key_store->secctx.time(); + + if (!key_store->blobs.empty() && (key_store->blobs[0]->type() == KBX_HEADER_BLOB)) { + kbx_header_blob_t &blob = dynamic_cast(*key_store->blobs[0]); + file_created_at = blob.file_created_at(); + } + + return !(!pu32(dst, BLOB_FIRST_SIZE) || !pu8(dst, KBX_HEADER_BLOB) || + !pu8(dst, 1) // version + || !pu16(dst, flags) || !pbuf(dst, "KBXf", 4) || !pu32(dst, 0) // RFU + || !pu32(dst, 0) // RFU +- || !pu32(dst, file_created_at) || !pu32(dst, time(NULL)) || !pu32(dst, 0)); // RFU ++ || !pu32(dst, file_created_at) || !pu32(dst, key_store->secctx.time()) || ++ !pu32(dst, 0)); // RFU + } + + static bool + rnp_key_store_kbx_write_pgp(rnp_key_store_t *key_store, pgp_key_t *key, pgp_dest_t *dst) + { +- unsigned i; +- pgp_dest_t memdst = {}; +- size_t key_start, uid_start; +- uint8_t * p; +- uint8_t checksum[20]; +- uint32_t pt; +- bool result = false; +- std::vector subkey_sig_expirations; +- uint32_t expiration = 0; ++ rnp::MemoryDest mem(NULL, BLOB_SIZE_LIMIT); ++ ++ if (!pu32(&mem.dst(), 0)) { // length, we don't know length of blob yet, so it's 0 ++ return false; ++ } + +- if (init_mem_dest(&memdst, NULL, BLOB_SIZE_LIMIT)) { +- RNP_LOG("alloc failed"); ++ if (!pu8(&mem.dst(), KBX_PGP_BLOB) || !pu8(&mem.dst(), 1)) { // type, version ++ return false; ++ } ++ ++ if (!pu16(&mem.dst(), 0)) { // flags, not used by GnuPG ++ return false; ++ } ++ ++ if (!pu32(&mem.dst(), 0) || ++ !pu32(&mem.dst(), 0)) { // offset and length of keyblock, update later + return false; + } + +- if (!pu32(&memdst, 0)) { // length, we don't know length of blob yet, so it's 0 right now +- goto finish; ++ if (!pu16(&mem.dst(), 1 + key->subkey_count())) { // number of keys in keyblock ++ return false; + } +- +- if (!pu8(&memdst, KBX_PGP_BLOB) || !pu8(&memdst, 1)) { // type, version +- goto finish; +- } +- +- if (!pu16(&memdst, 0)) { // flags, not used by GnuPG +- goto finish; ++ if (!pu16(&mem.dst(), 28)) { // size of key info structure) ++ return false; + } + +- if (!pu32(&memdst, 0) || +- !pu32(&memdst, 0)) { // offset and length of keyblock, update later +- goto finish; +- } +- +- if (!pu16(&memdst, 1 + key->subkey_count())) { // number of keys in keyblock +- goto finish; +- } +- if (!pu16(&memdst, 28)) { // size of key info structure) +- goto finish; +- } +- +- if (!pbuf(&memdst, key->fp().fingerprint, PGP_FINGERPRINT_SIZE) || +- !pu32(&memdst, memdst.writeb - 8) || // offset to keyid (part of fpr for V4) +- !pu16(&memdst, 0) || // flags, not used by GnuPG +- !pu16(&memdst, 0)) { // RFU +- goto finish; ++ if (!pbuf(&mem.dst(), key->fp().fingerprint, PGP_FINGERPRINT_SIZE) || ++ !pu32(&mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4) ++ !pu16(&mem.dst(), 0) || // flags, not used by GnuPG ++ !pu16(&mem.dst(), 0)) { // RFU ++ return false; + } + + // same as above, for each subkey ++ std::vector subkey_sig_expirations; + for (auto &sfp : key->subkey_fps()) { + pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp); +- if (!pbuf(&memdst, subkey->fp().fingerprint, PGP_FINGERPRINT_SIZE) || +- !pu32(&memdst, memdst.writeb - 8) || // offset to keyid (part of fpr for V4) +- !pu16(&memdst, 0) || // flags, not used by GnuPG +- !pu16(&memdst, 0)) { // RFU +- goto finish; ++ if (!pbuf(&mem.dst(), subkey->fp().fingerprint, PGP_FINGERPRINT_SIZE) || ++ !pu32(&mem.dst(), mem.writeb() - 8) || // offset to keyid (part of fpr for V4) ++ !pu16(&mem.dst(), 0) || // flags, not used by GnuPG ++ !pu16(&mem.dst(), 0)) { // RFU ++ return false; + } + // load signature expirations while we're at it +- for (i = 0; i < subkey->sig_count(); i++) { +- expiration = subkey->get_sig(i).sig.key_expiration(); +- try { +- subkey_sig_expirations.push_back(expiration); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- goto finish; +- } ++ for (size_t i = 0; i < subkey->sig_count(); i++) { ++ uint32_t expiration = subkey->get_sig(i).sig.key_expiration(); ++ subkey_sig_expirations.push_back(expiration); + } + } + +- if (!pu16(&memdst, 0)) { // Zero size of serial number +- goto finish; ++ if (!pu16(&mem.dst(), 0)) { // Zero size of serial number ++ return false; + } + + // skip serial number +- +- if (!pu16(&memdst, key->uid_count()) || !pu16(&memdst, 12)) { +- goto finish; ++ if (!pu16(&mem.dst(), key->uid_count()) || !pu16(&mem.dst(), 12)) { ++ return false; + } + +- uid_start = memdst.writeb; +- +- for (i = 0; i < key->uid_count(); i++) { +- if (!pu32(&memdst, 0) || +- !pu32(&memdst, 0)) { // UID offset and length, update when blob has done +- goto finish; ++ size_t uid_start = mem.writeb(); ++ for (size_t i = 0; i < key->uid_count(); i++) { ++ if (!pu32(&mem.dst(), 0) || ++ !pu32(&mem.dst(), 0)) { // UID offset and length, update when blob has done ++ return false; + } + +- if (!pu16(&memdst, 0)) { // flags, (not yet used) +- goto finish; ++ if (!pu16(&mem.dst(), 0)) { // flags, (not yet used) ++ return false; + } + +- if (!pu8(&memdst, 0) || !pu8(&memdst, 0)) { // Validity & RFU +- goto finish; ++ if (!pu8(&mem.dst(), 0) || !pu8(&mem.dst(), 0)) { // Validity & RFU ++ return false; + } + } + +- if (!pu16(&memdst, key->sig_count() + subkey_sig_expirations.size()) || +- !pu16(&memdst, 4)) { +- goto finish; ++ if (!pu16(&mem.dst(), key->sig_count() + subkey_sig_expirations.size()) || ++ !pu16(&mem.dst(), 4)) { ++ return false; + } + +- for (i = 0; i < key->sig_count(); i++) { +- if (!pu32(&memdst, key->get_sig(i).sig.key_expiration())) { +- goto finish; ++ for (size_t i = 0; i < key->sig_count(); i++) { ++ if (!pu32(&mem.dst(), key->get_sig(i).sig.key_expiration())) { ++ return false; + } + } + for (auto &expiration : subkey_sig_expirations) { +- if (!pu32(&memdst, expiration)) { +- goto finish; ++ if (!pu32(&mem.dst(), expiration)) { ++ return false; + } + } + +- if (!pu8(&memdst, 0) || +- !pu8(&memdst, 0)) { // Assigned ownertrust & All_Validity (not yet used) +- goto finish; ++ if (!pu8(&mem.dst(), 0) || ++ !pu8(&mem.dst(), 0)) { // Assigned ownertrust & All_Validity (not yet used) ++ return false; + } + +- if (!pu16(&memdst, 0) || !pu32(&memdst, 0)) { // RFU & Recheck_after +- goto finish; ++ if (!pu16(&mem.dst(), 0) || !pu32(&mem.dst(), 0)) { // RFU & Recheck_after ++ return false; + } + +- if (!pu32(&memdst, time(NULL)) || +- !pu32(&memdst, time(NULL))) { // Latest timestamp && created +- goto finish; ++ if (!pu32(&mem.dst(), key_store->secctx.time()) || ++ !pu32(&mem.dst(), key_store->secctx.time())) { // Latest timestamp && created ++ return false; + } + +- if (!pu32(&memdst, 0)) { // Size of reserved space +- goto finish; ++ if (!pu32(&mem.dst(), 0)) { // Size of reserved space ++ return false; + } + + // wrtite UID, we might redesign PGP write and use this information from keyblob +- for (i = 0; i < key->uid_count(); i++) { ++ for (size_t i = 0; i < key->uid_count(); i++) { + const pgp_userid_t &uid = key->get_uid(i); +- p = (uint8_t *) mem_dest_get_memory(&memdst) + uid_start + (12 * i); ++ uint8_t * p = (uint8_t *) mem.memory() + uid_start + (12 * i); + /* store absolute uid offset in the output stream */ +- pt = memdst.writeb + dst->writeb; ++ uint32_t pt = mem.writeb() + dst->writeb; + STORE32BE(p, pt); + /* and uid length */ + pt = uid.str.size(); +- p = (uint8_t *) mem_dest_get_memory(&memdst) + uid_start + (12 * i) + 4; +- STORE32BE(p, pt); ++ STORE32BE(p + 4, pt); + /* uid data itself */ +- if (!pbuf(&memdst, uid.str.c_str(), pt)) { +- goto finish; ++ if (!pbuf(&mem.dst(), uid.str.c_str(), pt)) { ++ return false; + } + } + + /* write keyblock and fix the offset/length */ +- key_start = memdst.writeb; +- pt = key_start; +- p = (uint8_t *) mem_dest_get_memory(&memdst) + 8; ++ size_t key_start = mem.writeb(); ++ uint32_t pt = key_start; ++ uint8_t *p = (uint8_t *) mem.memory() + 8; + STORE32BE(p, pt); + +- key->write(memdst); +- if (memdst.werr) { +- goto finish; ++ key->write(mem.dst()); ++ if (mem.werr()) { ++ return false; + } + + for (auto &sfp : key->subkey_fps()) { + const pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp); +- subkey->write(memdst); +- if (memdst.werr) { +- goto finish; ++ subkey->write(mem.dst()); ++ if (mem.werr()) { ++ return false; + } + } + + /* key blob length */ +- pt = memdst.writeb - key_start; +- p = (uint8_t *) mem_dest_get_memory(&memdst) + 12; ++ pt = mem.writeb() - key_start; ++ p = (uint8_t *) mem.memory() + 12; + STORE32BE(p, pt); + + // fix the length of blob +- pt = memdst.writeb + 20; +- p = (uint8_t *) mem_dest_get_memory(&memdst); ++ pt = mem.writeb() + 20; ++ p = (uint8_t *) mem.memory(); + STORE32BE(p, pt); + + // checksum +- try { +- rnp::Hash hash(PGP_HASH_SHA1); +- assert(hash.size() == 20); +- hash.add(mem_dest_get_memory(&memdst), memdst.writeb); +- hash.finish(checksum); +- } catch (const std::exception &e) { +- RNP_LOG("Hashing failed: %s", e.what()); +- goto finish; +- } ++ auto hash = rnp::Hash::create(PGP_HASH_SHA1); ++ hash->add(mem.memory(), mem.writeb()); ++ uint8_t checksum[PGP_SHA1_HASH_SIZE]; ++ assert(hash->size() == sizeof(checksum)); ++ hash->finish(checksum); + +- if (!(pbuf(&memdst, checksum, 20))) { +- goto finish; ++ if (!(pbuf(&mem.dst(), checksum, PGP_SHA1_HASH_SIZE))) { ++ return false; + } + + /* finally write to the output */ +- dst_write(dst, mem_dest_get_memory(&memdst), memdst.writeb); +- result = dst->werr == RNP_SUCCESS; +-finish: +- dst_close(&memdst, true); +- return result; ++ dst_write(dst, mem.memory(), mem.writeb()); ++ return !dst->werr; + } + + static bool + rnp_key_store_kbx_write_x509(rnp_key_store_t *key_store, pgp_dest_t *dst) + { +@@ -717,27 +673,31 @@ rnp_key_store_kbx_write_x509(rnp_key_sto + } + + bool + rnp_key_store_kbx_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst) + { +- if (!rnp_key_store_kbx_write_header(key_store, dst)) { +- RNP_LOG("Can't write KBX header"); ++ try { ++ if (!rnp_key_store_kbx_write_header(key_store, dst)) { ++ RNP_LOG("Can't write KBX header"); ++ return false; ++ } ++ ++ for (auto &key : key_store->keys) { ++ if (!key.is_primary()) { ++ continue; ++ } ++ if (!rnp_key_store_kbx_write_pgp(key_store, &key, dst)) { ++ RNP_LOG("Can't write PGP blobs for key %p", &key); ++ return false; ++ } ++ } ++ ++ if (!rnp_key_store_kbx_write_x509(key_store, dst)) { ++ RNP_LOG("Can't write X509 blobs"); ++ return false; ++ } ++ return true; ++ } catch (const std::exception &e) { ++ RNP_LOG("Failed to write KBX store: %s", e.what()); + return false; + } +- +- for (auto &key : key_store->keys) { +- if (!key.is_primary()) { +- continue; +- } +- if (!rnp_key_store_kbx_write_pgp(key_store, &key, dst)) { +- RNP_LOG("Can't write PGP blobs for key %p", &key); +- return false; +- } +- } +- +- if (!rnp_key_store_kbx_write_x509(key_store, dst)) { +- RNP_LOG("Can't write X509 blobs"); +- return false; +- } +- +- return true; + } +diff --git a/comm/third_party/rnp/src/librekey/key_store_pgp.cpp b/third_party/rnp/src/lib/commrekey/key_store_pgp.cpp +--- a/comm/third_party/rnp/src/librekey/key_store_pgp.cpp ++++ b/comm/third_party/rnp/src/librekey/key_store_pgp.cpp +@@ -157,52 +157,47 @@ rnp_key_store_pgp_read_key_from_src(rnp_ + } + + rnp_result_t + rnp_key_store_pgp_read_from_src(rnp_key_store_t *keyring, pgp_source_t *src, bool skiperrors) + { +- rnp_result_t ret = RNP_ERROR_GENERIC; +- + /* check whether we have transferable subkey in source */ + if (is_subkey_pkt(stream_pkt_type(src))) { + pgp_transferable_subkey_t tskey; +- ret = process_pgp_subkey(*src, tskey, skiperrors); ++ rnp_result_t ret = process_pgp_subkey(*src, tskey, skiperrors); + if (ret) { + return ret; + } + return rnp_key_store_add_transferable_subkey(keyring, &tskey, NULL) ? + RNP_SUCCESS : + RNP_ERROR_BAD_STATE; + } + + /* process armored or raw transferable key packets sequence(s) */ +- pgp_key_sequence_t keys; +- if ((ret = process_pgp_keys(src, keys, skiperrors))) { +- return ret; ++ try { ++ pgp_key_sequence_t keys; ++ rnp_result_t ret = process_pgp_keys(*src, keys, skiperrors); ++ if (ret) { ++ return ret; ++ } ++ for (auto &key : keys.keys) { ++ if (!rnp_key_store_add_transferable_key(keyring, &key)) { ++ return RNP_ERROR_BAD_STATE; ++ } ++ } ++ return RNP_SUCCESS; ++ } catch (const std::exception &e) { ++ RNP_LOG("%s", e.what()); ++ return RNP_ERROR_BAD_PARAMETERS; + } +- +- for (auto &key : keys.keys) { +- if (!rnp_key_store_add_transferable_key(keyring, &key)) { +- return RNP_ERROR_BAD_STATE; +- } +- } +- return RNP_SUCCESS; + } + +-bool +-rnp_key_to_src(const pgp_key_t *key, pgp_source_t *src) ++std::vector ++rnp_key_to_vec(const pgp_key_t &key) + { +- pgp_dest_t dst = {}; +- bool res; +- +- if (init_mem_dest(&dst, NULL, 0)) { +- return false; +- } +- +- key->write(dst); +- res = !dst.werr && !init_mem_src(src, mem_dest_own_memory(&dst), dst.writeb, true); +- dst_close(&dst, true); +- return res; ++ rnp::MemoryDest dst; ++ key.write(dst.dst()); ++ return dst.to_vector(); + } + + static bool + do_write(rnp_key_store_t *key_store, pgp_dest_t *dst, bool secret) + { +diff --git a/comm/third_party/rnp/src/librekey/key_store_pgp.h b/third_party/rnp/src/lib/commrekey/key_store_pgp.h +--- a/comm/third_party/rnp/src/librekey/key_store_pgp.h ++++ b/comm/third_party/rnp/src/librekey/key_store_pgp.h +@@ -76,8 +76,8 @@ bool rnp_key_store_add_transferable_subk + pgp_key_t * pkey); + + bool rnp_key_store_add_transferable_key(rnp_key_store_t * keyring, + pgp_transferable_key_t *tkey); + +-bool rnp_key_to_src(const pgp_key_t *key, pgp_source_t *src); ++std::vector rnp_key_to_vec(const pgp_key_t &key); + + #endif /* KEY_STORE_PGP_H_ */ +diff --git a/comm/third_party/rnp/src/librekey/rnp_key_store.cpp b/third_party/rnp/src/lib/commrekey/rnp_key_store.cpp +--- a/comm/third_party/rnp/src/librekey/rnp_key_store.cpp ++++ b/comm/third_party/rnp/src/librekey/rnp_key_store.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * This code is originally derived from software contributed to + * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and + * carried further by Ribose Inc (https://www.ribose.com). +@@ -55,11 +55,11 @@ + #include "key_store_g10.h" + #include "kbx_blob.hpp" + + #include "pgp-key.h" + #include "fingerprint.h" +-#include "crypto/hash.h" ++#include "crypto/hash.hpp" + #include "crypto/mem.h" + #include "file-utils.h" + #ifdef _WIN32 + #include "str-utils.h" + #endif +@@ -419,11 +419,11 @@ rnp_key_store_import_key(rnp_key_store_t + } + + pgp_key_t * + rnp_key_store_get_signer_key(rnp_key_store_t *store, const pgp_signature_t *sig) + { +- pgp_key_search_t search = {}; ++ pgp_key_search_t search; + // prefer using the issuer fingerprint when available + if (sig->has_keyfp()) { + search.by.fingerprint = sig->keyfp(); + search.type = PGP_KEY_SEARCH_FINGERPRINT; + return rnp_key_store_search(store, &search, NULL); +@@ -714,40 +714,40 @@ grip_hash_ec(rnp::Hash &hash, const pgp_ + /* keygrip is subjectKeyHash from pkcs#15 for RSA. */ + bool + rnp_key_store_get_key_grip(const pgp_key_material_t *key, pgp_key_grip_t &grip) + { + try { +- rnp::Hash hash(PGP_HASH_SHA1); ++ auto hash = rnp::Hash::create(PGP_HASH_SHA1); + switch (key->alg) { + case PGP_PKA_RSA: + case PGP_PKA_RSA_SIGN_ONLY: + case PGP_PKA_RSA_ENCRYPT_ONLY: +- grip_hash_mpi(hash, key->rsa.n, '\0'); ++ grip_hash_mpi(*hash, key->rsa.n, '\0'); + break; + case PGP_PKA_DSA: +- grip_hash_mpi(hash, key->dsa.p, 'p'); +- grip_hash_mpi(hash, key->dsa.q, 'q'); +- grip_hash_mpi(hash, key->dsa.g, 'g'); +- grip_hash_mpi(hash, key->dsa.y, 'y'); ++ grip_hash_mpi(*hash, key->dsa.p, 'p'); ++ grip_hash_mpi(*hash, key->dsa.q, 'q'); ++ grip_hash_mpi(*hash, key->dsa.g, 'g'); ++ grip_hash_mpi(*hash, key->dsa.y, 'y'); + break; + case PGP_PKA_ELGAMAL: + case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: +- grip_hash_mpi(hash, key->eg.p, 'p'); +- grip_hash_mpi(hash, key->eg.g, 'g'); +- grip_hash_mpi(hash, key->eg.y, 'y'); ++ grip_hash_mpi(*hash, key->eg.p, 'p'); ++ grip_hash_mpi(*hash, key->eg.g, 'g'); ++ grip_hash_mpi(*hash, key->eg.y, 'y'); + break; + case PGP_PKA_ECDH: + case PGP_PKA_ECDSA: + case PGP_PKA_EDDSA: + case PGP_PKA_SM2: +- grip_hash_ec(hash, key->ec); ++ grip_hash_ec(*hash, key->ec); + break; + default: + RNP_LOG("unsupported public-key algorithm %d", (int) key->alg); + return false; + } +- return hash.finish(grip.data()) == grip.size(); ++ return hash->finish(grip.data()) == grip.size(); + } catch (const std::exception &e) { + RNP_LOG("Grip calculation failed: %s", e.what()); + return false; + } + } +diff --git a/comm/third_party/rnp/src/librepgp/stream-armor.cpp b/third_party/rnp/src/lib/commrepgp/stream-armor.cpp +--- a/comm/third_party/rnp/src/librepgp/stream-armor.cpp ++++ b/comm/third_party/rnp/src/librepgp/stream-armor.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2017-2022, [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: + * +@@ -32,17 +32,15 @@ + #else + #include "uniwin.h" + #endif + #include + #include +-#include + #include "stream-def.h" + #include "stream-armor.h" + #include "stream-packet.h" + #include "str-utils.h" +-#include "crypto/hash.h" +-#include "types.h" ++#include "crypto/hash.hpp" + #include "utils.h" + + #define ARMORED_BLOCK_SIZE (4096) + #define ARMORED_PEEK_BUF_SIZE 1024 + #define ARMORED_MIN_LINE_LENGTH (16) +@@ -62,22 +60,23 @@ typedef struct pgp_source_armored_param_ + uint8_t brest[3]; /* decoded 6-bit tail bytes */ + unsigned brestlen; /* number of bytes in brest */ + bool eofb64; /* end of base64 stream reached */ + uint8_t readcrc[3]; /* crc-24 from the armored data */ + bool has_crc; /* message contains CRC line */ +- rnp::CRC24 crc_ctx; /* CTX used to calculate CRC */ ++ std::unique_ptr crc_ctx; /* CTX used to calculate CRC */ ++ bool noheaders; /* only base64 data, no headers */ + } pgp_source_armored_param_t; + + typedef struct pgp_dest_armored_param_t { + pgp_dest_t * writedst; + pgp_armored_msg_t type; /* type of the message */ +- bool usecrlf; /* use CR LF instead of LF as eol */ ++ char eol[2]; /* end of line, all non-zeroes are written */ + unsigned lout; /* chars written in current line */ + unsigned llen; /* length of the base64 line, defaults to 76 as per RFC */ + uint8_t tail[2]; /* bytes which didn't fit into 3-byte boundary */ + unsigned tailc; /* number of bytes in tail */ +- rnp::CRC24 crc_ctx; /* CTX used to calculate CRC */ ++ std::unique_ptr crc_ctx; /* CTX used to calculate CRC */ + } pgp_dest_armored_param_t; + + /* + Table for base64 lookups: + 0xff - wrong character, +@@ -104,36 +103,71 @@ static const uint8_t B64DEC[256] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff}; + + static bool +-armor_read_padding(pgp_source_t *src, size_t *read) ++armor_read_padding(pgp_source_armored_param_t *param, size_t *read) + { +- char st[64]; +- size_t stlen = 0; +- pgp_source_t *readsrc = ((pgp_source_armored_param_t *) src->param)->readsrc; ++ char st[64]; ++ size_t stlen = 0; + +- if (!src_peek_line(readsrc, st, 12, &stlen)) { ++ if (!src_peek_line(param->readsrc, st, 64, &stlen)) { + return false; + } + + if ((stlen == 1) || (stlen == 2)) { + if ((st[0] != CH_EQ) || ((stlen == 2) && (st[1] != CH_EQ))) { + return false; + } + + *read = stlen; +- src_skip(readsrc, stlen); +- return src_skip_eol(readsrc); ++ src_skip(param->readsrc, stlen); ++ return src_skip_eol(param->readsrc); + } else if (stlen == 5) { + *read = 0; + return true; ++ } else if ((stlen > 5) && !memcmp(st, ST_DASHES, 5)) { ++ /* case with absent crc and 3-byte last chunk */ ++ *read = 0; ++ return true; + } + return false; + } + + static bool ++base64_read_padding(pgp_source_armored_param_t *param, size_t *read) ++{ ++ char pad[16]; ++ size_t padlen = sizeof(pad); ++ ++ /* we would allow arbitrary number of whitespaces/eols after the padding */ ++ if (!src_read(param->readsrc, pad, padlen, &padlen)) { ++ return false; ++ } ++ /* strip trailing whitespaces */ ++ while (padlen && (B64DEC[(int) pad[padlen - 1]] == 0xfd)) { ++ padlen--; ++ } ++ /* check for '=' */ ++ for (size_t i = 0; i < padlen; i++) { ++ if (pad[i] != CH_EQ) { ++ RNP_LOG("wrong base64 padding: %.*s", (int) padlen, pad); ++ return false; ++ } ++ } ++ if (padlen > 2) { ++ RNP_LOG("wrong base64 padding length %zu.", padlen); ++ return false; ++ } ++ if (!src_eof(param->readsrc)) { ++ RNP_LOG("warning: extra data after the base64 stream."); ++ } ++ *read = padlen; ++ return true; ++} ++ ++static bool + armor_read_crc(pgp_source_t *src) + { + uint8_t dec[4] = {0}; + char crc[8] = {0}; + size_t clen = 0; +@@ -223,10 +257,35 @@ armor_read_trailer(pgp_source_t *src) + (void) src_skip_eol(param->readsrc); + return true; + } + + static bool ++armored_update_crc(pgp_source_armored_param_t *param, ++ const void * buf, ++ size_t len, ++ bool finish = false) ++{ ++ if (param->noheaders) { ++ return true; ++ } ++ try { ++ param->crc_ctx->add(buf, len); ++ if (!finish) { ++ return true; ++ } ++ auto crc = param->crc_ctx->finish(); ++ if (param->has_crc && memcmp(param->readcrc, crc.data(), 3)) { ++ RNP_LOG("Warning: CRC mismatch"); ++ } ++ return true; ++ } catch (const std::exception &e) { ++ RNP_LOG("%s", e.what()); ++ return false; ++ } ++} ++ ++static bool + armored_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres) + { + pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param; + uint8_t b64buf[ARMORED_BLOCK_SIZE]; /* input base64 data with spaces and so on */ + uint8_t decbuf[ARMORED_BLOCK_SIZE + 4]; /* decoded 6-bit values */ +@@ -248,11 +307,11 @@ armored_src_read(pgp_source_t *src, void + if (param->restpos < param->restlen) { + if (param->restlen - param->restpos >= len) { + memcpy(bufptr, ¶m->rest[param->restpos], len); + param->restpos += len; + try { +- param->crc_ctx.add(bufptr, len); ++ param->crc_ctx->add(bufptr, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return false; + } + *readres = len; +@@ -292,11 +351,17 @@ armored_src_read(pgp_source_t *src, void + } else if (bval == 0xfe) { + /* '=' means the base64 padding or the beginning of checksum */ + param->eofb64 = true; + break; + } else if (bval == 0xff) { +- RNP_LOG("wrong base64 character 0x%02hhX", *(bptr - 1)); ++ auto ch = *(bptr - 1); ++ /* OpenPGP message headers without the crc and without trailing = */ ++ if ((ch == CH_DASH) && !param->noheaders) { ++ param->eofb64 = true; ++ break; ++ } ++ RNP_LOG("wrong base64 character 0x%02hhX", ch); + return false; + } + } + + dend = dptr; +@@ -324,35 +389,48 @@ armored_src_read(pgp_source_t *src, void + + /* moving rest to the beginning of decbuf */ + memmove(decbuf, dptr, dend - dptr); + dend = decbuf + (dend - dptr); + +- if (param->eofb64) { ++ /* skip already processed data */ ++ if (!param->eofb64) { ++ /* all input is base64 data or eol/spaces, so skipping it */ ++ src_skip(param->readsrc, read); ++ /* check for eof for base64-encoded data without headers */ ++ if (param->noheaders && src_eof(param->readsrc)) { ++ src_skip(param->readsrc, read); ++ param->eofb64 = true; ++ } else { ++ continue; ++ } ++ } else { + /* '=' reached, bptr points on it */ + src_skip(param->readsrc, bptr - b64buf - 1); ++ } + +- /* reading b64 padding if any */ +- if (!armor_read_padding(src, &eqcount)) { +- RNP_LOG("wrong padding"); ++ /* end of base64 data */ ++ if (param->noheaders) { ++ if (!base64_read_padding(param, &eqcount)) { + return false; + } +- +- /* reading crc */ +- if (!armor_read_crc(src)) { +- RNP_LOG("Warning: missing or malformed CRC line"); +- } +- /* reading armor trailing line */ +- if (!armor_read_trailer(src)) { +- RNP_LOG("wrong armor trailer"); +- return false; +- } +- + break; +- } else { +- /* all input is base64 data or eol/spaces, so skipping it */ +- src_skip(param->readsrc, read); ++ } ++ /* reading b64 padding if any */ ++ if (!armor_read_padding(param, &eqcount)) { ++ RNP_LOG("wrong padding"); ++ return false; + } ++ /* reading crc */ ++ if (!armor_read_crc(src)) { ++ RNP_LOG("Warning: missing or malformed CRC line"); ++ } ++ /* reading armor trailing line */ ++ if (!armor_read_trailer(src)) { ++ RNP_LOG("wrong armor trailer"); ++ return false; ++ } ++ break; + } while (left >= 3); + + /* process bytes left in decbuf */ + + dptr = decbuf; +@@ -366,14 +444,11 @@ armored_src_read(pgp_source_t *src, void + *bptr++ = b24 >> 16; + *bptr++ = b24 >> 8; + *bptr++ = b24 & 0xff; + } + +- try { +- param->crc_ctx.add(buf, bufptr - (uint8_t *) buf); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); ++ if (!armored_update_crc(param, buf, bufptr - (uint8_t *) buf)) { + return false; + } + + if (param->eofb64) { + if ((dend - dptr + eqcount) % 4 != 0) { +@@ -387,23 +462,14 @@ armored_src_read(pgp_source_t *src, void + *bptr++ = b24 & 0xff; + } else if (eqcount == 2) { + *bptr++ = (*dptr << 2) | (*(dptr + 1) >> 4); + } + +- uint8_t crc_fin[5]; + /* Calculate CRC after reading whole input stream */ +- try { +- param->crc_ctx.add(param->rest, bptr - param->rest); +- param->crc_ctx.finish(crc_fin); +- } catch (const std::exception &e) { +- RNP_LOG("Can't finalize RNP ctx: %s", e.what()); ++ if (!armored_update_crc(param, param->rest, bptr - param->rest, true)) { + return false; + } +- +- if (param->has_crc && memcmp(param->readcrc, crc_fin, 3)) { +- RNP_LOG("Warning: CRC mismatch"); +- } + } else { + /* few bytes which do not fit to 4 boundary */ + for (int i = 0; i < dend - dptr; i++) { + param->brest[i] = *(dptr + i); + } +@@ -414,17 +480,12 @@ armored_src_read(pgp_source_t *src, void + + /* check whether we have some bytes to add */ + if ((left > 0) && (param->restlen > 0)) { + read = left > param->restlen ? param->restlen : left; + memcpy(bufptr, param->rest, read); +- if (!param->eofb64) { +- try { +- param->crc_ctx.add(bufptr, read); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- return false; +- } ++ if (!param->eofb64 && !armored_update_crc(param, bufptr, read)) { ++ return false; + } + left -= read; + param->restpos += read; + } + +@@ -731,32 +792,37 @@ armor_parse_headers(pgp_source_t *src) + } + } while (1); + } + + rnp_result_t +-init_armored_src(pgp_source_t *src, pgp_source_t *readsrc) ++init_armored_src(pgp_source_t *src, pgp_source_t *readsrc, bool noheaders) + { +- rnp_result_t errcode = RNP_ERROR_GENERIC; +- pgp_source_armored_param_t *param; +- + if (!init_src_common(src, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } +- try { +- param = new pgp_source_armored_param_t(); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); ++ pgp_source_armored_param_t *param = new (std::nothrow) pgp_source_armored_param_t(); ++ if (!param) { ++ RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + + param->readsrc = readsrc; ++ param->noheaders = noheaders; + src->param = param; + src->read = armored_src_read; + src->close = armored_src_close; + src->type = PGP_STREAM_ARMORED; + ++ /* base64 data only */ ++ if (noheaders) { ++ return RNP_SUCCESS; ++ } ++ ++ /* initialize crc context */ ++ param->crc_ctx = rnp::CRC24::create(); + /* parsing armored header */ ++ rnp_result_t errcode = RNP_ERROR_GENERIC; + if (!armor_parse_header(src)) { + errcode = RNP_ERROR_BAD_FORMAT; + goto finish; + } + /* eol */ +@@ -772,27 +838,24 @@ init_armored_src(pgp_source_t *src, pgp_ + goto finish; + } + + /* now we are good to go with base64-encoded data */ + errcode = RNP_SUCCESS; +- + finish: +- if (errcode != RNP_SUCCESS) { ++ if (errcode) { + src_close(src); + } + return errcode; + } + +-/** @brief Copy armor header of tail to the buffer. Buffer should be at least ~40 chars. */ ++/** @brief Write message header to the dst. */ + static bool +-armor_message_header(pgp_armored_msg_t type, bool finish, char *buf) ++armor_write_message_header(pgp_dest_armored_param_t *param, bool finish) + { +- const char *str; +- str = finish ? ST_ARMOR_END : ST_ARMOR_BEGIN; +- memcpy(buf, str, strlen(str)); +- buf += strlen(str); +- switch (type) { ++ const char *str = finish ? ST_ARMOR_END : ST_ARMOR_BEGIN; ++ dst_write(param->writedst, str, strlen(str)); ++ switch (param->type) { + case PGP_ARMORED_MESSAGE: + str = "MESSAGE"; + break; + case PGP_ARMORED_PUBLIC_KEY: + str = "PUBLIC KEY BLOCK"; +@@ -807,25 +870,34 @@ armor_message_header(pgp_armored_msg_t t + str = "SIGNED MESSAGE"; + break; + default: + return false; + } +- +- memcpy(buf, str, strlen(str)); +- buf += strlen(str); +- memcpy(buf, ST_DASHES, std::min(sizeof(ST_DASHES) - 1, 5)); +- buf[5] = '\0'; ++ dst_write(param->writedst, str, strlen(str)); ++ dst_write(param->writedst, ST_DASHES, strlen(ST_DASHES)); + return true; + } + + static void + armor_write_eol(pgp_dest_armored_param_t *param) + { +- if (param->usecrlf) { +- dst_write(param->writedst, ST_CRLF, 2); +- } else { +- dst_write(param->writedst, ST_LF, 1); ++ if (param->eol[0]) { ++ dst_write(param->writedst, ¶m->eol[0], 1); ++ } ++ if (param->eol[1]) { ++ dst_write(param->writedst, ¶m->eol[1], 1); ++ } ++} ++ ++static void ++armor_append_eol(pgp_dest_armored_param_t *param, uint8_t *&ptr) ++{ ++ if (param->eol[0]) { ++ *ptr++ = param->eol[0]; ++ } ++ if (param->eol[1]) { ++ *ptr++ = param->eol[1]; + } + } + + /* Base 64 encoded table, quadruplicated to save cycles on use & 0x3f operation */ + static const uint8_t B64ENC[256] = { +@@ -855,73 +927,68 @@ armored_encode3(uint8_t *out, uint8_t *i + } + + static rnp_result_t + armored_dst_write(pgp_dest_t *dst, const void *buf, size_t len) + { +- uint8_t encbuf[PGP_INPUT_CACHE_SIZE / 2]; +- uint8_t * encptr = encbuf; +- uint8_t * enclast; +- uint8_t dec3[3]; +- uint8_t * bufptr = (uint8_t *) buf; +- uint8_t * bufend = bufptr + len; +- uint8_t * inlend; +- uint32_t t; +- unsigned inllen; + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; +- + if (!param) { + RNP_LOG("wrong param"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* update crc */ +- try { +- param->crc_ctx.add(buf, len); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- return RNP_ERROR_BAD_STATE; ++ bool base64 = param->type == PGP_ARMORED_BASE64; ++ if (!base64) { ++ try { ++ param->crc_ctx->add(buf, len); ++ } catch (const std::exception &e) { ++ RNP_LOG("%s", e.what()); ++ return RNP_ERROR_BAD_STATE; ++ } + } + ++ uint8_t encbuf[PGP_INPUT_CACHE_SIZE / 2]; ++ uint8_t *bufptr = (uint8_t *) buf; ++ uint8_t *bufend = bufptr + len; ++ uint8_t *encptr = encbuf; + /* processing tail if any */ + if (len + param->tailc < 3) { + memcpy(¶m->tail[param->tailc], buf, len); + param->tailc += len; + return RNP_SUCCESS; + } else if (param->tailc > 0) { ++ uint8_t dec3[3] = {0}; + memcpy(dec3, param->tail, param->tailc); + memcpy(&dec3[param->tailc], bufptr, 3 - param->tailc); + bufptr += 3 - param->tailc; + param->tailc = 0; + armored_encode3(encptr, dec3); + encptr += 4; + param->lout += 4; + if (param->lout == param->llen) { +- if (param->usecrlf) { +- *encptr++ = CH_CR; +- } +- *encptr++ = CH_LF; ++ armor_append_eol(param, encptr); + param->lout = 0; + } + } + + /* this version prints whole chunks, so rounding down to the closest 4 */ + auto adjusted_llen = param->llen & ~3; + /* number of input bytes to form a whole line of output, param->llen / 4 * 3 */ +- inllen = (adjusted_llen >> 2) + (adjusted_llen >> 1); ++ auto inllen = (adjusted_llen >> 2) + (adjusted_llen >> 1); + /* pointer to the last full line space in encbuf */ +- enclast = encbuf + sizeof(encbuf) - adjusted_llen - 2; ++ auto enclast = encbuf + sizeof(encbuf) - adjusted_llen - 2; + + /* processing line chunks, this is the main performance-hitting cycle */ + while (bufptr + 3 <= bufend) { + /* checking whether we have enough space in encbuf */ + if (encptr > enclast) { + dst_write(param->writedst, encbuf, encptr - encbuf); + encptr = encbuf; + } + /* setup length of the input to process in this iteration */ +- inlend = param->lout == 0 ? bufptr + inllen : +- bufptr + ((adjusted_llen - param->lout) >> 2) * 3; ++ uint8_t *inlend = ++ !param->lout ? bufptr + inllen : bufptr + ((adjusted_llen - param->lout) >> 2) * 3; + if (inlend > bufend) { + /* no enough input for the full line */ + inlend = bufptr + (bufend - bufptr) / 3 * 3; + param->lout += (inlend - bufptr) / 3 * 4; + } else { +@@ -929,24 +996,21 @@ armored_dst_write(pgp_dest_t *dst, const + param->lout = 0; + } + + /* processing one line */ + while (bufptr < inlend) { +- t = (bufptr[0] << 16) | (bufptr[1] << 8) | (bufptr[2]); ++ uint32_t t = (bufptr[0] << 16) | (bufptr[1] << 8) | (bufptr[2]); + bufptr += 3; + *encptr++ = B64ENC[(t >> 18) & 0xff]; + *encptr++ = B64ENC[(t >> 12) & 0xff]; + *encptr++ = B64ENC[(t >> 6) & 0xff]; + *encptr++ = B64ENC[t & 0xff]; + } + + /* adding line ending */ +- if (param->lout == 0) { +- if (param->usecrlf) { +- *encptr++ = CH_CR; +- } +- *encptr++ = CH_LF; ++ if (!param->lout) { ++ armor_append_eol(param, encptr); + } + } + + dst_write(param->writedst, encbuf, encptr - encbuf); + +@@ -958,15 +1022,14 @@ armored_dst_write(pgp_dest_t *dst, const + } + + static rnp_result_t + armored_dst_finish(pgp_dest_t *dst) + { +- uint8_t buf[64]; +- uint8_t crcbuf[3]; + pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param; + + /* writing tail */ ++ uint8_t buf[5]; + if (param->tailc == 1) { + buf[0] = B64ENC[param->tail[0] >> 2]; + buf[1] = B64ENC[(param->tail[0] << 4) & 0xff]; + buf[2] = CH_EQ; + buf[3] = CH_EQ; +@@ -976,34 +1039,37 @@ armored_dst_finish(pgp_dest_t *dst) + buf[1] = B64ENC[((param->tail[0] << 4) | (param->tail[1] >> 4)) & 0xff]; + buf[2] = B64ENC[(param->tail[1] << 2) & 0xff]; + buf[3] = CH_EQ; + dst_write(param->writedst, buf, 4); + } ++ /* Check for base64 */ ++ if (param->type == PGP_ARMORED_BASE64) { ++ return param->writedst->werr; ++ } + + /* writing EOL if needed */ + if ((param->tailc > 0) || (param->lout > 0)) { + armor_write_eol(param); + } + + /* writing CRC and EOL */ ++ // At this point crc_ctx is initialized, so call can't fail + buf[0] = CH_EQ; +- +- // At this point crc_ctx is initialized, so call can't fail + try { +- param->crc_ctx.finish(crcbuf); ++ auto crc = param->crc_ctx->finish(); ++ armored_encode3(&buf[1], crc.data()); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } +- armored_encode3(&buf[1], crcbuf); + dst_write(param->writedst, buf, 5); + armor_write_eol(param); + + /* writing armor header */ +- armor_message_header(param->type, true, (char *) buf); +- dst_write(param->writedst, buf, strlen((char *) buf)); ++ if (!armor_write_message_header(param, true)) { ++ return RNP_ERROR_BAD_PARAMETERS; ++ } + armor_write_eol(param); +- + return param->writedst->werr; + } + + static void + armored_dst_close(pgp_dest_t *dst, bool discard) +@@ -1019,21 +1085,15 @@ armored_dst_close(pgp_dest_t *dst, bool + } + + rnp_result_t + init_armored_dst(pgp_dest_t *dst, pgp_dest_t *writedst, pgp_armored_msg_t msgtype) + { +- char hdr[64]; +- pgp_dest_armored_param_t *param; +- rnp_result_t ret = RNP_SUCCESS; +- + if (!init_dst_common(dst, 0)) { + return RNP_ERROR_OUT_OF_MEMORY; + } +- try { +- param = new pgp_dest_armored_param_t(); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); ++ pgp_dest_armored_param_t *param = new (std::nothrow) pgp_dest_armored_param_t(); ++ if (!param) { + return RNP_ERROR_OUT_OF_MEMORY; + } + + dst->param = param; + dst->write = armored_dst_write; +@@ -1043,31 +1103,34 @@ init_armored_dst(pgp_dest_t *dst, pgp_de + dst->writeb = 0; + dst->clen = 0; + + param->writedst = writedst; + param->type = msgtype; +- param->usecrlf = true; ++ /* Base64 message */ ++ if (msgtype == PGP_ARMORED_BASE64) { ++ /* Base64 encoding will not output EOLs but we need this to not duplicate code for a ++ * separate base64_dst_write function */ ++ param->eol[0] = 0; ++ param->eol[1] = 0; ++ param->llen = 256; ++ return RNP_SUCCESS; ++ } ++ /* create crc context */ ++ param->crc_ctx = rnp::CRC24::create(); ++ param->eol[0] = CH_CR; ++ param->eol[1] = CH_LF; + param->llen = 76; /* must be multiple of 4 */ +- +- if (!armor_message_header(param->type, false, hdr)) { ++ /* armor header */ ++ if (!armor_write_message_header(param, false)) { + RNP_LOG("unknown data type"); +- ret = RNP_ERROR_BAD_PARAMETERS; +- goto finish; ++ armored_dst_close(dst, true); ++ return RNP_ERROR_BAD_PARAMETERS; + } +- +- /* armor header */ +- dst_write(writedst, hdr, strlen(hdr)); + armor_write_eol(param); + /* empty line */ + armor_write_eol(param); +- +-finish: +- if (ret != RNP_SUCCESS) { +- armored_dst_close(dst, true); +- } +- +- return ret; ++ return RNP_SUCCESS; + } + + bool + is_armored_dest(pgp_dest_t *dst) + { +@@ -1110,10 +1173,22 @@ is_cleartext_source(pgp_source_t *src) + } + buf[read - 1] = 0; + return !!strstr((char *) buf, ST_CLEAR_BEGIN); + } + ++bool ++is_base64_source(pgp_source_t &src) ++{ ++ char buf[128]; ++ size_t read = 0; ++ ++ if (!src_peek(&src, buf, sizeof(buf), &read) || (read < 4)) { ++ return false; ++ } ++ return is_base64_line(buf, read); ++} ++ + rnp_result_t + rnp_dearmor_source(pgp_source_t *src, pgp_dest_t *dst) + { + rnp_result_t res = RNP_ERROR_BAD_FORMAT; + pgp_source_t armorsrc = {0}; +@@ -1148,5 +1223,65 @@ rnp_armor_source(pgp_source_t *src, pgp_ + } + + dst_close(&armordst, res != RNP_SUCCESS); + return res; + } ++ ++namespace rnp { ++ ++const uint32_t ArmoredSource::AllowBinary = 0x01; ++const uint32_t ArmoredSource::AllowBase64 = 0x02; ++const uint32_t ArmoredSource::AllowMultiple = 0x04; ++ ++ArmoredSource::ArmoredSource(pgp_source_t &readsrc, uint32_t flags) ++ : Source(), readsrc_(readsrc), multiple_(false) ++{ ++ /* Do not dearmor already armored stream */ ++ bool already = readsrc_.type == PGP_STREAM_ARMORED; ++ /* Check for base64 source: no multiple streams allowed */ ++ if (!already && (flags & AllowBase64) && (is_base64_source(readsrc))) { ++ auto res = init_armored_src(&src_, &readsrc_, true); ++ if (res) { ++ RNP_LOG("Failed to parse base64 data."); ++ throw rnp::rnp_exception(res); ++ } ++ armored_ = true; ++ return; ++ } ++ /* Check for armored source */ ++ if (!already && is_armored_source(&readsrc)) { ++ auto res = init_armored_src(&src_, &readsrc_); ++ if (res) { ++ RNP_LOG("Failed to parse armored data."); ++ throw rnp::rnp_exception(res); ++ } ++ armored_ = true; ++ multiple_ = flags & AllowMultiple; ++ return; ++ } ++ /* Use binary source if allowed */ ++ if (!(flags & AllowBinary)) { ++ RNP_LOG("Non-armored data is not allowed here."); ++ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); ++ } ++ armored_ = false; ++} ++ ++void ++ArmoredSource::restart() ++{ ++ if (!armored_ || src_eof(&readsrc_) || src_error(&readsrc_)) { ++ return; ++ } ++ src_close(&src_); ++ auto res = init_armored_src(&src_, &readsrc_); ++ if (res) { ++ throw rnp::rnp_exception(res); ++ } ++} ++ ++pgp_source_t & ++ArmoredSource::src() ++{ ++ return armored_ ? src_ : readsrc_; ++} ++} // namespace rnp +diff --git a/comm/third_party/rnp/src/librepgp/stream-armor.h b/third_party/rnp/src/lib/commrepgp/stream-armor.h +--- a/comm/third_party/rnp/src/librepgp/stream-armor.h ++++ b/comm/third_party/rnp/src/librepgp/stream-armor.h +@@ -33,19 +33,22 @@ typedef enum { + PGP_ARMORED_UNKNOWN, + PGP_ARMORED_MESSAGE, + PGP_ARMORED_PUBLIC_KEY, + PGP_ARMORED_SECRET_KEY, + PGP_ARMORED_SIGNATURE, +- PGP_ARMORED_CLEARTEXT ++ PGP_ARMORED_CLEARTEXT, ++ PGP_ARMORED_BASE64 + } pgp_armored_msg_t; + + /* @brief Init dearmoring stream + * @param src allocated pgp_source_t structure + * @param readsrc source to read data from + * @return RNP_SUCCESS on success or error code otherwise + **/ +-rnp_result_t init_armored_src(pgp_source_t *src, pgp_source_t *readsrc); ++rnp_result_t init_armored_src(pgp_source_t *src, ++ pgp_source_t *readsrc, ++ bool noheaders = false); + + /* @brief Init armoring stream + * @param dst allocated pgp_dest_t structure + * @param writedst destination to write armored data to + * @param msgtype type of the message (see pgp_armored_msg_t) +@@ -98,14 +101,74 @@ bool is_armored_dest(pgp_dest_t *dst); + * @param src initialized source with some data + * @return true if source could be a cleartext signed data or false otherwise + **/ + bool is_cleartext_source(pgp_source_t *src); + ++/** @brief Check whether source is base64-encoded ++ * @param src initialized source with some data ++ * @return true if source could be a base64-encoded data or false otherwise ++ **/ ++bool is_base64_source(pgp_source_t &src); ++ + /** Set line length for armoring + * + * @param dst initialized dest to write armored data to + * @param llen line length in characters + * @return RNP_SUCCESS on success, or any other value on error + */ + rnp_result_t armored_dst_set_line_length(pgp_dest_t *dst, size_t llen); + ++namespace rnp { ++ ++class ArmoredSource : public Source { ++ pgp_source_t &readsrc_; ++ bool armored_; ++ bool multiple_; ++ ++ public: ++ static const uint32_t AllowBinary; ++ static const uint32_t AllowBase64; ++ static const uint32_t AllowMultiple; ++ ++ ArmoredSource(const ArmoredSource &) = delete; ++ ArmoredSource(ArmoredSource &&) = delete; ++ ++ ArmoredSource(pgp_source_t &readsrc, uint32_t flags = 0); ++ ++ pgp_source_t &src(); ++ ++ bool ++ multiple() ++ { ++ return multiple_; ++ } ++ ++ /* Restart dearmoring in case of multiple armored messages in a single stream */ ++ void restart(); ++}; ++ ++class ArmoredDest : public Dest { ++ pgp_dest_t &writedst_; ++ ++ public: ++ ArmoredDest(const ArmoredDest &) = delete; ++ ArmoredDest(ArmoredDest &&) = delete; ++ ++ ArmoredDest(pgp_dest_t &writedst, pgp_armored_msg_t msgtype) : Dest(), writedst_(writedst) ++ { ++ auto ret = init_armored_dst(&dst_, &writedst_, msgtype); ++ if (ret) { ++ throw rnp::rnp_exception(ret); ++ } ++ }; ++ ++ ~ArmoredDest() ++ { ++ if (!discard_) { ++ dst_finish(&dst_); ++ } ++ } ++}; ++ ++} // namespace rnp ++ + #endif +diff --git a/comm/third_party/rnp/src/librepgp/stream-common.cpp b/third_party/rnp/src/lib/commrepgp/stream-common.cpp +--- a/comm/third_party/rnp/src/librepgp/stream-common.cpp ++++ b/comm/third_party/rnp/src/librepgp/stream-common.cpp +@@ -229,10 +229,13 @@ src_skip(pgp_source_t *src, size_t len) + uint8_t sbuf[16]; + if (len < sizeof(sbuf)) { + (void) src_read(src, sbuf, len, &res); + return; + } ++ if (src_eof(src)) { ++ return; ++ } + + void *buf = calloc(1, std::min((size_t) PGP_INPUT_CACHE_SIZE, len)); + if (!buf) { + src->error = 1; + return; +@@ -624,19 +627,20 @@ mem_src_get_memory(pgp_source_t *src, bo + + bool + init_dst_common(pgp_dest_t *dst, size_t paramsize) + { + memset(dst, 0, sizeof(*dst)); +- if (paramsize) { +- dst->param = calloc(1, paramsize); +- if (!dst->param) { +- RNP_LOG("allocation failed"); +- return false; +- } ++ dst->werr = RNP_SUCCESS; ++ if (!paramsize) { ++ return true; + } +- dst->werr = RNP_SUCCESS; +- return true; ++ /* allocate param */ ++ dst->param = calloc(1, paramsize); ++ if (!dst->param) { ++ RNP_LOG("allocation failed"); ++ } ++ return dst->param; + } + + void + dst_write(pgp_dest_t *dst, const void *buf, size_t len) + { +diff --git a/comm/third_party/rnp/src/librepgp/stream-common.h b/third_party/rnp/src/lib/commrepgp/stream-common.h +--- a/comm/third_party/rnp/src/librepgp/stream-common.h ++++ b/comm/third_party/rnp/src/librepgp/stream-common.h +@@ -372,6 +372,185 @@ rnp_result_t init_null_dest(pgp_dest_t * + * if 0, no limit is imposed + * @return RNP_SUCCESS or error code + **/ + rnp_result_t dst_write_src(pgp_source_t *src, pgp_dest_t *dst, uint64_t limit = 0); + ++namespace rnp { ++/* Temporary wrapper to destruct stack-based pgp_source_t */ ++class Source { ++ protected: ++ pgp_source_t src_; ++ ++ public: ++ Source(const Source &) = delete; ++ Source(Source &&) = delete; ++ ++ Source() : src_({}) ++ { ++ } ++ ++ virtual ~Source() ++ { ++ src_close(&src_); ++ } ++ ++ virtual pgp_source_t & ++ src() ++ { ++ return src_; ++ } ++ ++ size_t ++ size() ++ { ++ return src().size; ++ } ++ ++ size_t ++ readb() ++ { ++ return src().readb; ++ } ++ ++ bool ++ eof() ++ { ++ return src_eof(&src()); ++ } ++ ++ bool ++ error() ++ { ++ return src_error(&src()); ++ } ++}; ++ ++class MemorySource : public Source { ++ public: ++ MemorySource(const MemorySource &) = delete; ++ MemorySource(MemorySource &&) = delete; ++ ++ /** ++ * @brief Construct memory source object. ++ * ++ * @param mem source memory. Must be valid for the whole lifetime of the object. ++ * @param len size of the memory. ++ * @param free free memory once processing is finished. ++ */ ++ MemorySource(const void *mem, size_t len, bool free) : Source() ++ { ++ auto res = init_mem_src(&src_, mem, len, free); ++ if (res) { ++ throw std::bad_alloc(); ++ } ++ } ++ ++ /** ++ * @brief Construct memory source object ++ * ++ * @param vec vector with data. Must be valid for the whole lifetime of the object. ++ */ ++ MemorySource(const std::vector &vec) : MemorySource(vec.data(), vec.size(), false) ++ { ++ } ++ ++ MemorySource(pgp_source_t &src) : Source() ++ { ++ auto res = read_mem_src(&src_, &src); ++ if (res) { ++ throw rnp::rnp_exception(res); ++ } ++ } ++ ++ const void * ++ memory(bool own = false) ++ { ++ return mem_src_get_memory(&src_, own); ++ } ++}; ++ ++/* Temporary wrapper to destruct stack-based pgp_dest_t */ ++class Dest { ++ protected: ++ pgp_dest_t dst_; ++ bool discard_; ++ ++ public: ++ Dest(const Dest &) = delete; ++ Dest(Dest &&) = delete; ++ ++ Dest() : dst_({}), discard_(false) ++ { ++ } ++ ++ virtual ~Dest() ++ { ++ dst_close(&dst_, discard_); ++ } ++ ++ void ++ write(const void *buf, size_t len) ++ { ++ dst_write(&dst_, buf, len); ++ } ++ ++ void ++ set_discard(bool discard) ++ { ++ discard_ = discard; ++ } ++ ++ pgp_dest_t & ++ dst() ++ { ++ return dst_; ++ } ++ ++ size_t ++ writeb() ++ { ++ return dst_.writeb; ++ } ++ ++ rnp_result_t ++ werr() ++ { ++ return dst_.werr; ++ } ++}; ++ ++class MemoryDest : public Dest { ++ public: ++ MemoryDest(const MemoryDest &) = delete; ++ MemoryDest(MemoryDest &&) = delete; ++ ++ MemoryDest(void *mem = NULL, size_t len = 0) : Dest() ++ { ++ auto res = init_mem_dest(&dst_, mem, len); ++ if (res) { ++ throw std::bad_alloc(); ++ } ++ discard_ = true; ++ } ++ ++ void * ++ memory() ++ { ++ return mem_dest_get_memory(&dst_); ++ } ++ ++ void ++ set_secure(bool secure) ++ { ++ mem_dest_secure_memory(&dst_, secure); ++ } ++ ++ std::vector ++ to_vector() ++ { ++ uint8_t *mem = (uint8_t *) memory(); ++ return std::vector(mem, mem + writeb()); ++ } ++}; ++} // namespace rnp ++ + #endif +diff --git a/comm/third_party/rnp/src/librepgp/stream-ctx.cpp b/third_party/rnp/src/lib/commrepgp/stream-ctx.cpp +--- a/comm/third_party/rnp/src/librepgp/stream-ctx.cpp ++++ b/comm/third_party/rnp/src/librepgp/stream-ctx.cpp +@@ -29,30 +29,23 @@ + #include "defaults.h" + #include "utils.h" + #include "stream-ctx.h" + + rnp_result_t +-rnp_ctx_add_encryption_password(rnp_ctx_t & ctx, +- const char * password, +- pgp_hash_alg_t halg, +- pgp_symm_alg_t ealg, +- int iterations) ++rnp_ctx_t::add_encryption_password(const std::string &password, ++ pgp_hash_alg_t halg, ++ pgp_symm_alg_t ealg, ++ size_t iterations) + { + rnp_symmetric_pass_info_t info = {}; + + info.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED; + info.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED; + info.s2k.hash_alg = halg; +- +- try { +- ctx.ctx->rng.get(info.s2k.salt, sizeof(info.s2k.salt)); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- return RNP_ERROR_RNG; +- } +- if (iterations == 0) { +- iterations = pgp_s2k_compute_iters(halg, DEFAULT_S2K_MSEC, DEFAULT_S2K_TUNE_MSEC); ++ ctx->rng.get(info.s2k.salt, sizeof(info.s2k.salt)); ++ if (!iterations) { ++ iterations = ctx->s2k_iterations(halg); + } + if (!iterations) { + return RNP_ERROR_BAD_PARAMETERS; + } + info.s2k.iterations = pgp_s2k_encode_iterations(iterations); +@@ -66,16 +59,11 @@ rnp_ctx_add_encryption_password(rnp_ctx_ + * end up being used with until later. + * + * An alternative would be to keep a list of actual passwords and s2k params, + * and save the key derivation for later. + */ +- if (!pgp_s2k_derive_key(&info.s2k, password, info.key.data(), info.key.size())) { ++ if (!pgp_s2k_derive_key(&info.s2k, password.c_str(), info.key.data(), info.key.size())) { + return RNP_ERROR_GENERIC; + } +- try { +- ctx.passwords.push_back(info); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- return RNP_ERROR_OUT_OF_MEMORY; +- } ++ passwords.push_back(info); + return RNP_SUCCESS; + } +diff --git a/comm/third_party/rnp/src/librepgp/stream-ctx.h b/third_party/rnp/src/lib/commrepgp/stream-ctx.h +--- a/comm/third_party/rnp/src/librepgp/stream-ctx.h ++++ b/comm/third_party/rnp/src/librepgp/stream-ctx.h +@@ -35,17 +35,10 @@ + #include + #include "pgp-key.h" + #include "crypto/mem.h" + #include "sec_profile.hpp" + +-typedef enum rnp_operation_t { +- RNP_OP_UNKNOWN = 0, +- RNP_OP_DECRYPT_VERIFY = 1, +- RNP_OP_ENCRYPT_SIGN = 2, +- RNP_OP_ARMOR = 3 +-} rnp_operation_t; +- + /* signature info structure */ + typedef struct rnp_signer_info_t { + pgp_key_t * key{}; + pgp_hash_alg_t halg{}; + int64_t sigcreate{}; +@@ -106,27 +99,25 @@ typedef struct rnp_ctx_t { + int zlevel{}; /* compression level */ + pgp_aead_alg_t aalg{}; /* non-zero to use AEAD */ + int abits{}; /* AEAD chunk bits */ + bool overwrite{}; /* allow to overwrite output file if exists */ + bool armor{}; /* whether to use ASCII armor on output */ ++ bool no_wrap{}; /* do not wrap source in literal data packet */ + std::list recipients{}; /* recipients of the encrypted message */ + std::list passwords{}; /* passwords to encrypt message */ + std::list signers{}; /* keys to which sign message */ +- bool discard{}; /* discard the output */ + rnp::SecurityContext * ctx{}; /* pointer to rnp::RNG */ +- rnp_operation_t operation{}; /* current operation type */ + + rnp_ctx_t() = default; + rnp_ctx_t(const rnp_ctx_t &) = delete; + rnp_ctx_t(rnp_ctx_t &&) = delete; + + rnp_ctx_t &operator=(const rnp_ctx_t &) = delete; + rnp_ctx_t &operator=(rnp_ctx_t &&) = delete; ++ ++ rnp_result_t add_encryption_password(const std::string &password, ++ pgp_hash_alg_t halg, ++ pgp_symm_alg_t ealg, ++ size_t iterations = 0); + } rnp_ctx_t; + +-rnp_result_t rnp_ctx_add_encryption_password(rnp_ctx_t & ctx, +- const char * password, +- pgp_hash_alg_t halg, +- pgp_symm_alg_t ealg, +- int iterations); +- + #endif +diff --git a/comm/third_party/rnp/src/librepgp/stream-def.h b/third_party/rnp/src/lib/commrepgp/stream-def.h +--- a/comm/third_party/rnp/src/librepgp/stream-def.h ++++ b/comm/third_party/rnp/src/librepgp/stream-def.h +@@ -57,10 +57,11 @@ + #define PGP_AEAD_CACHE_LEN (PGP_INPUT_CACHE_SIZE + PGP_AEAD_MAX_TAG_LEN) + + /* Maximum OpenPGP packet nesting level */ + #define MAXIMUM_NESTING_LEVEL 32 + #define MAXIMUM_STREAM_PKTS 16 ++#define MAXIMUM_ERROR_PKTS 64 + + /* Maximum text line length supported by GnuPG */ + #define MAXIMUM_GNUPG_LINELEN 19995 + + #endif /* !STREAM_DEF_H_ */ +diff --git a/comm/third_party/rnp/src/librepgp/stream-dump.cpp b/third_party/rnp/src/lib/commrepgp/stream-dump.cpp +--- a/comm/third_party/rnp/src/librepgp/stream-dump.cpp ++++ b/comm/third_party/rnp/src/librepgp/stream-dump.cpp +@@ -772,11 +772,11 @@ stream_dump_signature_pkt(rnp_dump_ctx_t + } + indent_dest_decrease(dst); + indent_dest_decrease(dst); + } + +-static void ++static rnp_result_t + stream_dump_signature(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) + { + pgp_signature_t sig; + rnp_result_t ret; + +@@ -789,13 +789,14 @@ stream_dump_signature(rnp_dump_ctx_t *ct + } + if (ret) { + indent_dest_increase(dst); + dst_printf(dst, "failed to parse\n"); + indent_dest_decrease(dst); +- return; ++ return ret; + } + stream_dump_signature_pkt(ctx, &sig, dst); ++ return ret; + } + + static rnp_result_t + stream_dump_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst) + { +@@ -1290,12 +1291,11 @@ stream_dump_packets_raw(rnp_dump_ctx_t * + dst_printf(dst, "\n"); + } + + switch (hdr.tag) { + case PGP_PKT_SIGNATURE: +- stream_dump_signature(ctx, src, dst); +- ret = RNP_SUCCESS; ++ ret = stream_dump_signature(ctx, src, dst); + break; + case PGP_PKT_SECRET_KEY: + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_SECRET_SUBKEY: + case PGP_PKT_PUBLIC_SUBKEY: +@@ -1337,15 +1337,21 @@ stream_dump_packets_raw(rnp_dump_ctx_t * + ret = stream_skip_packet(src); + break; + default: + dst_printf(dst, "Skipping Unknown pkt: %d\n\n", (int) hdr.tag); + ret = stream_skip_packet(src); ++ if (ret) { ++ goto finish; ++ } + } + + if (ret) { + RNP_LOG("failed to process packet"); +- goto finish; ++ if (++ctx->failures > MAXIMUM_ERROR_PKTS) { ++ RNP_LOG("too many packet dump errors."); ++ goto finish; ++ } + } + + if (ctx->stream_pkts > MAXIMUM_STREAM_PKTS) { + RNP_LOG("Too many OpenPGP stream packets during the dump."); + dst_printf(dst, ":too many OpenPGP stream packets, stopping.\n"); +@@ -1392,10 +1398,11 @@ stream_dump_packets(rnp_dump_ctx_t *ctx, + bool indent = false; + rnp_result_t ret = RNP_ERROR_GENERIC; + + ctx->layers = 0; + ctx->stream_pkts = 0; ++ ctx->failures = 0; + /* check whether source is cleartext - then skip till the signature */ + if (is_cleartext_source(src)) { + dst_printf(dst, ":cleartext signed data\n"); + if (!stream_skip_cleartext(src)) { + RNP_LOG("malformed cleartext signed data"); +@@ -1881,11 +1888,11 @@ stream_dump_signature_json(rnp_dump_ctx_ + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + ret = RNP_ERROR_GENERIC; + } + if (ret) { +- return RNP_SUCCESS; ++ return ret; + } + return stream_dump_signature_pkt_json(ctx, &sig, pkt); + } + + static rnp_result_t +@@ -2451,11 +2458,15 @@ stream_dump_raw_packets_json(rnp_dump_ct + ret = stream_skip_packet(src); + } + + if (ret) { + RNP_LOG("failed to process packet"); +- goto done; ++ if (++ctx->failures > MAXIMUM_ERROR_PKTS) { ++ RNP_LOG("too many packet dump errors."); ++ goto done; ++ } ++ ret = RNP_SUCCESS; + } + + if (json_object_array_add(pkts, pkt)) { + ret = RNP_ERROR_OUT_OF_MEMORY; + goto done; +@@ -2484,10 +2495,12 @@ stream_dump_packets_json(rnp_dump_ctx_t + pgp_source_t armorsrc = {0}; + bool armored = false; + rnp_result_t ret = RNP_ERROR_GENERIC; + + ctx->layers = 0; ++ ctx->stream_pkts = 0; ++ ctx->failures = 0; + /* check whether source is cleartext - then skip till the signature */ + if (is_cleartext_source(src)) { + if (!stream_skip_cleartext(src)) { + RNP_LOG("malformed cleartext signed data"); + ret = RNP_ERROR_BAD_FORMAT; +diff --git a/comm/third_party/rnp/src/librepgp/stream-dump.h b/third_party/rnp/src/lib/commrepgp/stream-dump.h +--- a/comm/third_party/rnp/src/librepgp/stream-dump.h ++++ b/comm/third_party/rnp/src/librepgp/stream-dump.h +@@ -39,10 +39,11 @@ typedef struct rnp_dump_ctx_t { + bool dump_mpi; + bool dump_packets; + bool dump_grips; + size_t layers; + size_t stream_pkts; ++ size_t failures; + } rnp_dump_ctx_t; + + rnp_result_t stream_dump_packets(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst); + + rnp_result_t stream_dump_packets_json(rnp_dump_ctx_t *ctx, +diff --git a/comm/third_party/rnp/src/librepgp/stream-key.cpp b/third_party/rnp/src/lib/commrepgp/stream-key.cpp +--- a/comm/third_party/rnp/src/librepgp/stream-key.cpp ++++ b/comm/third_party/rnp/src/librepgp/stream-key.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2018-2020, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2018-2022, [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: + * +@@ -87,20 +87,18 @@ transferable_userid_merge(pgp_transferab + } + + rnp_result_t + transferable_subkey_from_key(pgp_transferable_subkey_t &dst, const pgp_key_t &key) + { +- pgp_source_t memsrc = {}; +- rnp_result_t ret = RNP_ERROR_GENERIC; +- +- if (!rnp_key_to_src(&key, &memsrc)) { +- return RNP_ERROR_BAD_STATE; ++ try { ++ auto vec = rnp_key_to_vec(key); ++ rnp::MemorySource mem(vec); ++ return process_pgp_subkey(mem.src(), dst, false); ++ } catch (const std::exception &e) { ++ RNP_LOG("%s", e.what()); ++ return RNP_ERROR_GENERIC; + } +- +- ret = process_pgp_subkey(memsrc, dst, false); +- src_close(&memsrc); +- return ret; + } + + rnp_result_t + transferable_subkey_merge(pgp_transferable_subkey_t &dst, const pgp_transferable_subkey_t &src) + { +@@ -117,20 +115,18 @@ transferable_subkey_merge(pgp_transferab + } + + rnp_result_t + transferable_key_from_key(pgp_transferable_key_t &dst, const pgp_key_t &key) + { +- pgp_source_t memsrc = {}; +- rnp_result_t ret = RNP_ERROR_GENERIC; +- +- if (!rnp_key_to_src(&key, &memsrc)) { +- return RNP_ERROR_BAD_STATE; ++ try { ++ auto vec = rnp_key_to_vec(key); ++ rnp::MemorySource mem(vec); ++ return process_pgp_key(mem.src(), dst, false); ++ } catch (const std::exception &e) { ++ RNP_LOG("%s", e.what()); ++ return RNP_ERROR_GENERIC; + } +- +- ret = process_pgp_key(&memsrc, dst, false); +- src_close(&memsrc); +- return ret; + } + + static pgp_transferable_userid_t * + transferable_key_has_userid(pgp_transferable_key_t &src, const pgp_userid_pkt_t &userid) + { +@@ -342,11 +338,19 @@ process_pgp_key_auto(pgp_source_t & + + rnp_result_t ret = RNP_ERROR_BAD_FORMAT; + if (!is_primary_key_pkt(ptag)) { + RNP_LOG("wrong key tag: %d at pos %" PRIu64, ptag, src.readb); + } else { +- ret = process_pgp_key(&src, key, skiperrors); ++ try { ++ ret = process_pgp_key(src, key, skiperrors); ++ } catch (const rnp::rnp_exception &e) { ++ RNP_LOG("%s", e.what()); ++ ret = e.code(); ++ } catch (const std::exception &e) { ++ RNP_LOG("%s", e.what()); ++ ret = RNP_ERROR_GENERIC; ++ } + } + if (skiperrors && (ret == RNP_ERROR_BAD_FORMAT) && + !skip_pgp_packets(&src, + {PGP_PKT_TRUST, + PGP_PKT_SIGNATURE, +@@ -362,247 +366,127 @@ process_pgp_key_auto(pgp_source_t & + } + return ret; + } + + rnp_result_t +-process_pgp_keys(pgp_source_t *src, pgp_key_sequence_t &keys, bool skiperrors) ++process_pgp_keys(pgp_source_t &src, pgp_key_sequence_t &keys, bool skiperrors) + { +- bool armored = false; +- pgp_source_t armorsrc = {0}; +- pgp_source_t *origsrc = src; +- bool has_secret = false; +- bool has_public = false; +- rnp_result_t ret = RNP_ERROR_GENERIC; ++ bool has_secret = false; ++ bool has_public = false; + + keys.keys.clear(); +- /* check whether keys are armored */ +-armoredpass: +- if ((src->type != PGP_STREAM_ARMORED) && is_armored_source(src)) { +- if (init_armored_src(&armorsrc, src)) { +- RNP_LOG("failed to parse armored data"); +- ret = RNP_ERROR_READ; +- goto finish; +- } +- armored = true; +- src = &armorsrc; +- } ++ /* create maybe-armored stream */ ++ rnp::ArmoredSource armor( ++ src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); + + /* read sequence of transferable OpenPGP keys as described in RFC 4880, 11.1 - 11.2 */ +- while (!src_eof(src) && !src_error(src)) { ++ while (!armor.error()) { ++ /* Allow multiple armored messages in a single stream */ ++ if (armor.eof() && armor.multiple()) { ++ armor.restart(); ++ } ++ if (armor.eof()) { ++ break; ++ } ++ /* Attempt to read the next key */ + pgp_transferable_key_t curkey; +- ret = process_pgp_key_auto(*src, curkey, false, skiperrors); ++ rnp_result_t ret = process_pgp_key_auto(armor.src(), curkey, false, skiperrors); + if (ret && (!skiperrors || (ret != RNP_ERROR_BAD_FORMAT))) { +- goto finish; ++ keys.keys.clear(); ++ return ret; + } + /* check whether we actually read any key or just skipped erroneous packets */ + if (curkey.key.tag == PGP_PKT_RESERVED) { + continue; + } + has_secret |= (curkey.key.tag == PGP_PKT_SECRET_KEY); + has_public |= (curkey.key.tag == PGP_PKT_PUBLIC_KEY); + +- try { +- keys.keys.emplace_back(std::move(curkey)); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto finish; +- } +- } +- +- /* file may have multiple armored keys */ +- if (armored && !src_eof(origsrc) && is_armored_source(origsrc)) { +- src_close(&armorsrc); +- armored = false; +- src = origsrc; +- goto armoredpass; ++ keys.keys.emplace_back(std::move(curkey)); + } + + if (has_secret && has_public) { + RNP_LOG("warning! public keys are mixed together with secret ones!"); + } + +- ret = RNP_SUCCESS; +-finish: +- if (armored) { +- src_close(&armorsrc); ++ if (armor.error()) { ++ keys.keys.clear(); ++ return RNP_ERROR_READ; + } +- if (ret) { +- keys.keys.clear(); +- } +- return ret; ++ return RNP_SUCCESS; + } + + rnp_result_t +-process_pgp_key(pgp_source_t *src, pgp_transferable_key_t &key, bool skiperrors) ++process_pgp_key(pgp_source_t &src, pgp_transferable_key_t &key, bool skiperrors) + { +- pgp_source_t armorsrc = {0}; +- bool armored = false; +- int ptag; +- rnp_result_t ret = RNP_ERROR_GENERIC; +- + key = pgp_transferable_key_t(); +- /* check whether keys are armored */ +- if ((src->type != PGP_STREAM_ARMORED) && is_armored_source(src)) { +- if (init_armored_src(&armorsrc, src)) { +- RNP_LOG("failed to parse armored data"); +- return RNP_ERROR_READ; +- } +- armored = true; +- src = &armorsrc; ++ /* create maybe-armored stream */ ++ rnp::ArmoredSource armor( ++ src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); ++ ++ /* main key packet */ ++ uint64_t keypos = armor.readb(); ++ int ptag = stream_pkt_type(&armor.src()); ++ if ((ptag <= 0) || !is_primary_key_pkt(ptag)) { ++ RNP_LOG("wrong key packet tag: %d at %" PRIu64, ptag, keypos); ++ return RNP_ERROR_BAD_FORMAT; + } + +- /* main key packet */ +- uint64_t keypos = src->readb; +- ptag = stream_pkt_type(src); +- if ((ptag <= 0) || !is_primary_key_pkt(ptag)) { +- RNP_LOG("wrong key packet tag: %d at %" PRIu64, ptag, keypos); +- ret = RNP_ERROR_BAD_FORMAT; +- goto finish; +- } +- +- try { +- ret = key.key.parse(*src); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- ret = RNP_ERROR_GENERIC; +- } ++ rnp_result_t ret = key.key.parse(armor.src()); + if (ret) { + RNP_LOG("failed to parse key pkt at %" PRIu64, keypos); + key.key = {}; +- goto finish; ++ return ret; + } + +- if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) { +- ret = RNP_ERROR_READ; +- goto finish; ++ if (!skip_pgp_packets(&armor.src(), {PGP_PKT_TRUST})) { ++ return RNP_ERROR_READ; + } + + /* direct-key signatures */ +- if ((ret = process_pgp_key_signatures(src, key.signatures, skiperrors))) { +- goto finish; ++ if ((ret = process_pgp_key_signatures(&armor.src(), key.signatures, skiperrors))) { ++ return ret; + } + + /* user ids/attrs with signatures */ +- while ((ptag = stream_pkt_type(src)) > 0) { ++ while ((ptag = stream_pkt_type(&armor.src())) > 0) { + if ((ptag != PGP_PKT_USER_ID) && (ptag != PGP_PKT_USER_ATTR)) { + break; + } + +- try { +- pgp_transferable_userid_t uid; +- ret = process_pgp_userid(src, uid, skiperrors); +- if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && +- skip_pgp_packets(src, {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { +- /* skip malformed uid */ +- continue; +- } +- if (ret) { +- goto finish; +- } +- key.userids.push_back(std::move(uid)); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto finish; ++ pgp_transferable_userid_t uid; ++ ret = process_pgp_userid(&armor.src(), uid, skiperrors); ++ if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && ++ skip_pgp_packets(&armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { ++ /* skip malformed uid */ ++ continue; + } ++ if (ret) { ++ return ret; ++ } ++ key.userids.push_back(std::move(uid)); + } + + /* subkeys with signatures */ +- while ((ptag = stream_pkt_type(src)) > 0) { ++ while ((ptag = stream_pkt_type(&armor.src())) > 0) { + if (!is_subkey_pkt(ptag)) { + break; + } + + pgp_transferable_subkey_t subkey; +- ret = process_pgp_subkey(*src, subkey, skiperrors); ++ ret = process_pgp_subkey(armor.src(), subkey, skiperrors); + if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors && +- skip_pgp_packets(src, {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { ++ skip_pgp_packets(&armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) { + /* skip malformed subkey */ + continue; + } +- try { +- key.subkeys.emplace_back(std::move(subkey)); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- ret = RNP_ERROR_OUT_OF_MEMORY; +- } + if (ret) { +- goto finish; +- } +- } +- ret = ptag >= 0 ? RNP_SUCCESS : RNP_ERROR_BAD_FORMAT; +-finish: +- if (armored) { +- src_close(&armorsrc); +- } +- return ret; +-} +- +-rnp_result_t +-write_pgp_keys(pgp_key_sequence_t &keys, pgp_dest_t *dst, bool armor) +-{ +- pgp_dest_t armdst = {0}; +- rnp_result_t ret = RNP_ERROR_GENERIC; +- +- if (armor) { +- pgp_armored_msg_t msgtype = PGP_ARMORED_PUBLIC_KEY; +- if (!keys.keys.empty() && is_secret_key_pkt(keys.keys.front().key.tag)) { +- msgtype = PGP_ARMORED_SECRET_KEY; +- } +- +- if ((ret = init_armored_dst(&armdst, dst, msgtype))) { + return ret; + } +- dst = &armdst; ++ key.subkeys.emplace_back(std::move(subkey)); + } +- +- try { +- for (auto &key : keys.keys) { +- /* main key */ +- key.key.write(*dst); +- /* revocation and direct-key signatures */ +- for (auto &sig : key.signatures) { +- sig.write(*dst); +- } +- /* user ids/attrs and signatures */ +- for (auto &uid : key.userids) { +- uid.uid.write(*dst); +- for (auto &sig : uid.signatures) { +- sig.write(*dst); +- } +- } +- /* subkeys with signatures */ +- for (auto &skey : key.subkeys) { +- skey.subkey.write(*dst); +- for (auto &sig : skey.signatures) { +- sig.write(*dst); +- } +- } +- } +- ret = dst->werr; +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- ret = RNP_ERROR_WRITE; +- } +- if (armor) { +- dst_close(&armdst, ret); +- } +- return ret; +-} +- +-rnp_result_t +-write_pgp_key(pgp_transferable_key_t &key, pgp_dest_t *dst, bool armor) +-{ +- try { +- pgp_key_sequence_t keys; +- keys.keys.push_back(key); +- return write_pgp_keys(keys, dst, armor); +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- return RNP_ERROR_OUT_OF_MEMORY; +- } ++ return ptag >= 0 ? RNP_SUCCESS : RNP_ERROR_BAD_FORMAT; + } + + static rnp_result_t + decrypt_secret_key_v3(pgp_crypt_t *crypt, uint8_t *dec, const uint8_t *enc, size_t len) + { +@@ -680,15 +564,15 @@ parse_secret_key_mpis(pgp_key_pkt_t &key + return RNP_ERROR_BAD_FORMAT; + } + /* calculate and check sha1 hash of the cleartext */ + uint8_t hval[PGP_SHA1_HASH_SIZE]; + try { +- rnp::Hash hash(PGP_HASH_SHA1); +- assert(hash.size() == sizeof(hval)); ++ auto hash = rnp::Hash::create(PGP_HASH_SHA1); ++ assert(hash->size() == sizeof(hval)); + len -= PGP_SHA1_HASH_SIZE; +- hash.add(mpis, len); +- if (hash.finish(hval) != PGP_SHA1_HASH_SIZE) { ++ hash->add(mpis, len); ++ if (hash->finish(hval) != PGP_SHA1_HASH_SIZE) { + return RNP_ERROR_BAD_STATE; + } + } catch (const std::exception &e) { + RNP_LOG("hash calculation failed: %s", e.what()); + return RNP_ERROR_BAD_STATE; +@@ -876,15 +760,15 @@ write_secret_key_mpis(pgp_packet_body_t + body.add_uint16(sum); + return; + } + + /* add sha1 hash */ +- rnp::Hash hash(PGP_HASH_SHA1); +- hash.add(body.data(), body.size()); ++ auto hash = rnp::Hash::create(PGP_HASH_SHA1); ++ hash->add(body.data(), body.size()); + uint8_t hval[PGP_SHA1_HASH_SIZE]; +- assert(sizeof(hval) == hash.size()); +- if (hash.finish(hval) != PGP_SHA1_HASH_SIZE) { ++ assert(sizeof(hval) == hash->size()); ++ if (hash->finish(hval) != PGP_SHA1_HASH_SIZE) { + RNP_LOG("failed to finish hash"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + body.add(hval, PGP_SHA1_HASH_SIZE); + } +diff --git a/comm/third_party/rnp/src/librepgp/stream-key.h b/third_party/rnp/src/lib/commrepgp/stream-key.h +--- a/comm/third_party/rnp/src/librepgp/stream-key.h ++++ b/comm/third_party/rnp/src/librepgp/stream-key.h +@@ -124,22 +124,18 @@ rnp_result_t transferable_subkey_merge(p + rnp_result_t process_pgp_key_auto(pgp_source_t & src, + pgp_transferable_key_t &key, + bool allowsub, + bool skiperrors); + +-rnp_result_t process_pgp_keys(pgp_source_t *src, pgp_key_sequence_t &keys, bool skiperrors); ++rnp_result_t process_pgp_keys(pgp_source_t &src, pgp_key_sequence_t &keys, bool skiperrors); + +-rnp_result_t process_pgp_key(pgp_source_t *src, pgp_transferable_key_t &key, bool skiperrors); ++rnp_result_t process_pgp_key(pgp_source_t &src, pgp_transferable_key_t &key, bool skiperrors); + + rnp_result_t process_pgp_subkey(pgp_source_t & src, + pgp_transferable_subkey_t &subkey, + bool skiperrors); + +-rnp_result_t write_pgp_key(pgp_transferable_key_t &key, pgp_dest_t *dst, bool armor); +- +-rnp_result_t write_pgp_keys(pgp_key_sequence_t &keys, pgp_dest_t *dst, bool armor); +- + rnp_result_t decrypt_secret_key(pgp_key_pkt_t *key, const char *password); + + rnp_result_t encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng); + + void forget_secret_key_fields(pgp_key_material_t *key); +diff --git a/comm/third_party/rnp/src/librepgp/stream-parse.cpp b/third_party/rnp/src/lib/commrepgp/stream-parse.cpp +--- a/comm/third_party/rnp/src/librepgp/stream-parse.cpp ++++ b/comm/third_party/rnp/src/librepgp/stream-parse.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2017-2022, [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: + * +@@ -90,11 +90,11 @@ typedef struct pgp_source_encrypted_para + 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 */ +- rnp::Hash mdc; /* mdc SHA1 hash */ ++ std::unique_ptr 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 */ +@@ -672,18 +672,19 @@ encrypted_src_read_cfb(pgp_source_t *src + + pgp_cipher_cfb_decrypt(¶m->decrypt, (uint8_t *) buf, (uint8_t *) buf, read); + + if (param->has_mdc) { + try { +- param->mdc.add(buf, read); ++ param->mdc->add(buf, read); + + if (parsemdc) { + pgp_cipher_cfb_decrypt(¶m->decrypt, mdcbuf, mdcbuf, MDC_V1_SIZE); + pgp_cipher_cfb_finish(¶m->decrypt); +- param->mdc.add(mdcbuf, 2); ++ param->mdc->add(mdcbuf, 2); + uint8_t hash[PGP_SHA1_HASH_SIZE] = {0}; +- param->mdc.finish(hash); ++ param->mdc->finish(hash); ++ param->mdc = nullptr; + + if ((mdcbuf[0] != MDC_PKT_TAG) || (mdcbuf[1] != MDC_V1_SIZE - 2)) { + RNP_LOG("mdc header check failed"); + return false; + } +@@ -777,13 +778,18 @@ get_hash_for_sig(pgp_source_signed_param + } + + static void + signed_validate_signature(pgp_source_signed_param_t ¶m, pgp_signature_info_t &sinfo) + { ++ /* Check signature type */ ++ if (!sinfo.sig->is_document()) { ++ RNP_LOG("Invalid document signature type: %d", (int) sinfo.sig->type()); ++ sinfo.valid = false; ++ return; ++ } + /* Find signing key */ +- pgp_key_request_ctx_t keyctx = { +- .op = PGP_OP_VERIFY, .secret = false, .search = {.type = PGP_KEY_SEARCH_FINGERPRINT}}; ++ pgp_key_request_ctx_t keyctx(PGP_OP_VERIFY, false, PGP_KEY_SEARCH_FINGERPRINT); + + /* Get signer's fp or keyid */ + if (sinfo.sig->has_keyfp()) { + keyctx.search.by.fingerprint = sinfo.sig->keyfp(); + } else if (sinfo.sig->has_keyid()) { +@@ -803,20 +809,19 @@ signed_validate_signature(pgp_source_sig + RNP_LOG("signer's key not found"); + sinfo.no_signer = true; + return; + } + } +- /* Get the hash context and clone it. */ +- const rnp::Hash *hash = get_hash_for_sig(param, sinfo); +- if (!hash) { +- RNP_LOG("failed to get hash context."); +- return; +- } + try { +- rnp::Hash shash; +- hash->clone(shash); +- key->validate_sig(sinfo, shash, *param.handler->ctx->ctx); ++ /* Get the hash context and clone it. */ ++ auto hash = get_hash_for_sig(param, sinfo); ++ if (!hash) { ++ RNP_LOG("failed to get hash context."); ++ return; ++ } ++ auto shash = hash->clone(); ++ key->validate_sig(sinfo, *shash, *param.handler->ctx->ctx); + } catch (const std::exception &e) { + RNP_LOG("Signature validation failed: %s", e.what()); + sinfo.valid = false; + } + } +@@ -850,11 +855,11 @@ signed_src_update(pgp_source_t *src, con + param->hashes.add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + } + /* update text-mode sig hashes */ +- if (param->txt_hashes.empty()) { ++ if (param->txt_hashes.hashes.empty()) { + return; + } + + uint8_t *ch = (uint8_t *) buf; + uint8_t *linebeg = ch; +@@ -991,63 +996,62 @@ signed_read_single_signature(pgp_source_ + return RNP_ERROR_OUT_OF_MEMORY; + } + } + + static rnp_result_t +-signed_read_cleartext_signatures(pgp_source_t *src) ++signed_read_cleartext_signatures(pgp_source_t &src, pgp_source_signed_param_t *param) + { +- pgp_source_t armor = {0}; +- rnp_result_t ret = RNP_ERROR_BAD_FORMAT; +- pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; +- +- if ((ret = init_armored_src(&armor, param->readsrc)) != RNP_SUCCESS) { +- return ret; ++ try { ++ rnp::ArmoredSource armor(*param->readsrc); ++ while (!armor.eof()) { ++ auto ret = signed_read_single_signature(param, &armor.src(), NULL); ++ if (ret) { ++ return ret; ++ } ++ } ++ return RNP_SUCCESS; ++ } catch (const rnp::rnp_exception &e) { ++ RNP_LOG("%s", e.what()); ++ return e.code(); ++ } catch (const std::exception &e) { ++ RNP_LOG("%s", e.what()); ++ return RNP_ERROR_BAD_FORMAT; + } +- +- while (!src_eof(&armor)) { +- if ((ret = signed_read_single_signature(param, &armor, NULL)) != RNP_SUCCESS) { +- goto finish; +- } +- } +- +- ret = RNP_SUCCESS; +- +-finish: +- src_close(&armor); +- return ret; + } + + static rnp_result_t + signed_read_signatures(pgp_source_t *src) + { +- pgp_signature_t * sig = NULL; +- rnp_result_t ret; + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + + /* reading signatures */ + for (auto op = param->onepasses.rbegin(); op != param->onepasses.rend(); op++) { +- if ((ret = signed_read_single_signature(param, src, &sig)) != RNP_SUCCESS) { ++ pgp_signature_t *sig = NULL; ++ rnp_result_t ret = signed_read_single_signature(param, src, &sig); ++ /* we have more onepasses then signatures */ ++ if (ret == RNP_ERROR_READ) { ++ RNP_LOG("Warning: premature end of signatures"); ++ return param->siginfos.size() ? RNP_SUCCESS : ret; ++ } ++ if (ret) { + return ret; + } +- +- if (!sig || !sig->matches_onepass(*op)) { +- RNP_LOG("signature doesn't match one-pass"); +- return RNP_ERROR_BAD_FORMAT; ++ if (sig && !sig->matches_onepass(*op)) { ++ RNP_LOG("Warning: signature doesn't match one-pass"); + } + } +- + return RNP_SUCCESS; + } + + static rnp_result_t + signed_src_finish(pgp_source_t *src) + { + pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param; + rnp_result_t ret = RNP_ERROR_GENERIC; + + if (param->cleartext) { +- ret = signed_read_cleartext_signatures(src); ++ ret = signed_read_cleartext_signatures(*src, param); + } else { + ret = signed_read_signatures(src); + } + + if (ret) { +@@ -1065,20 +1069,16 @@ signed_src_finish(pgp_source_t *src) + } + signed_validate_signature(*param, sinfo); + } + + /* checking the validation results */ +- ret = RNP_SUCCESS; ++ ret = RNP_ERROR_SIGNATURE_INVALID; + for (auto &sinfo : param->siginfos) { +- 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; ++ if (sinfo.valid) { ++ /* If we have at least one valid signature then data is safe to process */ ++ ret = RNP_SUCCESS; ++ break; + } + } + + /* call the callback with signature infos */ + if (param->handler->on_signatures) { +@@ -1363,12 +1363,12 @@ encrypted_decrypt_cfb_header(pgp_source_ + pgp_cipher_cfb_resync(¶m->decrypt, enchdr + 2); + return true; + } + + try { +- param->mdc = rnp::Hash(PGP_HASH_SHA1); +- param->mdc.add(dechdr, blsize + 2); ++ param->mdc = rnp::Hash::create(PGP_HASH_SHA1); ++ param->mdc->add(dechdr, blsize + 2); + } catch (const std::exception &e) { + RNP_LOG("cannot create sha1 hash: %s", e.what()); + goto error; + } + return true; +@@ -1474,11 +1474,12 @@ encrypted_try_key(pgp_source_encrypted_p + pgp_fingerprint_t fingerprint; + if (pgp_fingerprint(fingerprint, *seckey)) { + RNP_LOG("ECDH fingerprint calculation failed"); + return false; + } +- if (!x25519_bits_tweaked(keymaterial->ec)) { ++ if ((keymaterial->ec.curve == PGP_CURVE_25519) && ++ !x25519_bits_tweaked(keymaterial->ec)) { + RNP_LOG("Warning: bits of 25519 secret key are not tweaked."); + } + declen = decbuf.size(); + err = ecdh_decrypt_pkcs5( + decbuf.data(), &declen, &encmaterial.ecdh, &keymaterial->ec, fingerprint); +@@ -1933,20 +1934,22 @@ encrypted_read_packet_data(pgp_source_en + switch (ptype) { + case PGP_PKT_SK_SESSION_KEY: { + pgp_sk_sesskey_t skey; + rnp_result_t ret = skey.parse(*param->pkt.readsrc); + if (ret) { +- return ret; ++ RNP_LOG("Failed to parse SKESK, skipping."); ++ continue; + } + param->symencs.push_back(skey); + break; + } + case PGP_PKT_PK_SESSION_KEY: { + pgp_pk_sesskey_t pkey; + rnp_result_t ret = pkey.parse(*param->pkt.readsrc); + if (ret) { +- return ret; ++ RNP_LOG("Failed to parse PKESK, skipping."); ++ continue; + } + param->pubencs.push_back(pkey); + break; + } + case PGP_PKT_SE_DATA: +@@ -1997,10 +2000,13 @@ encrypted_read_packet_data(pgp_source_en + } + if (param->aead_hdr.csize > 56) { + RNP_LOG("too large chunk size: %d", param->aead_hdr.csize); + return RNP_ERROR_BAD_FORMAT; + } ++ if (param->aead_hdr.csize > 16) { ++ RNP_LOG("Warning: AEAD chunk bits > 16."); ++ } + param->chunklen = 1L << (param->aead_hdr.csize + 6); + + /* build additional data */ + param->aead_adlen = 13; + param->aead_ad[0] = param->pkt.hdr[0]; +@@ -2078,25 +2084,22 @@ init_encrypted_src(pgp_parse_handler_t * + 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; ++ pgp_key_request_ctx_t keyctx(PGP_OP_DECRYPT_SYM, true, PGP_KEY_SEARCH_KEYID); + + for (auto &pubenc : param->pubencs) { + keyctx.search.by.keyid = pubenc.key_id; + /* Get the key if any */ + if (!(seckey = pgp_request_key(handler->key_provider, &keyctx))) { + errcode = RNP_ERROR_NO_SUITABLE_KEY; + continue; + } + /* Decrypt key */ + if (seckey->encrypted()) { +- pgp_password_ctx_t pass_ctx{.op = PGP_OP_DECRYPT, .key = seckey}; ++ pgp_password_ctx_t pass_ctx(PGP_OP_DECRYPT, seckey); + decrypted_seckey = + pgp_decrypt_seckey(*seckey, *handler->password_provider, pass_ctx); + if (!decrypted_seckey) { + errcode = RNP_ERROR_BAD_PASSWORD; + continue; +@@ -2127,11 +2130,11 @@ init_encrypted_src(pgp_parse_handler_t * + } + + /* Trying password-based decryption */ + if (!have_key && !param->symencs.empty()) { + rnp::secure_array password; +- pgp_password_ctx_t pass_ctx{.op = PGP_OP_DECRYPT_SYM, .key = NULL}; ++ pgp_password_ctx_t pass_ctx(PGP_OP_DECRYPT_SYM); + if (!pgp_request_password( + handler->password_provider, &pass_ctx, password.data(), password.size())) { + errcode = RNP_ERROR_BAD_PASSWORD; + goto finish; + } +diff --git a/comm/third_party/rnp/src/librepgp/stream-sig.cpp b/third_party/rnp/src/lib/commrepgp/stream-sig.cpp +--- a/comm/third_party/rnp/src/librepgp/stream-sig.cpp ++++ b/comm/third_party/rnp/src/librepgp/stream-sig.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2018-2020, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2018-2022, [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: + * +@@ -85,97 +85,74 @@ signature_hash_userid(const pgp_userid_p + STORE32BE(hdr + 1, uid.uid_len); + hash.add(hdr, 5); + hash.add(uid.uid, uid.uid_len); + } + +-void ++std::unique_ptr + signature_hash_certification(const pgp_signature_t & sig, + const pgp_key_pkt_t & key, +- const pgp_userid_pkt_t &userid, +- rnp::Hash & hash) ++ const pgp_userid_pkt_t &userid) + { +- signature_init(key.material, sig.halg, hash); +- signature_hash_key(key, hash); +- signature_hash_userid(userid, hash, sig.version); ++ auto hash = signature_init(key.material, sig.halg); ++ signature_hash_key(key, *hash); ++ signature_hash_userid(userid, *hash, sig.version); ++ return hash; + } + +-void ++std::unique_ptr + signature_hash_binding(const pgp_signature_t &sig, + const pgp_key_pkt_t & key, +- const pgp_key_pkt_t & subkey, +- rnp::Hash & hash) ++ const pgp_key_pkt_t & subkey) + { +- signature_init(key.material, sig.halg, hash); +- signature_hash_key(key, hash); +- signature_hash_key(subkey, hash); ++ auto hash = signature_init(key.material, sig.halg); ++ signature_hash_key(key, *hash); ++ signature_hash_key(subkey, *hash); ++ return hash; + } + +-void +-signature_hash_direct(const pgp_signature_t &sig, const pgp_key_pkt_t &key, rnp::Hash &hash) ++std::unique_ptr ++signature_hash_direct(const pgp_signature_t &sig, const pgp_key_pkt_t &key) + { +- signature_init(key.material, sig.halg, hash); +- signature_hash_key(key, hash); ++ auto hash = signature_init(key.material, sig.halg); ++ signature_hash_key(key, *hash); ++ return hash; + } + + rnp_result_t +-process_pgp_signatures(pgp_source_t *src, pgp_signature_list_t &sigs) ++process_pgp_signatures(pgp_source_t &src, pgp_signature_list_t &sigs) + { +- bool armored = false; +- pgp_source_t armorsrc = {0}; +- pgp_source_t *origsrc = src; +- rnp_result_t ret = RNP_ERROR_GENERIC; +- + sigs.clear(); +- /* check whether signatures are armored */ +-armoredpass: +- if (is_armored_source(src)) { +- if ((ret = init_armored_src(&armorsrc, src))) { +- RNP_LOG("failed to parse armored data"); +- goto finish; ++ /* Allow binary or armored input, including multiple armored messages */ ++ rnp::ArmoredSource armor( ++ src, rnp::ArmoredSource::AllowBinary | rnp::ArmoredSource::AllowMultiple); ++ /* read sequence of OpenPGP signatures */ ++ while (!armor.error()) { ++ if (armor.eof() && armor.multiple()) { ++ armor.restart(); + } +- armored = true; +- src = &armorsrc; +- } +- +- /* read sequence of OpenPGP signatures */ +- while (!src_eof(src) && !src_error(src)) { +- int ptag = stream_pkt_type(src); +- ++ if (armor.eof()) { ++ break; ++ } ++ int ptag = stream_pkt_type(&armor.src()); + if (ptag != PGP_PKT_SIGNATURE) { + RNP_LOG("wrong signature tag: %d", ptag); +- ret = RNP_ERROR_BAD_FORMAT; +- goto finish; ++ sigs.clear(); ++ return RNP_ERROR_BAD_FORMAT; + } + +- try { +- sigs.emplace_back(); +- if ((ret = sigs.back().parse(*src))) { +- goto finish; +- } +- } catch (const std::exception &e) { +- RNP_LOG("%s", e.what()); +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto finish; ++ sigs.emplace_back(); ++ rnp_result_t ret = sigs.back().parse(armor.src()); ++ if (ret) { ++ sigs.clear(); ++ return ret; + } + } +- +- /* file may have multiple armored keys */ +- if (armored && !src_eof(origsrc) && is_armored_source(origsrc)) { +- src_close(&armorsrc); +- armored = false; +- src = origsrc; +- goto armoredpass; ++ if (armor.error()) { ++ sigs.clear(); ++ return RNP_ERROR_READ; + } +- ret = RNP_SUCCESS; +-finish: +- if (armored) { +- src_close(&armorsrc); +- } +- if (ret) { +- sigs.clear(); +- } +- return ret; ++ return RNP_SUCCESS; + } + + pgp_sig_subpkt_t::pgp_sig_subpkt_t(const pgp_sig_subpkt_t &src) + { + type = src.type; +@@ -583,17 +560,17 @@ pgp_signature_t::~pgp_signature_t() + } + + pgp_sig_id_t + pgp_signature_t::get_id() const + { +- rnp::Hash hash(PGP_HASH_SHA1); +- hash.add(hashed_data, hashed_len); +- hash.add(material_buf, material_len); +- pgp_sig_id_t res; ++ auto hash = rnp::Hash::create(PGP_HASH_SHA1); ++ hash->add(hashed_data, hashed_len); ++ hash->add(material_buf, material_len); ++ pgp_sig_id_t res = {0}; + static_assert(std::tuple_size::value == PGP_SHA1_HASH_SIZE, + "pgp_sig_id_t size mismatch"); +- hash.finish(res.data()); ++ hash->finish(res.data()); + return res; + } + + pgp_sig_subpkt_t * + pgp_signature_t::get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) +@@ -1098,19 +1075,14 @@ pgp_signature_t::add_notation(const std: + } + + void + pgp_signature_t::set_embedded_sig(const pgp_signature_t &esig) + { +- pgp_rawpacket_t esigpkt(esig); +- pgp_source_t memsrc = {}; +- if (init_mem_src(&memsrc, esigpkt.raw.data(), esigpkt.raw.size(), false)) { +- RNP_LOG("failed to init mem src"); +- throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); +- } +- size_t len = 0; +- stream_read_pkt_len(&memsrc, &len); +- src_close(&memsrc); ++ pgp_rawpacket_t esigpkt(esig); ++ rnp::MemorySource mem(esigpkt.raw); ++ size_t len = 0; ++ stream_read_pkt_len(&mem.src(), &len); + if (!len || (len > 0xffff) || (len >= esigpkt.raw.size())) { + RNP_LOG("wrong pkt len"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } + pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, len, true); +@@ -1574,12 +1546,12 @@ rnp_selfsig_cert_info_t::populate(pgp_us + if (!prefs.key_server.empty()) { + sig.set_key_server(prefs.key_server); + } + /* populate uid */ + uid.tag = PGP_PKT_USER_ID; +- uid.uid_len = strlen((char *) userid); ++ uid.uid_len = userid.size(); + if (!(uid.uid = (uint8_t *) malloc(uid.uid_len))) { + RNP_LOG("alloc failed"); + throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY); + } +- memcpy(uid.uid, (char *) userid, uid.uid_len); ++ memcpy(uid.uid, userid.data(), uid.uid_len); + } +diff --git a/comm/third_party/rnp/src/librepgp/stream-sig.h b/third_party/rnp/src/lib/commrepgp/stream-sig.h +--- a/comm/third_party/rnp/src/librepgp/stream-sig.h ++++ b/comm/third_party/rnp/src/librepgp/stream-sig.h +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2018-2022, [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: + * +@@ -83,10 +83,16 @@ typedef struct pgp_signature_t { + set_type(pgp_sig_type_t atype) + { + type_ = atype; + }; + ++ bool ++ is_document() const ++ { ++ return (type_ == PGP_SIG_BINARY) || (type_ == PGP_SIG_TEXT); ++ }; ++ + /** @brief Calculate the unique signature identifier by hashing signature's fields. */ + pgp_sig_id_t get_id() const; + + /** + * @brief Get v4 signature's subpacket of the specified type and hashedness. +@@ -404,31 +410,28 @@ typedef struct pgp_signature_info_t { + */ + void signature_hash_key(const pgp_key_pkt_t &key, rnp::Hash &hash); + + void signature_hash_userid(const pgp_userid_pkt_t &uid, rnp::Hash &hash, pgp_version_t sigver); + +-void signature_hash_certification(const pgp_signature_t & sig, +- const pgp_key_pkt_t & key, +- const pgp_userid_pkt_t &userid, +- rnp::Hash & hash); ++std::unique_ptr signature_hash_certification(const pgp_signature_t & sig, ++ const pgp_key_pkt_t & key, ++ const pgp_userid_pkt_t &userid); + +-void signature_hash_binding(const pgp_signature_t &sig, +- const pgp_key_pkt_t & key, +- const pgp_key_pkt_t & subkey, +- rnp::Hash & hash); ++std::unique_ptr signature_hash_binding(const pgp_signature_t &sig, ++ const pgp_key_pkt_t & key, ++ const pgp_key_pkt_t & subkey); + +-void signature_hash_direct(const pgp_signature_t &sig, +- const pgp_key_pkt_t & key, +- rnp::Hash & hash); ++std::unique_ptr signature_hash_direct(const pgp_signature_t &sig, ++ const pgp_key_pkt_t & key); + + /** + * @brief Parse stream with signatures to the signatures list. + * Can handle binary or armored stream with signatures, including stream with multiple + * armored signatures. + * + * @param src signatures stream, cannot be NULL. + * @param sigs on success parsed signature structures will be put here. + * @return RNP_SUCCESS or error code otherwise. + */ +-rnp_result_t process_pgp_signatures(pgp_source_t *src, pgp_signature_list_t &sigs); ++rnp_result_t process_pgp_signatures(pgp_source_t &src, pgp_signature_list_t &sigs); + + #endif +diff --git a/comm/third_party/rnp/src/librepgp/stream-write.cpp b/third_party/rnp/src/lib/commrepgp/stream-write.cpp +--- a/comm/third_party/rnp/src/librepgp/stream-write.cpp ++++ b/comm/third_party/rnp/src/librepgp/stream-write.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2017-2022, [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: + * +@@ -82,25 +82,25 @@ typedef struct pgp_dest_compressed_param + uint8_t cache[PGP_INPUT_CACHE_SIZE / 2]; /* pre-allocated cache for compression */ + size_t len; /* number of bytes cached */ + } pgp_dest_compressed_param_t; + + typedef struct pgp_dest_encrypted_param_t { +- pgp_dest_packet_param_t pkt; /* underlying packet-related params */ +- rnp_ctx_t * ctx; /* rnp operation context with additional parameters */ +- bool has_mdc; /* encrypted with mdc, i.e. tag 18 */ +- bool aead; /* we use AEAD encryption */ +- pgp_crypt_t encrypt; /* encrypting crypto */ +- rnp::Hash mdc; /* mdc SHA1 hash */ +- pgp_aead_alg_t aalg; /* AEAD algorithm used */ +- uint8_t iv[PGP_AEAD_MAX_NONCE_LEN]; /* iv for AEAD mode */ +- uint8_t ad[PGP_AEAD_MAX_AD_LEN]; /* additional data for AEAD mode */ +- size_t adlen; /* length of additional data, including chunk idx */ +- size_t chunklen; /* length of the AEAD chunk in bytes */ +- size_t chunkout; /* how many bytes from the chunk were written out */ +- size_t chunkidx; /* index of the current AEAD chunk */ +- size_t cachelen; /* how many bytes are in cache, for AEAD */ +- uint8_t cache[PGP_AEAD_CACHE_LEN]; /* pre-allocated cache for encryption */ ++ pgp_dest_packet_param_t pkt; /* underlying packet-related params */ ++ rnp_ctx_t * ctx; /* rnp operation context with additional parameters */ ++ bool has_mdc; /* encrypted with mdc, i.e. tag 18 */ ++ bool aead; /* we use AEAD encryption */ ++ pgp_crypt_t encrypt; /* encrypting crypto */ ++ std::unique_ptr mdc; /* mdc SHA1 hash */ ++ pgp_aead_alg_t aalg; /* AEAD algorithm used */ ++ uint8_t iv[PGP_AEAD_MAX_NONCE_LEN]; /* iv for AEAD mode */ ++ uint8_t ad[PGP_AEAD_MAX_AD_LEN]; /* additional data for AEAD mode */ ++ size_t adlen; /* length of additional data, including chunk idx */ ++ size_t chunklen; /* length of the AEAD chunk in bytes */ ++ size_t chunkout; /* how many bytes from the chunk were written out */ ++ size_t chunkidx; /* index of the current AEAD chunk */ ++ size_t cachelen; /* how many bytes are in cache, for AEAD */ ++ uint8_t cache[PGP_AEAD_CACHE_LEN]; /* pre-allocated cache for encryption */ + } pgp_dest_encrypted_param_t; + + typedef struct pgp_dest_signer_info_t { + pgp_one_pass_sig_t onepass; + pgp_key_t * key; +@@ -296,11 +296,11 @@ encrypted_dst_write_cfb(pgp_dest_t *dst, + return RNP_ERROR_BAD_PARAMETERS; + } + + if (param->has_mdc) { + try { +- param->mdc.add(buf, len); ++ param->mdc->add(buf, len); + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + } +@@ -478,12 +478,13 @@ encrypted_dst_finish(pgp_dest_t *dst) + } else if (param->has_mdc) { + uint8_t mdcbuf[MDC_V1_SIZE]; + mdcbuf[0] = MDC_PKT_TAG; + mdcbuf[1] = MDC_V1_SIZE - 2; + try { +- param->mdc.add(mdcbuf, 2); +- param->mdc.finish(&mdcbuf[2]); ++ param->mdc->add(mdcbuf, 2); ++ param->mdc->finish(&mdcbuf[2]); ++ param->mdc = nullptr; + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } + pgp_cipher_cfb_encrypt(¶m->encrypt, mdcbuf, mdcbuf, MDC_V1_SIZE); +@@ -756,11 +757,11 @@ encrypted_start_cfb(pgp_dest_encrypted_p + if (param->has_mdc) { + /* initializing the mdc */ + dst_write(param->pkt.writedst, &mdcver, 1); + + try { +- param->mdc = rnp::Hash(PGP_HASH_SHA1); ++ param->mdc = rnp::Hash::create(PGP_HASH_SHA1); + } catch (const std::exception &e) { + RNP_LOG("cannot create sha1 hash: %s", e.what()); + return RNP_ERROR_GENERIC; + } + } +@@ -776,11 +777,11 @@ encrypted_start_cfb(pgp_dest_encrypted_p + param->ctx->ctx->rng.get(enchdr, blsize); + enchdr[blsize] = enchdr[blsize - 2]; + enchdr[blsize + 1] = enchdr[blsize - 1]; + + if (param->has_mdc) { +- param->mdc.add(enchdr, blsize + 2); ++ param->mdc->add(enchdr, blsize + 2); + } + } catch (const std::exception &e) { + RNP_LOG("%s", e.what()); + return RNP_ERROR_BAD_STATE; + } +@@ -866,21 +867,21 @@ init_encrypted_dst(pgp_write_handler_t * + return RNP_ERROR_BAD_PARAMETERS; + } + + if (handler->ctx->aalg) { + if ((handler->ctx->aalg != PGP_AEAD_EAX) && (handler->ctx->aalg != PGP_AEAD_OCB)) { +- RNP_LOG("unknown AEAD algorithm"); ++ RNP_LOG("unknown AEAD algorithm: %d", (int) handler->ctx->aalg); + return RNP_ERROR_BAD_PARAMETERS; + } + + if ((pgp_block_size(handler->ctx->ealg) != 16)) { + RNP_LOG("wrong AEAD symmetric algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + +- if ((handler->ctx->abits < 0) || (handler->ctx->abits > 56)) { +- RNP_LOG("wrong AEAD chunk bits"); ++ if ((handler->ctx->abits < 0) || (handler->ctx->abits > 16)) { ++ RNP_LOG("wrong AEAD chunk bits: %d", handler->ctx->abits); + return RNP_ERROR_BAD_PARAMETERS; + } + } + + if (!init_dst_common(dst, 0)) { +@@ -1116,42 +1117,40 @@ signed_fill_signature(pgp_dest_signed_pa + sig.set_creation(signer.sigcreate); + } + sig.set_expiration(signer.sigexpire); + sig.fill_hashed_data(); + +- const rnp::Hash *listh = param.hashes.get(sig.halg); ++ auto listh = param.hashes.get(sig.halg); + if (!listh) { + RNP_LOG("failed to obtain hash"); + throw rnp::rnp_exception(RNP_ERROR_BAD_STATE); + } +- rnp::Hash hash; +- listh->clone(hash); + + /* decrypt the secret key if needed */ + rnp::KeyLocker(*signer.key); + if (signer.key->encrypted() && + !signer.key->unlock(*param.password_provider, PGP_OP_SIGN)) { + RNP_LOG("wrong secret key password"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PASSWORD); + } + /* calculate the signature */ +- signature_calculate(sig, signer.key->material(), hash, *param.ctx->ctx); ++ signature_calculate(sig, signer.key->material(), *listh->clone(), *param.ctx->ctx); + } + + static rnp_result_t + signed_write_signature(pgp_dest_signed_param_t *param, + pgp_dest_signer_info_t * signer, + pgp_dest_t * writedst) + { + try { + pgp_signature_t sig; + if (signer->onepass.version) { +- signer->key->sign_init(sig, signer->onepass.halg); ++ signer->key->sign_init(sig, signer->onepass.halg, param->ctx->ctx->time()); + sig.palg = signer->onepass.palg; + sig.set_type(signer->onepass.type); + } else { +- signer->key->sign_init(sig, signer->halg); ++ signer->key->sign_init(sig, signer->halg, param->ctx->ctx->time()); + /* line below should be checked */ + sig.set_type(param->ctx->detached ? PGP_SIG_BINARY : PGP_SIG_TEXT); + } + signed_fill_signature(*param, sig, *signer); + sig.write(*writedst); +@@ -1199,38 +1198,35 @@ signed_detached_dst_finish(pgp_dest_t *d + } + + static rnp_result_t + cleartext_dst_finish(pgp_dest_t *dst) + { +- pgp_dest_t armordst = {0}; +- rnp_result_t ret; + pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param; + + /* writing cached line if any */ + if (param->clr_buflen > 0) { + cleartext_dst_writeline(param, param->clr_buf, param->clr_buflen, true); + } + /* trailing \r\n which is not hashed */ + dst_write(param->writedst, ST_CRLF, 2); + + /* writing signatures to the armored stream, which outputs to param->writedst */ +- if ((ret = init_armored_dst(&armordst, param->writedst, PGP_ARMORED_SIGNATURE))) { +- return ret; +- } +- +- for (auto &sinfo : param->siginfos) { +- if ((ret = signed_write_signature(param, &sinfo, &armordst))) { +- break; ++ try { ++ rnp::ArmoredDest armor(*param->writedst, PGP_ARMORED_SIGNATURE); ++ armor.set_discard(true); ++ for (auto &sinfo : param->siginfos) { ++ auto ret = signed_write_signature(param, &sinfo, &armor.dst()); ++ if (ret) { ++ return ret; ++ } + } ++ armor.set_discard(false); ++ return RNP_SUCCESS; ++ } catch (const std::exception &e) { ++ RNP_LOG("Failed to write armored signature: %s", e.what()); ++ return RNP_ERROR_WRITE; + } +- +- if (ret == RNP_SUCCESS) { +- ret = dst_finish(&armordst); +- } +- +- dst_close(&armordst, ret != RNP_SUCCESS); +- return ret; + } + + static void + signed_dst_close(pgp_dest_t *dst, bool discard) + { +@@ -1366,25 +1362,25 @@ init_signed_dst(pgp_write_handler_t *han + goto finish; + } + } + + /* Do we have any signatures? */ +- if (param->hashes.empty()) { ++ if (param->hashes.hashes.empty()) { + ret = RNP_ERROR_BAD_PARAMETERS; + goto finish; + } + + /* Writing headers for cleartext signed document */ + if (param->ctx->clearsign) { + dst_write(param->writedst, ST_CLEAR_BEGIN, strlen(ST_CLEAR_BEGIN)); + dst_write(param->writedst, ST_CRLF, strlen(ST_CRLF)); + dst_write(param->writedst, ST_HEADER_HASH, strlen(ST_HEADER_HASH)); + +- for (const auto &hash : param->hashes.hashes()) { +- auto hname = rnp::Hash::name(hash.alg()); ++ for (const auto &hash : param->hashes.hashes) { ++ auto hname = rnp::Hash::name(hash->alg()); + dst_write(param->writedst, hname, strlen(hname)); +- if (&hash != ¶m->hashes.hashes().back()) { ++ if (&hash != ¶m->hashes.hashes.back()) { + dst_write(param->writedst, ST_COMMA, 1); + } + } + + dst_write(param->writedst, ST_CRLFCRLF, strlen(ST_CRLFCRLF)); +@@ -1721,125 +1717,57 @@ finish: + + return ret; + } + + static rnp_result_t +-process_stream_sequence(pgp_source_t *src, pgp_dest_t *streams, unsigned count) ++process_stream_sequence(pgp_source_t *src, ++ pgp_dest_t * streams, ++ unsigned count, ++ pgp_dest_t * sstream, ++ pgp_dest_t * wstream) + { +- uint8_t * readbuf = NULL; +- pgp_dest_t * sstream = NULL; /* signed stream if any, to call signed_dst_update on it */ +- pgp_dest_t * wstream = NULL; /* stream to dst_write() source data, may be empty */ +- rnp_result_t ret = RNP_ERROR_GENERIC; +- +- if (!(readbuf = (uint8_t *) calloc(1, PGP_INPUT_CACHE_SIZE))) { ++ std::unique_ptr readbuf(new (std::nothrow) uint8_t[PGP_INPUT_CACHE_SIZE]); ++ if (!readbuf) { + RNP_LOG("allocation failure"); +- ret = RNP_ERROR_OUT_OF_MEMORY; +- goto finish; +- } +- +- /* check whether we have signed stream and stream for data output */ +- for (int i = count - 1; i >= 0; i--) { +- if (streams[i].type == PGP_STREAM_SIGNED) { +- sstream = &streams[i]; +- } else if ((streams[i].type == PGP_STREAM_CLEARTEXT) || +- (streams[i].type == PGP_STREAM_LITERAL)) { +- wstream = &streams[i]; +- } ++ return RNP_ERROR_OUT_OF_MEMORY; + } + + /* processing source stream */ + while (!src->eof) { + size_t read = 0; +- if (!src_read(src, readbuf, PGP_INPUT_CACHE_SIZE, &read)) { ++ if (!src_read(src, readbuf.get(), PGP_INPUT_CACHE_SIZE, &read)) { + RNP_LOG("failed to read from source"); +- ret = RNP_ERROR_READ; +- goto finish; ++ return RNP_ERROR_READ; + } else if (!read) { + continue; + } + + if (sstream) { +- signed_dst_update(sstream, readbuf, read); ++ signed_dst_update(sstream, readbuf.get(), read); + } + + if (wstream) { +- dst_write(wstream, readbuf, read); ++ dst_write(wstream, readbuf.get(), read); + + for (int i = count - 1; i >= 0; i--) { +- if (streams[i].werr != RNP_SUCCESS) { ++ if (streams[i].werr) { + RNP_LOG("failed to process data"); +- ret = RNP_ERROR_WRITE; +- goto finish; ++ return RNP_ERROR_WRITE; + } + } + } + } + + /* finalizing destinations */ + for (int i = count - 1; i >= 0; i--) { +- ret = dst_finish(&streams[i]); +- if (ret != RNP_SUCCESS) { ++ rnp_result_t ret = dst_finish(&streams[i]); ++ if (ret) { + RNP_LOG("failed to finish stream"); +- goto finish; ++ return ret; + } + } +- +- ret = RNP_SUCCESS; +-finish: +- free(readbuf); +- return ret; +-} +- +-rnp_result_t +-rnp_encrypt_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst) +-{ +- /* stack of the streams would be as following: +- [armoring stream] - if armoring is enabled +- encrypting stream, partial writing stream +- [compressing stream, partial writing stream] - if compression is enabled +- literal data stream, partial writing stream +- */ +- pgp_dest_t dests[4]; +- int destc = 0; +- rnp_result_t ret = RNP_ERROR_GENERIC; +- +- /* pushing armoring stream, which will write to the output */ +- if (handler->ctx->armor) { +- if ((ret = init_armored_dst(&dests[destc], dst, PGP_ARMORED_MESSAGE))) { +- goto finish; +- } +- destc++; +- } +- +- /* pushing encrypting stream, which will write to the output or armoring stream */ +- if ((ret = init_encrypted_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { +- goto finish; +- } +- destc++; +- +- /* if compression is enabled then pushing compressing stream */ +- if (handler->ctx->zlevel > 0) { +- if ((ret = init_compressed_dst(handler, &dests[destc], &dests[destc - 1]))) { +- goto finish; +- } +- destc++; +- } +- +- /* pushing literal data stream */ +- if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) { +- goto finish; +- } +- destc++; +- +- /* processing stream sequence */ +- ret = process_stream_sequence(src, dests, destc); +-finish: +- for (int i = destc - 1; i >= 0; i--) { +- dst_close(&dests[i], ret != RNP_SUCCESS); +- } +- +- return ret; ++ return RNP_SUCCESS; + } + + rnp_result_t + rnp_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst) + { +@@ -1850,24 +1778,26 @@ rnp_sign_src(pgp_write_handler_t *handle + literal data stream, partial writing stream - if not detached or cleartext signature + */ + pgp_dest_t dests[4]; + unsigned destc = 0; + rnp_result_t ret = RNP_ERROR_GENERIC; ++ rnp_ctx_t & ctx = *handler->ctx; ++ pgp_dest_t * wstream = NULL; ++ pgp_dest_t * sstream = NULL; + + /* pushing armoring stream, which will write to the output */ +- if (handler->ctx->armor && !handler->ctx->clearsign) { +- pgp_armored_msg_t msgt = +- handler->ctx->detached ? PGP_ARMORED_SIGNATURE : PGP_ARMORED_MESSAGE; ++ if (ctx.armor && !ctx.clearsign) { ++ pgp_armored_msg_t msgt = ctx.detached ? PGP_ARMORED_SIGNATURE : PGP_ARMORED_MESSAGE; + ret = init_armored_dst(&dests[destc], dst, msgt); +- if (ret != RNP_SUCCESS) { ++ if (ret) { + goto finish; + } + destc++; + } + + /* if compression is enabled then pushing compressing stream */ +- if (!handler->ctx->detached && !handler->ctx->clearsign && (handler->ctx->zlevel > 0)) { ++ if (!ctx.detached && !ctx.clearsign && (ctx.zlevel > 0)) { + if ((ret = + init_compressed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } + destc++; +@@ -1876,25 +1806,32 @@ rnp_sign_src(pgp_write_handler_t *handle + /* pushing signing stream, which will use handler->ctx to distinguish between + * attached/detached/cleartext signature */ + if ((ret = init_signed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) { + goto finish; + } ++ if (!ctx.clearsign) { ++ sstream = &dests[destc]; ++ } ++ if (!ctx.detached) { ++ wstream = &dests[destc]; ++ } + destc++; + + /* pushing literal data stream, if not detached/cleartext signature */ +- if (!handler->ctx->detached && !handler->ctx->clearsign) { ++ if (!ctx.no_wrap && !ctx.detached && !ctx.clearsign) { + if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } ++ wstream = &dests[destc]; + destc++; + } + + /* process source with streams stack */ +- ret = process_stream_sequence(src, dests, destc); ++ ret = process_stream_sequence(src, dests, destc, sstream, wstream); + finish: + for (int i = destc - 1; i >= 0; i--) { +- dst_close(&dests[i], ret != RNP_SUCCESS); ++ dst_close(&dests[i], ret); + } + return ret; + } + + rnp_result_t +@@ -1906,23 +1843,24 @@ rnp_encrypt_sign_src(pgp_write_handler_t + [compressing stream, partial writing stream] - compression is enabled + signing stream + literal data stream, partial writing stream + */ + pgp_dest_t dests[5]; +- unsigned destc = 0; ++ size_t destc = 0; + rnp_result_t ret = RNP_SUCCESS; ++ rnp_ctx_t & ctx = *handler->ctx; ++ pgp_dest_t * sstream = NULL; + + /* we may use only attached signatures here */ +- if (handler->ctx->clearsign || handler->ctx->detached) { ++ if (ctx.clearsign || ctx.detached) { + RNP_LOG("cannot clearsign or sign detached together with encryption"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* pushing armoring stream, which will write to the output */ +- if (handler->ctx->armor) { +- ret = init_armored_dst(&dests[destc], dst, PGP_ARMORED_MESSAGE); +- if (ret != RNP_SUCCESS) { ++ if (ctx.armor) { ++ if ((ret = init_armored_dst(&dests[destc], dst, PGP_ARMORED_MESSAGE))) { + goto finish; + } + destc++; + } + +@@ -1931,34 +1869,39 @@ rnp_encrypt_sign_src(pgp_write_handler_t + goto finish; + } + destc++; + + /* if compression is enabled then pushing compressing stream */ +- if (handler->ctx->zlevel > 0) { ++ if (ctx.zlevel > 0) { + if ((ret = init_compressed_dst(handler, &dests[destc], &dests[destc - 1]))) { + goto finish; + } + destc++; + } + +- /* pushing signing stream */ +- if ((ret = init_signed_dst(handler, &dests[destc], &dests[destc - 1]))) { +- goto finish; ++ /* pushing signing stream if we have signers */ ++ if (!ctx.signers.empty()) { ++ if ((ret = init_signed_dst(handler, &dests[destc], &dests[destc - 1]))) { ++ goto finish; ++ } ++ sstream = &dests[destc]; ++ destc++; + } +- destc++; + + /* pushing literal data stream */ +- if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) { +- goto finish; ++ if (!ctx.no_wrap) { ++ if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) { ++ goto finish; ++ } ++ destc++; + } +- destc++; + + /* process source with streams stack */ +- ret = process_stream_sequence(src, dests, destc); ++ ret = process_stream_sequence(src, dests, destc, sstream, &dests[destc - 1]); + finish: +- for (int i = destc - 1; i >= 0; i--) { +- dst_close(&dests[i], ret != RNP_SUCCESS); ++ for (size_t i = destc; i > 0; i--) { ++ dst_close(&dests[i - 1], ret); + } + return ret; + } + + rnp_result_t +@@ -2014,12 +1957,18 @@ rnp_raw_encrypt_src(pgp_source_t & + ctx.ctx = &secctx; + ctx.ealg = DEFAULT_PGP_SYMM_ALG; + handler.ctx = &ctx; + pgp_dest_t encrypted = {}; + +- rnp_result_t ret = rnp_ctx_add_encryption_password( +- ctx, password.c_str(), DEFAULT_PGP_HASH_ALG, DEFAULT_PGP_SYMM_ALG, 0); ++ rnp_result_t ret = RNP_ERROR_GENERIC; ++ try { ++ ret = ++ ctx.add_encryption_password(password, DEFAULT_PGP_HASH_ALG, DEFAULT_PGP_SYMM_ALG); ++ } catch (const std::exception &e) { ++ RNP_LOG("%s", e.what()); ++ goto done; ++ } + if (ret) { + goto done; + } + + ret = init_encrypted_dst(&handler, &encrypted, &dst); +diff --git a/comm/third_party/rnp/src/librepgp/stream-write.h b/third_party/rnp/src/lib/commrepgp/stream-write.h +--- a/comm/third_party/rnp/src/librepgp/stream-write.h ++++ b/comm/third_party/rnp/src/librepgp/stream-write.h +@@ -40,17 +40,10 @@ typedef struct pgp_write_handler_t { + rnp_ctx_t * ctx; + + void *param; + } pgp_write_handler_t; + +-/** @brief symmetrically encrypt the input data +- * @param handler handler to respond on stream processor callbacks +- * @param src input source: file, stdin, memory, whatever else conforming to pgp_source_t +- * @param dst output destination: file, stdout, memory, whatever else conforming to pgp_dest_t +- **/ +-rnp_result_t rnp_encrypt_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst); +- + /** @brief sign the input data, producing attached, detached or cleartext signature. + * Type of the signature is controlled by clearsign and detached fields of the + * rnp_ctx_t structure + * @param handler handler to respond on stream processor callbacks, and additional processing + * parameters, including rnp_ctx_t +diff --git a/comm/third_party/rnp/src/rnp/fficli.cpp b/comm/third_party/rnp/src/rnp/fficli.cpp +--- a/comm/third_party/rnp/src/rnp/fficli.cpp ++++ b/comm/third_party/rnp/src/rnp/fficli.cpp +@@ -50,11 +50,14 @@ + #ifdef HAVE_SYS_RESOURCE_H + #include + #endif + #endif + +-#include "config.h" ++#ifdef _WIN32 ++#include ++#endif ++ + #include "fficli.h" + #include "str-utils.h" + #include "file-utils.h" + #include "time-utils.h" + #include "defaults.h" +@@ -98,13 +101,11 @@ disable_core_dumps(void) + return false; + } + #endif + + #ifdef _WIN32 +-#include "str-utils.h" + #include +-#include + #include + + static std::vector + get_utf8_args() + { +@@ -149,21 +150,23 @@ rnp_win_substitute_cmdline_args(int *arg + char **argv_utf8_cstrs = NULL; + try { + auto argv_utf8_strings = get_utf8_args(); + argc_utf8 = argv_utf8_strings.size(); + *argc = argc_utf8; +- argv_utf8_cstrs = new (std::nothrow) char *[argc_utf8](); ++ argv_utf8_cstrs = new (std::nothrow) char *[argc_utf8 + 1](); + if (!argv_utf8_cstrs) { + throw std::bad_alloc(); + } + for (int i = 0; i < argc_utf8; i++) { + auto arg_utf8 = strdup(argv_utf8_strings[i].c_str()); + if (!arg_utf8) { + throw std::bad_alloc(); + } + argv_utf8_cstrs[i] = arg_utf8; + } ++ /* argv must be terminated with NULL string */ ++ argv_utf8_cstrs[argc_utf8] = NULL; + } catch (...) { + if (argv_utf8_cstrs) { + rnp_win_clear_args(argc_utf8, argv_utf8_cstrs); + } + throw; +@@ -178,13 +181,13 @@ static bool + set_pass_fd(FILE **file, int passfd) + { + if (!file) { + return false; + } +- *file = fdopen(passfd, "r"); ++ *file = rnp_fdopen(passfd, "r"); + if (!*file) { +- ERR_MSG("cannot open fd %d for reading", passfd); ++ ERR_MSG("Cannot open fd %d for reading", passfd); + return false; + } + return true; + } + +@@ -320,18 +323,20 @@ stdin_getpass(const char *prompt, char * + goto end; + } + // doesn't hurt + *buffer = '\0'; + ++ if (!rnp->cfg().get_bool(CFG_NOTTY)) { + #ifndef _WIN32 +- in = fopen("/dev/tty", "w+ce"); ++ in = fopen("/dev/tty", "w+ce"); + #endif ++ out = in; ++ } ++ + if (!in) { + in = userio_in; +- out = stderr; +- } else { +- out = in; ++ out = (rnp && rnp->userio_out) ? rnp->userio_out : stdout; + } + + // TODO: Implement alternative for hiding password entry on Windows + // TODO: avoid duplicate termios code with pass-provider.cpp + #ifndef _WIN32 +@@ -375,40 +380,71 @@ ffi_pass_callback_stdin(rnp_ffi_t + char * keyid = NULL; + char target[64] = {0}; + char prompt[128] = {0}; + char * buffer = NULL; + bool ok = false; ++ bool protect = false; ++ bool decrypt_symmetric = false; ++ bool encrypt_symmetric = false; ++ bool is_primary = false; + cli_rnp_t *rnp = static_cast(app_ctx); + + if (!ffi || !pgp_context) { + goto done; + } + +- if (strcmp(pgp_context, "decrypt (symmetric)") && +- strcmp(pgp_context, "encrypt (symmetric)")) { ++ if (!strcmp(pgp_context, "protect")) { ++ protect = true; ++ } else if (!strcmp(pgp_context, "decrypt (symmetric)")) { ++ decrypt_symmetric = true; ++ } else if (!strcmp(pgp_context, "encrypt (symmetric)")) { ++ encrypt_symmetric = true; ++ } ++ ++ if (!decrypt_symmetric && !encrypt_symmetric) { + rnp_key_get_keyid(key, &keyid); + snprintf(target, sizeof(target), "key 0x%s", keyid); + rnp_buffer_destroy(keyid); ++ (void) rnp_key_is_primary(key, &is_primary); + } ++ ++ if (protect && rnp->reuse_password_for_subkey && !is_primary) { ++ char *primary_fprint = NULL; ++ if (rnp_key_get_primary_fprint(key, &primary_fprint) == RNP_SUCCESS && ++ !rnp->reuse_primary_fprint.empty() && ++ rnp->reuse_primary_fprint == primary_fprint) { ++ strncpy(buf, rnp->reused_password, buf_len); ++ ok = true; ++ } ++ ++ rnp_buffer_clear(rnp->reused_password, strnlen(rnp->reused_password, buf_len)); ++ free(rnp->reused_password); ++ rnp->reused_password = NULL; ++ rnp->reuse_password_for_subkey = false; ++ rnp_buffer_destroy(primary_fprint); ++ if (ok) ++ return true; ++ } ++ + buffer = (char *) calloc(1, buf_len); + if (!buffer) { + return false; + } + start: +- if (!strcmp(pgp_context, "decrypt (symmetric)")) { ++ if (decrypt_symmetric) { + snprintf(prompt, sizeof(prompt), "Enter password to decrypt data: "); +- } else if (!strcmp(pgp_context, "encrypt (symmetric)")) { ++ } else if (encrypt_symmetric) { + snprintf(prompt, sizeof(prompt), "Enter password to encrypt data: "); + } else { +- snprintf(prompt, sizeof(prompt), "Enter password for %s: ", target); ++ snprintf(prompt, sizeof(prompt), "Enter password for %s to %s: ", target, pgp_context); + } + + if (!stdin_getpass(prompt, buf, buf_len, rnp)) { + goto done; + } +- if (!strcmp(pgp_context, "protect") || !strcmp(pgp_context, "encrypt (symmetric)")) { +- if (!strcmp(pgp_context, "protect")) { ++ if (protect || encrypt_symmetric) { ++ if (protect) { + snprintf(prompt, sizeof(prompt), "Repeat password for %s: ", target); + } else { + snprintf(prompt, sizeof(prompt), "Repeat password: "); + } + +@@ -418,10 +454,27 @@ start: + if (strcmp(buf, buffer) != 0) { + fputs("\nPasswords do not match!", rnp->userio_out); + // currently will loop forever + goto start; + } ++ if (strnlen(buf, buf_len) == 0 && !rnp->cfg().get_bool(CFG_FORCE)) { ++ if (!cli_rnp_get_confirmation( ++ rnp, "Password is empty. The key will be left unprotected. Are you sure?")) { ++ goto start; ++ } ++ } ++ } ++ if (protect && is_primary) { ++ if (cli_rnp_get_confirmation( ++ rnp, "Would you like to use the same password to protect subkey(s)?")) { ++ char *primary_fprint = NULL; ++ rnp->reuse_password_for_subkey = true; ++ rnp_key_get_fprint(key, &primary_fprint); ++ rnp->reuse_primary_fprint = primary_fprint; ++ rnp->reused_password = strdup(buf); ++ rnp_buffer_destroy(primary_fprint); ++ } + } + ok = true; + done: + fputs("", rnp->userio_out); + rnp_buffer_clear(buffer, buf_len); +@@ -468,10 +521,22 @@ ffi_pass_callback_string(rnp_ffi_t + + strncpy(buf, pswd, buf_len); + return true; + } + ++#ifdef _WIN32 ++void ++rnpffiInvalidParameterHandler(const wchar_t *expression, ++ const wchar_t *function, ++ const wchar_t *file, ++ unsigned int line, ++ uintptr_t pReserved) ++{ ++ // do nothing as within release CRT all params are NULL ++} ++#endif ++ + bool + cli_rnp_t::init(const rnp_cfg &cfg) + { + cfg_.copy(cfg); + +@@ -500,19 +565,26 @@ cli_rnp_t::init(const rnp_cfg &cfg) + if (coredumps) { + ERR_MSG("warning: core dumps may be enabled, sensitive data may be leaked to disk"); + } + #endif + ++#ifdef _WIN32 ++ /* Setup invalid parameter handler for Windows */ ++ _invalid_parameter_handler handler = rnpffiInvalidParameterHandler; ++ _set_invalid_parameter_handler(handler); ++ _CrtSetReportMode(_CRT_ASSERT, 0); ++#endif ++ + /* Configure the results stream. */ + // TODO: UTF8? + const std::string &ress = cfg_.get_str(CFG_IO_RESS); + if (ress.empty() || (ress == "")) { + resfp = stderr; + } else if (ress == "") { + resfp = stdout; + } else if (!(resfp = rnp_fopen(ress.c_str(), "w"))) { +- ERR_MSG("cannot open results %s for writing", ress.c_str()); ++ ERR_MSG("Cannot open results %s for writing", ress.c_str()); + return false; + } + + bool res = false; + const std::string pformat = pubformat(); +@@ -520,11 +592,11 @@ cli_rnp_t::init(const rnp_cfg &cfg) + if (pformat.empty() || sformat.empty()) { + ERR_MSG("Unknown public or secret keyring format"); + return false; + } + if (rnp_ffi_create(&ffi, pformat.c_str(), sformat.c_str())) { +- ERR_MSG("failed to initialize FFI"); ++ ERR_MSG("Failed to initialize FFI"); + return false; + } + + // by default use stdin password provider + if (rnp_ffi_set_pass_provider(ffi, ffi_pass_callback_stdin, this)) { +@@ -538,10 +610,14 @@ cli_rnp_t::init(const rnp_cfg &cfg) + } + if (rnp_ffi_set_pass_provider(ffi, ffi_pass_callback_file, passfp)) { + goto done; + } + } ++ // setup current time if requested ++ if (cfg_.has(CFG_CURTIME)) { ++ rnp_set_timestamp(ffi, cfg_.time()); ++ } + pswdtries = MAX_PASSWORD_ATTEMPTS; + res = true; + done: + if (!res) { + rnp_ffi_destroy(ffi); +@@ -570,37 +646,39 @@ cli_rnp_t::end() + } + userio_out = NULL; + rnp_ffi_destroy(ffi); + ffi = NULL; + cfg_.clear(); ++ reuse_primary_fprint.clear(); ++ if (reused_password) { ++ rnp_buffer_clear(reused_password, strlen(reused_password)); ++ free(reused_password); ++ reused_password = NULL; ++ } ++ reuse_password_for_subkey = false; + } + + bool + cli_rnp_t::load_keyring(bool secret) + { +- const char *path = secret ? secpath().c_str() : pubpath().c_str(); +- bool dir = secret && (secformat() == RNP_KEYSTORE_G10); +- if (dir && !rnp_dir_exists(path)) { +- ERR_MSG("warning: keyring directory at '%s' doesn't exist.", path); +- return true; +- } +- if (!dir && !rnp_file_exists(path)) { +- ERR_MSG("warning: keyring at path '%s' doesn't exist.", path); ++ const std::string &path = secret ? secpath() : pubpath(); ++ bool dir = secret && (secformat() == RNP_KEYSTORE_G10); ++ if (!rnp::path::exists(path, dir)) { + return true; + } + + rnp_input_t keyin = NULL; +- if (rnp_input_from_path(&keyin, path)) { +- ERR_MSG("warning: failed to open keyring at path '%s' for reading.", path); ++ if (rnp_input_from_path(&keyin, path.c_str())) { ++ ERR_MSG("Warning: failed to open keyring at path '%s' for reading.", path.c_str()); + return true; + } + + const char * format = secret ? secformat().c_str() : pubformat().c_str(); + uint32_t flags = secret ? RNP_LOAD_SAVE_SECRET_KEYS : RNP_LOAD_SAVE_PUBLIC_KEYS; + rnp_result_t ret = rnp_load_keys(ffi, format, keyin, flags); + if (ret) { +- ERR_MSG("error: failed to load keyring from '%s'", path); ++ ERR_MSG("Error: failed to load keyring from '%s'", path.c_str()); + } + rnp_input_destroy(keyin); + + if (ret) { + return false; +@@ -611,11 +689,11 @@ cli_rnp_t::load_keyring(bool secret) + (void) rnp_get_secret_key_count(ffi, &keycount); + } else { + (void) rnp_get_public_key_count(ffi, &keycount); + } + if (!keycount) { +- ERR_MSG("warning: no keys were loaded from the keyring '%s'.", path); ++ ERR_MSG("Warning: no keys were loaded from the keyring '%s'.", path.c_str()); + } + return true; + } + + bool +@@ -903,35 +981,10 @@ json_obj_get_str(json_object *obj, const + return NULL; + } + return json_object_get_string(fld); + } + +-int64_t +-json_obj_get_int64(json_object *obj, const char *key) +-{ +- json_object *fld = NULL; +- if (!json_object_object_get_ex(obj, key, &fld)) { +- return 0; +- } +- return json_object_get_int64(fld); +-} +- +-bool +-rnp_casecmp(const std::string &str1, const std::string &str2) +-{ +- if (str1.size() != str2.size()) { +- return false; +- } +- +- for (size_t i = 0; i < str1.size(); i++) { +- if (tolower(str1[i]) != tolower(str2[i])) { +- return false; +- } +- } +- return true; +-} +- + static char * + cli_key_usage_str(rnp_key_handle_t key, char *buf) + { + char *orig = buf; + bool allow = false; +@@ -1000,17 +1053,43 @@ const std::string + cli_rnp_alg_to_ffi(const std::string alg) + { + size_t count = sizeof(alg_aliases) / sizeof(alg_aliases[0]); + assert((count % 2) == 0); + for (size_t idx = 0; idx < count; idx += 2) { +- if (rnp_casecmp(alg, alg_aliases[idx])) { ++ if (rnp::str_case_eq(alg, alg_aliases[idx])) { + return alg_aliases[idx + 1]; + } + } + return alg; + } + ++bool ++cli_rnp_set_hash(rnp_cfg &cfg, const std::string &hash) ++{ ++ bool supported = false; ++ auto &alg = cli_rnp_alg_to_ffi(hash); ++ if (rnp_supports_feature(RNP_FEATURE_HASH_ALG, alg.c_str(), &supported) || !supported) { ++ ERR_MSG("Unsupported hash algorithm: %s", hash.c_str()); ++ return false; ++ } ++ cfg.set_str(CFG_HASH, alg); ++ return true; ++} ++ ++bool ++cli_rnp_set_cipher(rnp_cfg &cfg, const std::string &cipher) ++{ ++ bool supported = false; ++ auto &alg = cli_rnp_alg_to_ffi(cipher); ++ if (rnp_supports_feature(RNP_FEATURE_SYMM_ALG, alg.c_str(), &supported) || !supported) { ++ ERR_MSG("Unsupported encryption algorithm: %s", cipher.c_str()); ++ return false; ++ } ++ cfg.set_str(CFG_CIPHER, alg); ++ return true; ++} ++ + #ifndef RNP_USE_STD_REGEX + static std::string + cli_rnp_unescape_for_regcomp(const std::string &src) + { + std::string result; +@@ -1049,40 +1128,91 @@ cli_rnp_unescape_for_regcomp(const std:: + + return result; + } + #endif + ++/* Convert key algorithm constant to one displayed to the user */ ++static const char * ++cli_rnp_normalize_key_alg(const char *alg) ++{ ++ if (!strcmp(alg, RNP_ALGNAME_EDDSA)) { ++ return "EdDSA"; ++ } ++ if (!strcmp(alg, RNP_ALGNAME_ELGAMAL)) { ++ return "ElGamal"; ++ } ++ return alg; ++} ++ ++static void ++cli_rnp_print_sig_info(FILE *fp, rnp_ffi_t ffi, rnp_signature_handle_t sig) ++{ ++ uint32_t creation = 0; ++ (void) rnp_signature_get_creation(sig, &creation); ++ ++ char *keyfp = NULL; ++ char *keyid = NULL; ++ (void) rnp_signature_get_key_fprint(sig, &keyfp); ++ (void) rnp_signature_get_keyid(sig, &keyid); ++ ++ char * signer_uid = NULL; ++ rnp_key_handle_t signer = NULL; ++ if (keyfp) { ++ /* Fingerprint lookup is faster */ ++ (void) rnp_locate_key(ffi, "fingerprint", keyfp, &signer); ++ } else if (keyid) { ++ (void) rnp_locate_key(ffi, "keyid", keyid, &signer); ++ } ++ if (signer) { ++ /* signer primary uid */ ++ (void) rnp_key_get_primary_uid(signer, &signer_uid); ++ } ++ ++ /* signer key id */ ++ fprintf(fp, "sig %s ", keyid ? rnp::lowercase(keyid) : "[no key id]"); ++ /* signature creation time */ ++ char buf[64] = {0}; ++ fprintf(fp, "%s", ptimestr(buf, sizeof(buf), creation)); ++ /* signer's userid */ ++ fprintf(fp, " %s", signer_uid ? signer_uid : "[unknown]"); ++ /* signature validity */ ++ const char * valmsg = NULL; ++ rnp_result_t validity = rnp_signature_is_valid(sig, 0); ++ switch (validity) { ++ case RNP_SUCCESS: ++ valmsg = ""; ++ break; ++ case RNP_ERROR_SIGNATURE_EXPIRED: ++ valmsg = " [expired]"; ++ break; ++ case RNP_ERROR_SIGNATURE_INVALID: ++ valmsg = " [invalid]"; ++ break; ++ default: ++ valmsg = " [unverified]"; ++ } ++ fprintf(fp, "%s\n", valmsg); ++ ++ (void) rnp_key_handle_destroy(signer); ++ rnp_buffer_destroy(keyid); ++ rnp_buffer_destroy(keyfp); ++ rnp_buffer_destroy(signer_uid); ++} ++ + void + cli_rnp_print_key_info(FILE *fp, rnp_ffi_t ffi, rnp_key_handle_t key, bool psecret, bool psigs) + { +- char buf[64] = {0}; +- const char * header = NULL; +- bool secret = false; +- bool primary = false; +- bool revoked = false; +- uint32_t bits = 0; +- int64_t create = 0; +- uint32_t expiry = 0; +- size_t uids = 0; +- char * json = NULL; +- json_object *pkts = NULL; +- json_object *keypkt = NULL; ++ char buf[64] = {0}; ++ const char *header = NULL; ++ bool secret = false; ++ bool primary = false; + + /* header */ +- if (rnp_key_have_secret(key, &secret) || rnp_key_is_primary(key, &primary) || +- rnp_key_packets_to_json(key, false, RNP_JSON_DUMP_GRIP, &json)) { ++ if (rnp_key_have_secret(key, &secret) || rnp_key_is_primary(key, &primary)) { + fprintf(fp, "Key error.\n"); + return; + } +- if (!(pkts = json_tokener_parse(json))) { +- fprintf(fp, "Key JSON error.\n"); +- goto done; +- } +- if (!(keypkt = json_object_array_get_idx(pkts, 0))) { +- fprintf(fp, "Key JSON error.\n"); +- goto done; +- } + + if (psecret && secret) { + header = primary ? "sec" : "ssb"; + } else { + header = primary ? "pub" : "sub"; +@@ -1091,110 +1221,110 @@ cli_rnp_print_key_info(FILE *fp, rnp_ffi + fprintf(fp, "\n"); + } + fprintf(fp, "%s ", header); + + /* key bits */ ++ uint32_t bits = 0; + rnp_key_get_bits(key, &bits); + fprintf(fp, "%d/", (int) bits); + /* key algorithm */ +- fprintf(fp, "%s ", json_obj_get_str(keypkt, "algorithm.str")); ++ char *alg = NULL; ++ (void) rnp_key_get_alg(key, &alg); ++ fprintf(fp, "%s ", cli_rnp_normalize_key_alg(alg)); + /* key id */ +- fprintf(fp, "%s", json_obj_get_str(keypkt, "keyid")); ++ char *keyid = NULL; ++ (void) rnp_key_get_keyid(key, &keyid); ++ fprintf(fp, "%s", rnp::lowercase(keyid)); + /* key creation time */ +- create = json_obj_get_int64(keypkt, "creation time"); ++ uint32_t create = 0; ++ (void) rnp_key_get_creation(key, &create); + fprintf(fp, " %s", ptimestr(buf, sizeof(buf), create)); + /* key usage */ +- fprintf(fp, " [%s]", cli_key_usage_str(key, buf)); ++ bool valid = false; ++ bool expired = false; ++ bool revoked = false; ++ (void) rnp_key_is_valid(key, &valid); ++ (void) rnp_key_is_expired(key, &expired); ++ (void) rnp_key_is_revoked(key, &revoked); ++ if (valid || expired || revoked) { ++ fprintf(fp, " [%s]", cli_key_usage_str(key, buf)); ++ } else { ++ fprintf(fp, " [INVALID]"); ++ } + /* key expiration */ ++ uint32_t expiry = 0; + (void) rnp_key_get_expiration(key, &expiry); +- if (expiry > 0) { +- uint32_t now = time(NULL); +- auto expire_time = create + expiry; +- ptimestr(buf, sizeof(buf), expire_time); +- fprintf(fp, " [%s %s]", expire_time <= now ? "EXPIRED" : "EXPIRES", buf); ++ if (expiry) { ++ ptimestr(buf, sizeof(buf), create + expiry); ++ fprintf(fp, " [%s %s]", expired ? "EXPIRED" : "EXPIRES", buf); + } + /* key is revoked */ +- (void) rnp_key_is_revoked(key, &revoked); + if (revoked) { + fprintf(fp, " [REVOKED]"); + } + /* fingerprint */ +- fprintf(fp, "\n %s\n", json_obj_get_str(keypkt, "fingerprint")); ++ char *keyfp = NULL; ++ (void) rnp_key_get_fprint(key, &keyfp); ++ fprintf(fp, "\n %s\n", rnp::lowercase(keyfp)); ++ /* direct-key or binding signatures */ ++ if (psigs) { ++ size_t sigs = 0; ++ (void) rnp_key_get_signature_count(key, &sigs); ++ for (size_t i = 0; i < sigs; i++) { ++ rnp_signature_handle_t sig = NULL; ++ (void) rnp_key_get_signature_at(key, i, &sig); ++ if (!sig) { ++ continue; ++ } ++ cli_rnp_print_sig_info(fp, ffi, sig); ++ rnp_signature_handle_destroy(sig); ++ } ++ } + /* user ids */ ++ size_t uids = 0; + (void) rnp_key_get_uid_count(key, &uids); + for (size_t i = 0; i < uids; i++) { + rnp_uid_handle_t uid = NULL; +- bool revoked = false; +- char * uid_str = NULL; +- size_t sigs = 0; + + if (rnp_key_get_uid_handle_at(key, i, &uid)) { + continue; + } ++ bool revoked = false; ++ bool valid = false; ++ char *uid_str = NULL; + (void) rnp_uid_is_revoked(uid, &revoked); ++ (void) rnp_uid_is_valid(uid, &valid); + (void) rnp_key_get_uid_at(key, i, &uid_str); + + /* userid itself with revocation status */ + fprintf(fp, "uid %s", cli_rnp_escape_string(uid_str).c_str()); +- fprintf(fp, "%s\n", revoked ? "[REVOKED]" : ""); ++ fprintf(fp, "%s\n", revoked ? " [REVOKED]" : valid ? "" : " [INVALID]"); + rnp_buffer_destroy(uid_str); + + /* print signatures only if requested */ + if (!psigs) { + (void) rnp_uid_handle_destroy(uid); + continue; + } + ++ size_t sigs = 0; + (void) rnp_uid_get_signature_count(uid, &sigs); + for (size_t j = 0; j < sigs; j++) { + rnp_signature_handle_t sig = NULL; +- rnp_key_handle_t signer = NULL; +- char * keyid = NULL; +- uint32_t creation = 0; +- char * signer_uid = NULL; +- +- if (rnp_uid_get_signature_at(uid, j, &sig)) { ++ (void) rnp_uid_get_signature_at(uid, j, &sig); ++ if (!sig) { + continue; + } +- if (rnp_signature_get_creation(sig, &creation)) { +- goto next; +- } +- if (rnp_signature_get_keyid(sig, &keyid)) { +- goto next; +- } +- if (keyid) { +- /* lowercase key id */ +- for (char *idptr = keyid; *idptr; ++idptr) { +- *idptr = tolower(*idptr); +- } +- /* signer primary uid */ +- if (rnp_locate_key(ffi, "keyid", keyid, &signer)) { +- goto next; +- } +- if (signer) { +- (void) rnp_key_get_primary_uid(signer, &signer_uid); +- } +- } +- +- /* signer key id */ +- fprintf(fp, "sig %s ", keyid ? keyid : "[no key id]"); +- /* signature creation time */ +- fprintf(fp, "%s", ptimestr(buf, sizeof(buf), creation)); +- /* signer's userid */ +- fprintf(fp, " %s\n", signer_uid ? signer_uid : "[unknown]"); +- next: +- (void) rnp_signature_handle_destroy(sig); +- (void) rnp_key_handle_destroy(signer); +- rnp_buffer_destroy(keyid); +- rnp_buffer_destroy(signer_uid); ++ cli_rnp_print_sig_info(fp, ffi, sig); ++ rnp_signature_handle_destroy(sig); + } + (void) rnp_uid_handle_destroy(uid); + } + +-done: +- rnp_buffer_destroy(json); +- json_object_put(pkts); ++ rnp_buffer_destroy(alg); ++ rnp_buffer_destroy(keyid); ++ rnp_buffer_destroy(keyfp); + } + + bool + cli_rnp_save_keyrings(cli_rnp_t *rnp) + { +@@ -1715,38 +1845,10 @@ done: + clear_key_handles(keys); + } + return res; + } + +-/** @brief compose path from dir, subdir and filename, and return it. +- * @param dir [in] directory path +- * @param subddir [in] subdirectory to add to the path, can be empty +- * @param filename [in] filename (or path/filename) +- * +- * @return constructed path +- **/ +-static std::string +-rnp_path_compose(const std::string &dir, +- const std::string &subdir, +- const std::string &filename) +-{ +- std::string res = dir; +- if (!subdir.empty()) { +- if (!res.empty() && (res.back() != '/')) { +- res.push_back('/'); +- } +- res.append(subdir); +- } +- +- if (!res.empty() && (res.back() != '/')) { +- res.push_back('/'); +- } +- +- res.append(filename); +- return res; +-} +- + static bool + rnp_cfg_set_ks_info(rnp_cfg &cfg) + { + if (cfg.get_bool(CFG_KEYSTORE_DISABLED)) { + cfg.set_str(CFG_KR_PUB_PATH, ""); +@@ -1759,214 +1861,144 @@ rnp_cfg_set_ks_info(rnp_cfg &cfg) + /* getting path to keyrings. If it is specified by user in 'homedir' param then it is + * considered as the final path */ + bool defhomedir = false; + std::string homedir = cfg.get_str(CFG_HOMEDIR); + if (homedir.empty()) { +- const char *home = getenv("HOME"); +- homedir = home ? home : ""; ++ homedir = rnp::path::HOME(); + defhomedir = true; + } + ++ /* Check whether $HOME or homedir exists */ + struct stat st; +- + if (rnp_stat(homedir.c_str(), &st) || rnp_access(homedir.c_str(), R_OK | W_OK)) { + ERR_MSG("Home directory '%s' does not exist or is not writable!", homedir.c_str()); + return false; + } + +- /* detecting key storage format */ +- std::string subdir = defhomedir ? SUBDIRECTORY_RNP : ""; +- std::string pubpath; +- std::string secpath; +- std::string ks_format = cfg.get_str(CFG_KEYSTOREFMT); +- +- if (ks_format.empty()) { +- pubpath = rnp_path_compose(homedir, subdir, PUBRING_KBX); +- secpath = rnp_path_compose(homedir, subdir, SECRING_G10); +- +- bool pubpath_exists = !rnp_stat(pubpath.c_str(), &st); +- bool secpath_exists = !rnp_stat(secpath.c_str(), &st); +- +- if (pubpath_exists && secpath_exists) { +- ks_format = RNP_KEYSTORE_GPG21; +- } else if (secpath_exists) { +- ks_format = RNP_KEYSTORE_G10; +- } else if (pubpath_exists) { +- ks_format = RNP_KEYSTORE_KBX; +- } else { +- ks_format = RNP_KEYSTORE_GPG; ++ /* creating home dir if needed */ ++ if (defhomedir) { ++ char *rnphome = NULL; ++ if (rnp_get_default_homedir(&rnphome)) { ++ ERR_MSG("Failed to obtain default home directory."); ++ return false; ++ } ++ homedir = rnphome; ++ rnp_buffer_destroy(rnphome); ++ if (!rnp::path::exists(homedir, true) && RNP_MKDIR(homedir.c_str(), 0700) == -1 && ++ errno != EEXIST) { ++ ERR_MSG("Cannot mkdir '%s' errno = %d", homedir.c_str(), errno); ++ return false; + } + } + +- /* creating home dir if needed */ +- if (!subdir.empty()) { +- pubpath = rnp_path_compose(homedir, "", subdir); +- if (RNP_MKDIR(pubpath.c_str(), 0700) == -1 && errno != EEXIST) { +- ERR_MSG("cannot mkdir '%s' errno = %d", pubpath.c_str(), errno); +- return false; ++ /* detecting key storage format */ ++ std::string ks_format = cfg.get_str(CFG_KEYSTOREFMT); ++ if (ks_format.empty()) { ++ char *pub_format = NULL; ++ char *sec_format = NULL; ++ char *pubpath = NULL; ++ char *secpath = NULL; ++ rnp_detect_homedir_info(homedir.c_str(), &pub_format, &pubpath, &sec_format, &secpath); ++ bool detected = pub_format && sec_format && pubpath && secpath; ++ if (detected) { ++ cfg.set_str(CFG_KR_PUB_FORMAT, pub_format); ++ cfg.set_str(CFG_KR_SEC_FORMAT, sec_format); ++ cfg.set_str(CFG_KR_PUB_PATH, pubpath); ++ cfg.set_str(CFG_KR_SEC_PATH, secpath); ++ } else { ++ /* default to GPG */ ++ ks_format = RNP_KEYSTORE_GPG; ++ } ++ rnp_buffer_destroy(pub_format); ++ rnp_buffer_destroy(sec_format); ++ rnp_buffer_destroy(pubpath); ++ rnp_buffer_destroy(secpath); ++ if (detected) { ++ return true; + } + } + + std::string pub_format = RNP_KEYSTORE_GPG; + std::string sec_format = RNP_KEYSTORE_GPG; ++ std::string pubpath; ++ std::string secpath; + + if (ks_format == RNP_KEYSTORE_GPG) { +- pubpath = rnp_path_compose(homedir, subdir, PUBRING_GPG); +- secpath = rnp_path_compose(homedir, subdir, SECRING_GPG); +- pub_format = RNP_KEYSTORE_GPG; +- sec_format = RNP_KEYSTORE_GPG; ++ pubpath = rnp::path::append(homedir, PUBRING_GPG); ++ secpath = rnp::path::append(homedir, SECRING_GPG); + } else if (ks_format == RNP_KEYSTORE_GPG21) { +- pubpath = rnp_path_compose(homedir, subdir, PUBRING_KBX); +- secpath = rnp_path_compose(homedir, subdir, SECRING_G10); ++ pubpath = rnp::path::append(homedir, PUBRING_KBX); ++ secpath = rnp::path::append(homedir, SECRING_G10); + pub_format = RNP_KEYSTORE_KBX; + sec_format = RNP_KEYSTORE_G10; + } else if (ks_format == RNP_KEYSTORE_KBX) { +- pubpath = rnp_path_compose(homedir, subdir, PUBRING_KBX); +- secpath = rnp_path_compose(homedir, subdir, SECRING_KBX); ++ pubpath = rnp::path::append(homedir, PUBRING_KBX); ++ secpath = rnp::path::append(homedir, SECRING_KBX); + pub_format = RNP_KEYSTORE_KBX; + sec_format = RNP_KEYSTORE_KBX; + } else if (ks_format == RNP_KEYSTORE_G10) { +- pubpath = rnp_path_compose(homedir, subdir, PUBRING_G10); +- secpath = rnp_path_compose(homedir, subdir, SECRING_G10); ++ pubpath = rnp::path::append(homedir, PUBRING_G10); ++ secpath = rnp::path::append(homedir, SECRING_G10); + pub_format = RNP_KEYSTORE_G10; + sec_format = RNP_KEYSTORE_G10; + } else { +- ERR_MSG("unsupported keystore format: \"%s\"", ks_format.c_str()); ++ ERR_MSG("Unsupported keystore format: \"%s\"", ks_format.c_str()); + return false; + } + ++ /* Check whether homedir is empty */ ++ if (rnp::path::empty(homedir)) { ++ ERR_MSG("Keyring directory '%s' is empty.\nUse \"rnpkeys\" command to generate a new " ++ "key or import existing keys from the file or GnuPG keyrings.", ++ homedir.c_str()); ++ } ++ + cfg.set_str(CFG_KR_PUB_PATH, pubpath); + cfg.set_str(CFG_KR_SEC_PATH, secpath); + cfg.set_str(CFG_KR_PUB_FORMAT, pub_format); + cfg.set_str(CFG_KR_SEC_FORMAT, sec_format); + return true; + } + +-/* read any gpg config file */ +-static bool +-conffile(const std::string &homedir, std::string &userid) +-{ +- char buf[BUFSIZ]; +- FILE *fp; +- +-#ifndef RNP_USE_STD_REGEX +- regmatch_t matchv[10]; +- regex_t keyre; +-#else +- static std::regex keyre("^[ \t]*default-key[ \t]+([0-9a-zA-F]+)", +- std::regex_constants::extended); +-#endif +- (void) snprintf(buf, sizeof(buf), "%s/.gnupg/gpg.conf", homedir.c_str()); +- if ((fp = rnp_fopen(buf, "r")) == NULL) { +- return false; +- } +-#ifndef RNP_USE_STD_REGEX +- (void) memset(&keyre, 0x0, sizeof(keyre)); +- if (regcomp(&keyre, "^[ \t]*default-key[ \t]+([0-9a-zA-F]+)", REG_EXTENDED) != 0) { +- ERR_MSG("failed to compile regular expression"); +- fclose(fp); +- return false; +- } +-#endif +- while (fgets(buf, (int) sizeof(buf), fp) != NULL) { +-#ifndef RNP_USE_STD_REGEX +- if (regexec(&keyre, buf, 10, matchv, 0) == 0) { +- userid = +- std::string(&buf[(int) matchv[1].rm_so], matchv[1].rm_eo - matchv[1].rm_so); +- ERR_MSG("rnp: default key set to \"%s\"", userid.c_str()); +- } +-#else +- std::smatch result; +- std::string input = buf; +- if (std::regex_search(input, result, keyre)) { +- userid = result[1].str(); +- ERR_MSG("rnp: default key set to \"%s\"", userid.c_str()); +- } +-#endif +- } +- (void) fclose(fp); +-#ifndef RNP_USE_STD_REGEX +- regfree(&keyre); +-#endif +- return true; +-} +- + static void + rnp_cfg_set_defkey(rnp_cfg &cfg) + { +- bool defhomedir = false; +- std::string homedir = cfg.get_str(CFG_HOMEDIR); +- if (homedir.empty()) { +- const char *home = getenv("HOME"); +- homedir = home ? home : ""; +- defhomedir = true; +- } +- + /* If a userid has been given, we'll use it. */ + std::string userid = cfg.get_count(CFG_USERID) ? cfg.get_str(CFG_USERID, 0) : ""; + if (!userid.empty()) { + cfg.set_str(CFG_KR_DEF_KEY, userid); +- return; +- } +- /* also search in config file for default id */ +- if (defhomedir) { +- std::string id; +- if (conffile(homedir, id) && !id.empty()) { +- cfg.unset(CFG_USERID); +- cfg.add_str(CFG_USERID, id); +- cfg.set_str(CFG_KR_DEF_KEY, id); +- } + } + } + + bool + cli_cfg_set_keystore_info(rnp_cfg &cfg) + { + /* detecting keystore paths and format */ + if (!rnp_cfg_set_ks_info(cfg)) { +- ERR_MSG("cannot obtain keystore path(s)"); + return false; + } + + /* default key/userid */ + rnp_cfg_set_defkey(cfg); + return true; + } + + static bool +-stdin_reader(void *app_ctx, void *buf, size_t len, size_t *readres) ++is_stdinout_spec(const std::string &spec) + { +- ssize_t res = read(STDIN_FILENO, buf, len); +- if (res < 0) { +- return false; +- } +- *readres = res; +- return true; +-} +- +-/* This produces +- runtime error: call to function stdout_writer(void*, void const*, unsigned long) through +- pointer to incorrect function type 'bool (*)(void *, const void *, unsigned long)' */ +-#if defined(__clang__) +-__attribute__((no_sanitize("undefined"))) +-#endif +-static bool +-stdout_writer(void *app_ctx, const void *buf, size_t len) +-{ +- ssize_t wlen = write(STDOUT_FILENO, buf, len); +- return (wlen >= 0) && (size_t) wlen == len; ++ return spec.empty() || (spec == "-"); + } + + rnp_input_t + cli_rnp_input_from_specifier(cli_rnp_t &rnp, const std::string &spec, bool *is_path) + { + rnp_input_t input = NULL; + rnp_result_t res = RNP_ERROR_GENERIC; + bool path = false; +- if (spec.empty() || (spec == "-")) { ++ if (is_stdinout_spec(spec)) { + /* input from stdin */ +- res = rnp_input_from_callback(&input, stdin_reader, NULL, NULL); ++ res = rnp_input_from_stdin(&input); + } else if ((spec.size() > 4) && (spec.compare(0, 4, "env:") == 0)) { + /* input from an environment variable */ + const char *envval = getenv(spec.c_str() + 4); + if (!envval) { + ERR_MSG("Failed to get value of the environment variable '%s'.", spec.c_str() + 4); +@@ -1994,14 +2026,18 @@ cli_rnp_output_to_specifier(cli_rnp_t &r + rnp_output_t output = NULL; + rnp_result_t res = RNP_ERROR_GENERIC; + std::string path = spec; + if (discard) { + res = rnp_output_to_null(&output); +- } else if (spec.empty() || (spec == "-")) { +- res = rnp_output_to_callback(&output, stdout_writer, NULL, NULL); ++ } else if (is_stdinout_spec(spec)) { ++ res = rnp_output_to_stdout(&output); + } else if (!rnp_get_output_filename(spec, path, rnp)) { +- ERR_MSG("Operation failed: file '%s' already exists.", spec.c_str()); ++ if (spec.empty()) { ++ ERR_MSG("Operation failed: no output filename specified"); ++ } else { ++ ERR_MSG("Operation failed: file '%s' already exists.", spec.c_str()); ++ } + res = RNP_ERROR_BAD_PARAMETERS; + } else { + res = rnp_output_to_file(&output, path.c_str(), RNP_OUTPUT_FILE_OVERWRITE); + } + return res ? NULL : output; +@@ -2069,31 +2105,24 @@ cli_rnp_export_revocation(cli_rnp_t *rnp + ERR_MSG("Ambiguous input: too many keys found for '%s'.", key); + clear_key_handles(keys); + return false; + } + rnp_output_t output = NULL; +- rnp_output_t armored = NULL; + bool result = false; + + output = cli_rnp_output_to_specifier(*rnp, rnp->cfg().get_str(CFG_OUTFILE)); + if (!output) { + goto done; + } + +- /* export it armored by default */ +- if (rnp_output_to_armor(output, &armored, "public key")) { +- goto done; +- } +- + result = !rnp_key_export_revocation(keys[0], +- armored, +- 0, ++ output, ++ RNP_KEY_EXPORT_ARMORED, + rnp->cfg().get_cstr(CFG_HASH), + rnp->cfg().get_cstr(CFG_REV_TYPE), + rnp->cfg().get_cstr(CFG_REV_REASON)); + done: +- rnp_output_destroy(armored); + rnp_output_destroy(output); + clear_key_handles(keys); + return result; + } + +@@ -2263,24 +2292,48 @@ has_extension(const std::string &path, c + } + return path.compare(path.length() - ext.length(), ext.length(), ext) == 0; + } + + static std::string +-output_extension(const rnp_cfg &cfg, const std::string &op) ++output_extension(const rnp_cfg &cfg, Operation op) + { +- if (op == "encrypt_sign") { ++ switch (op) { ++ case Operation::EncryptOrSign: { + bool armor = cfg.get_bool(CFG_ARMOR); + if (cfg.get_bool(CFG_DETACHED)) { + return armor ? EXT_ASC : EXT_SIG; + } + if (cfg.get_bool(CFG_CLEARTEXT)) { + return EXT_ASC; + } + return armor ? EXT_ASC : EXT_PGP; + } +- if (op == "armor") { ++ case Operation::Enarmor: + return EXT_ASC; ++ default: ++ return ""; ++ } ++} ++ ++static bool ++has_pgp_extension(const std::string &path) ++{ ++ return has_extension(path, EXT_PGP) || has_extension(path, EXT_ASC) || ++ has_extension(path, EXT_GPG); ++} ++ ++static std::string ++output_strip_extension(Operation op, const std::string &in) ++{ ++ std::string out = in; ++ if ((op == Operation::Verify) && (has_pgp_extension(out))) { ++ strip_extension(out); ++ return out; ++ } ++ if ((op == Operation::Dearmor) && (has_extension(out, EXT_ASC))) { ++ strip_extension(out); ++ return out; + } + return ""; + } + + static std::string +@@ -2291,39 +2344,39 @@ extract_filename(const std::string path) + return path; + } + return path.substr(lpos + 1); + } + +-static bool +-cli_rnp_init_io(const std::string &op, +- rnp_input_t * input, +- rnp_output_t * output, +- cli_rnp_t * rnp) ++bool ++cli_rnp_t::init_io(Operation op, rnp_input_t *input, rnp_output_t *output) + { +- const std::string &in = rnp->cfg().get_str(CFG_INFILE); ++ const std::string &in = cfg().get_str(CFG_INFILE); + bool is_pathin = true; + if (input) { +- *input = cli_rnp_input_from_specifier(*rnp, in, &is_pathin); ++ *input = cli_rnp_input_from_specifier(*this, in, &is_pathin); + if (!*input) { + return false; + } + } + + if (!output) { + return true; + } +- std::string out = rnp->cfg().get_str(CFG_OUTFILE); +- bool discard = (op == "verify") && out.empty() && rnp->cfg().get_bool(CFG_NO_OUTPUT); ++ std::string out = cfg().get_str(CFG_OUTFILE); ++ bool discard = (op == Operation::Verify) && out.empty() && cfg().get_bool(CFG_NO_OUTPUT); + + if (out.empty() && is_pathin && !discard) { +- std::string ext = output_extension(rnp->cfg(), op); ++ /* Attempt to guess whether to add or strip extension for known cases */ ++ std::string ext = output_extension(cfg(), op); + if (!ext.empty()) { + out = in + ext; ++ } else { ++ out = output_strip_extension(op, in); + } + } + +- *output = cli_rnp_output_to_specifier(*rnp, out, discard); ++ *output = cli_rnp_output_to_specifier(*this, out, discard); + if (!*output && input) { + rnp_input_destroy(*input); + *input = NULL; + } + return *output; +@@ -2349,11 +2402,11 @@ cli_rnp_dump_file(cli_rnp_t *rnp) + flags |= RNP_DUMP_RAW; + jflags |= RNP_JSON_DUMP_RAW; + } + + rnp_result_t ret = 0; +- if (!cli_rnp_init_io("dump", &input, &output, rnp)) { ++ if (!rnp->init_io(Operation::Dump, &input, &output)) { + ERR_MSG("failed to open source or create output"); + ret = 1; + goto done; + } + +@@ -2389,11 +2442,11 @@ bool + cli_rnp_armor_file(cli_rnp_t *rnp) + { + rnp_input_t input = NULL; + rnp_output_t output = NULL; + +- if (!cli_rnp_init_io("armor", &input, &output, rnp)) { ++ if (!rnp->init_io(Operation::Enarmor, &input, &output)) { + ERR_MSG("failed to open source or create output"); + return false; + } + rnp_result_t ret = rnp_enarmor(input, output, rnp->cfg().get_cstr(CFG_ARMOR_DATA_TYPE)); + rnp_input_destroy(input); +@@ -2405,11 +2458,11 @@ bool + cli_rnp_dearmor_file(cli_rnp_t *rnp) + { + rnp_input_t input = NULL; + rnp_output_t output = NULL; + +- if (!cli_rnp_init_io("dearmor", &input, &output, rnp)) { ++ if (!rnp->init_io(Operation::Dearmor, &input, &output)) { + ERR_MSG("failed to open source or create output"); + return false; + } + + rnp_result_t ret = rnp_dearmor(input, output); +@@ -2541,10 +2594,13 @@ cli_rnp_encrypt_and_sign(const rnp_cfg & + } + if (cfg.has(CFG_AEAD_CHUNK) && + rnp_op_encrypt_set_aead_bits(op, cfg.get_int(CFG_AEAD_CHUNK))) { + goto done; + } ++ if (cfg.has(CFG_NOWRAP) && rnp_op_encrypt_set_flags(op, RNP_ENCRYPT_NOWRAP)) { ++ goto done; ++ } + + /* adding passwords if password-based encryption is used */ + if (cfg.get_bool(CFG_ENCRYPT_SK)) { + std::string halg = cfg.get_hashalg(); + std::string ealg = cfg.get_str(CFG_CIPHER); +@@ -2634,11 +2690,11 @@ bool + cli_rnp_protect_file(cli_rnp_t *rnp) + { + rnp_input_t input = NULL; + rnp_output_t output = NULL; + +- if (!cli_rnp_init_io("encrypt_sign", &input, &output, rnp)) { ++ if (!rnp->init_io(Operation::EncryptOrSign, &input, &output)) { + ERR_MSG("failed to open source or create output"); + return false; + } + + bool res = false; +@@ -2659,41 +2715,23 @@ cli_rnp_protect_file(cli_rnp_t *rnp) + + /* helper function which prints something like 'using RSA (Sign-Only) key 0x0102030405060708 */ + static void + cli_rnp_print_sig_key_info(FILE *resfp, rnp_signature_handle_t sig) + { +- char * keyid = NULL; +- const char *alg = "Unknown"; ++ char *keyid = NULL; ++ char *alg = NULL; + +- if (!rnp_signature_get_keyid(sig, &keyid)) { +- for (char *idptr = keyid; *idptr; ++idptr) { +- *idptr = tolower(*idptr); +- } +- } +- +- char * json = NULL; +- json_object *pkts = NULL; +- json_object *sigpkt = NULL; ++ (void) rnp_signature_get_keyid(sig, &keyid); ++ rnp::lowercase(keyid); ++ (void) rnp_signature_get_alg(sig, &alg); + +- if (rnp_signature_packet_to_json(sig, RNP_JSON_DUMP_GRIP, &json)) { +- ERR_MSG("Signature error."); +- goto done; +- } +- if (!(pkts = json_tokener_parse(json))) { +- ERR_MSG("Signature JSON error"); +- goto done; +- } +- if (!(sigpkt = json_object_array_get_idx(pkts, 0))) { +- ERR_MSG("Signature JSON error"); +- goto done; +- } +- alg = json_obj_get_str(sigpkt, "algorithm.str"); +-done: +- fprintf(resfp, "using %s key %s\n", alg, keyid ? keyid : "0000000000000000"); ++ fprintf(resfp, ++ "using %s key %s\n", ++ cli_rnp_normalize_key_alg(alg), ++ keyid ? keyid : "0000000000000000"); + rnp_buffer_destroy(keyid); +- rnp_buffer_destroy(json); +- json_object_put(pkts); ++ rnp_buffer_destroy(alg); + } + + static void + cli_rnp_print_signatures(cli_rnp_t *rnp, const std::vector &sigs) + { +@@ -2720,35 +2758,40 @@ cli_rnp_print_signatures(cli_rnp_t *rnp, + break; + case RNP_ERROR_KEY_NOT_FOUND: + title = "NO PUBLIC KEY for signature"; + unknownc++; + break; +- default: ++ case RNP_ERROR_SIGNATURE_UNKNOWN: + title = "UNKNOWN signature"; ++ unknownc++; + break; ++ default: ++ title = "UNKNOWN signature status"; ++ break; ++ } ++ ++ if (status == RNP_ERROR_SIGNATURE_UNKNOWN) { ++ fprintf(resfp, "%s\n", title.c_str()); ++ continue; + } + + uint32_t create = 0; + uint32_t expiry = 0; + rnp_op_verify_signature_get_times(sig, &create, &expiry); + +- if (create > 0) { +- time_t crtime = create; ++ time_t crtime = create; ++ fprintf(resfp, ++ "%s made %s%s", ++ title.c_str(), ++ rnp_y2k38_warning(crtime) ? ">=" : "", ++ rnp_ctime(crtime)); ++ if (expiry) { ++ crtime = rnp_timeadd(crtime, expiry); + fprintf(resfp, +- "%s made %s%s", +- title.c_str(), ++ "Valid until %s%s\n", + rnp_y2k38_warning(crtime) ? ">=" : "", + rnp_ctime(crtime)); +- if (expiry > 0) { +- crtime = rnp_timeadd(crtime, expiry); +- fprintf(resfp, +- "Valid until %s%s\n", +- rnp_y2k38_warning(crtime) ? ">=" : "", +- rnp_ctime(crtime)); +- } +- } else { +- fprintf(resfp, "%s\n", title.c_str()); + } + + rnp_signature_handle_t handle = NULL; + if (rnp_op_verify_signature_get_handle(sig, &handle)) { + ERR_MSG("Failed to obtain signature handle."); +@@ -2763,27 +2806,41 @@ cli_rnp_print_signatures(cli_rnp_t *rnp, + rnp_key_handle_destroy(key); + } + rnp_signature_handle_destroy(handle); + } + +- if (sigs.size() == 0) { ++ if (!sigs.size()) { + ERR_MSG("No signature(s) found - is this a signed file?"); +- } else if (invalidc > 0 || unknownc > 0) { +- ERR_MSG( +- "Signature verification failure: %u invalid signature(s), %u unknown signature(s)", +- invalidc, +- unknownc); ++ return; ++ } ++ if (!invalidc && !unknownc) { ++ ERR_MSG("Signature(s) verified successfully"); ++ return; ++ } ++ /* Show a proper error message if there are invalid/unknown signatures */ ++ auto si = invalidc > 1 ? "s" : ""; ++ auto su = unknownc > 1 ? "s" : ""; ++ auto fail = "Signature verification failure: "; ++ if (invalidc && !unknownc) { ++ ERR_MSG("%s%u invalid signature%s", fail, invalidc, si); ++ } else if (!invalidc && unknownc) { ++ ERR_MSG("%s%u unknown signature%s", fail, unknownc, su); + } else { +- ERR_MSG("Signature(s) verified successfully"); ++ ERR_MSG("%s%u invalid signature%s, %u unknown signature%s", ++ fail, ++ invalidc, ++ si, ++ unknownc, ++ su); + } + } + + bool + cli_rnp_process_file(cli_rnp_t *rnp) + { + rnp_input_t input = NULL; +- if (!cli_rnp_init_io("verify", &input, NULL, rnp)) { ++ if (!rnp->init_io(Operation::Verify, &input, NULL)) { + ERR_MSG("failed to open source"); + return false; + } + + char *contents = NULL; +@@ -2800,33 +2857,52 @@ cli_rnp_process_file(cli_rnp_t *rnp) + rnp_result_t ret = RNP_ERROR_GENERIC; + bool res = false; + std::vector sigs; + size_t scount = 0; + +- if (rnp_casecmp(contents, "signature")) { ++ if (rnp::str_case_eq(contents, "signature")) { + /* detached signature */ + std::string in = rnp->cfg().get_str(CFG_INFILE); +- if (in.empty() || in == "-") { +- ERR_MSG("Cannot verify detached signature from stdin."); ++ std::string src = rnp->cfg().get_str(CFG_SOURCE); ++ if (is_stdinout_spec(in) && is_stdinout_spec(src)) { ++ ERR_MSG("Detached signature and signed source cannot be both stdin."); + goto done; + } +- if (!has_extension(in, EXT_SIG) && !has_extension(in, EXT_ASC)) { +- ERR_MSG("Unsupported detached signature extension."); ++ if (src.empty() && !has_extension(in, EXT_SIG) && !has_extension(in, EXT_ASC)) { ++ ERR_MSG("Unsupported detached signature extension. Use --source to override."); + goto done; + } +- if (!strip_extension(in) || rnp_input_from_path(&source, in.c_str())) { ++ if (src.empty()) { ++ src = in; ++ /* cannot fail as we checked for extension previously */ ++ strip_extension(src); ++ } ++ source = cli_rnp_input_from_specifier(*rnp, src, NULL); ++ if (!source) { + ERR_MSG("Failed to open source for detached signature verification."); + goto done; + } + + ret = rnp_op_verify_detached_create(&verify, rnp->ffi, source, input); ++ if (!ret) { ++ /* Currently CLI requires all signatures to be valid for success */ ++ ret = rnp_op_verify_set_flags(verify, RNP_VERIFY_REQUIRE_ALL_SIGS); ++ } + } else { +- if (!cli_rnp_init_io("verify", NULL, &output, rnp)) { ++ if (!rnp->init_io(Operation::Verify, NULL, &output)) { + ERR_MSG("Failed to create output stream."); + goto done; + } + ret = rnp_op_verify_create(&verify, rnp->ffi, input, output); ++ if (!ret && !rnp->cfg().get_bool(CFG_NO_OUTPUT)) { ++ /* This would happen if user requested decryption instead of verification */ ++ ret = rnp_op_verify_set_flags(verify, RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT); ++ } ++ if (!ret && rnp->cfg().get_bool(CFG_NO_OUTPUT)) { ++ /* Currently CLI requires all signatures to be valid for success */ ++ ret = rnp_op_verify_set_flags(verify, RNP_VERIFY_REQUIRE_ALL_SIGS); ++ } + } + if (ret) { + ERR_MSG("Failed to initialize verification/decryption operation."); + goto done; + } +diff --git a/comm/third_party/rnp/src/rnp/fficli.h b/comm/third_party/rnp/src/rnp/fficli.h +--- a/comm/third_party/rnp/src/rnp/fficli.h ++++ b/comm/third_party/rnp/src/rnp/fficli.h +@@ -34,10 +34,12 @@ + #include "rnp/rnp_err.h" + #include "config.h" + #include "rnpcfg.h" + #include "json.h" + ++enum class Operation { EncryptOrSign, Verify, Enarmor, Dearmor, Dump }; ++ + class cli_rnp_t { + private: + rnp_cfg cfg_{}; + bool load_keyring(bool secret); + bool is_cv25519_subkey(rnp_key_handle_t handle); +@@ -46,20 +48,25 @@ class cli_rnp_t { + std::string & cipher, + size_t & iterations); + bool check_cv25519_bits(rnp_key_handle_t key, char *prot_password, bool &tweaked); + + public: +- rnp_ffi_t ffi{}; +- FILE * resfp{}; /* where to put result messages, defaults to stdout */ +- FILE * passfp{}; /* file pointer for password input */ +- FILE * userio_in{}; /* file pointer for user's inputs */ +- FILE * userio_out{}; /* file pointer for user's outputs */ +- int pswdtries{}; /* number of password tries, -1 for unlimited */ ++ rnp_ffi_t ffi{}; ++ FILE * resfp{}; /* where to put result messages, defaults to stdout */ ++ FILE * passfp{}; /* file pointer for password input */ ++ FILE * userio_in{}; /* file pointer for user's inputs */ ++ FILE * userio_out{}; /* file pointer for user's outputs */ ++ int pswdtries{}; /* number of password tries, -1 for unlimited */ ++ bool reuse_password_for_subkey{}; ++ std::string reuse_primary_fprint; ++ char * reused_password{}; + + bool init(const rnp_cfg &cfg); + void end(); + ++ bool init_io(Operation op, rnp_input_t *input, rnp_output_t *output); ++ + bool load_keyrings(bool loadsecret = false); + + const std::string & + defkey() + { +@@ -215,15 +222,31 @@ void cli_rnp_print_feature(FILE * + * @return string with FFI algorithm's name. In case alias is not found the source string will + * be returned. + */ + const std::string cli_rnp_alg_to_ffi(const std::string alg); + ++/** ++ * @brief Attempt to set hash algorithm using the value provided. ++ * ++ * @param cfg config ++ * @param hash algorithm name. ++ * @return true if algorithm is supported and set correctly, or false otherwise. ++ */ ++bool cli_rnp_set_hash(rnp_cfg &cfg, const std::string &hash); ++ ++/** ++ * @brief Attempt to set symmetric cipher algorithm using the value provided. ++ * ++ * @param cfg config ++ * @param cipher algorithm name. ++ * @return true if algorithm is supported and set correctly, or false otherwise. ++ */ ++bool cli_rnp_set_cipher(rnp_cfg &cfg, const std::string &cipher); ++ + void clear_key_handles(std::vector &keys); + + const char *json_obj_get_str(json_object *obj, const char *key); +-int64_t json_obj_get_int64(json_object *obj, const char *key); +-bool rnp_casecmp(const std::string &str1, const std::string &str2); + + #ifdef _WIN32 + bool rnp_win_substitute_cmdline_args(int *argc, char ***argv); + void rnp_win_clear_args(int argc, char **argv); + #endif +diff --git a/comm/third_party/rnp/src/rnp/rnp.1.adoc b/comm/third_party/rnp/src/rnp/rnp.1.adoc +--- a/comm/third_party/rnp/src/rnp/rnp.1.adoc ++++ b/comm/third_party/rnp/src/rnp/rnp.1.adoc +@@ -33,13 +33,15 @@ There are some special cases for _INPUT_ + * env:VARIABLE_NAME substitutes to the contents of environment variable VARIABLE_NAME + + Depending on the input, output may be written: + + * if *--output* option is given output is written to the path specified (or to the *stdout* if *-* is used) +-* to the _INPUT_FILE_ with a removed or added file extension (_.pgp_, _.asc_, _.sig_); or ++* to the _INPUT_FILE_ with a removed or added file extension (_.pgp_, _.gpg_, _.asc_, _.sig_), depending on operation. + * to the _stdout_ if input was read from the _stdin_. + ++If output file already exists, it will *not* be overwritten, unless *--overwrite* option is given. ++ + Without the *--armor* option, output will be in binary. + + If _COMMAND_ requires public or private keys, *rnp* will look for the keyrings in *~/.rnp*. The options *--homedir* and *--keyfile* override this (see below). + + If _COMMAND_ needs a password, *rnp* will ask for it via *stdin* or *tty*, +@@ -76,10 +78,14 @@ Select a specific cipher. + Select a compression algorithm and level. + + *--armor*::: + Output ASCII data instead of binary via the *--armor* option. If the input file is _file.ext_, and *--output* is not specified, then the data will be written (depending on *--armor* option) to _file.ext.pgp_ or _file.ext.asc_. + + ++*--no-wrap*::: ++Do not wrap the output in literal data packet. This could be used to encrypt a file which is already signed or encrypted. ++By default this would also disable compression, use option *-z* to override. ++ + *--overwrite*::: + If the destination file already exists, and the *--overwrite* option is not given, the caller will be asked for the permission to overwrite or to provide a new file name. Please see the *OPTIONS* section for more information. + + *-c*, *--symmetric*:: + Encrypt data with password(s). + +@@ -126,20 +132,22 @@ Decrypt and verify data from the _INPUT_ + If the data is signed, signature verification information will be printed to _stdout_/_tty_. + + + Additional options: + + *--output*::: +-Output, if not overridden with this option, will be written to the file with stripped _.pgp_ extension or stdout. If _INPUT_FILE_ does not end with the _.pgp_ extension, then output file name will be asked via _stdin_/_tty_. ++Override the default output selection with a file name or stdout specifier (*_-_*). For the default output path selection see the *BASICS* section. + + *--password*, *--pass-fd*::: + Depending on encryption options, you may be asked for the password of one of your secret keys, or for the encryption password. These options override that behavior such that you can input the password through automated means. + + *-v*, *--verify*:: + Verify signature(s) without writing embedded data out, if any (unless option _--output_ is specified). + + + + To verify the detached signature of a file _file.ext_, the detached signature file in the file name pattern of _file.ext.sig_ or _file.ext.asc_ must exist. + + + ++Also you may use option *--source* to specify the exact source for the signed data. + +++ + If data is encrypted, you may be asked for password as in the *--decrypt* command. + + === OTHER COMMANDS + + *--list-packets*:: +@@ -230,10 +238,13 @@ the input filename/extension or the comm + via _stdin_/_tty_. + + *--overwrite*:: + Overwrite already existing files without prompt. + ++*--source*:: ++Specify signed data for the detached signature verification (_-_ and _env:_ substitutions may be used here). + ++ + *--hash* _ALGORITHM_:: + Set hash algorithm which to be used for signing and derivation + of the encryption key from a password. + + + + The default value is _SHA256_. +@@ -245,11 +256,11 @@ The default value is _AES256_. + + *--aead* [_EAX_, _OCB_]:: + Enable AEAD encryption and select algorithm to be used. + + *--aead-chunk-bits* _BITS_:: +-Change AEAD chunk size. This is used for testing or debugging. ++Change AEAD chunk size bits, from 0 to 16 (actual chunk size would be 1 << (6 + bits)). See OpenPGP documentation for the details. + + + *--zip*, *--zlib*, *--bzip2*:: + Select corresponding algorithm to compress data with. + Please refer to IETF RFC 4880 for details. + +@@ -266,26 +277,24 @@ Specify a file descriptor to read passwo + Useful for automated or non-interactive sessions. + + *--password* _PASSWORD_:: + Use the specified password when it is needed. + + + +-WARNING: Not recommended for production use due to potential security issues. ++WARNING: Not recommended for production use due to potential security issues. + Use *--pass-fd* for batch operations instead. + + *--passwords* _COUNT_:: + Set the number of passwords for *--symmetric* encryption. + + + + While not commonly used, you may encrypt a message to any reasonable number of passwords. + + *--creation* _TIME_:: + Override signature creation time. + + + +-By default, creation time is set to current local computer time. + ++By default, creation time is set to the current local computer time. + + + +-A specific time could be specified in the +-ISO 8601-1:2019 date format (_yyyy-mm-dd_), +-or in the UNIX timestamp format. ++*TIME* could be specified in the ISO 8601-1:2019 date format (_yyyy-mm-dd_), or in the UNIX timestamp format. + + *--expiration* _TIME_:: + Set signature expiration time, counting from the creation time. + + + + By default, signatures do not expire. + +@@ -308,10 +317,17 @@ Disable use of tty. + + + + By default RNP would detect whether TTY is attached and use it for user prompts. + + + + This option overrides default behaviour so user input may be passed in batch mode. + ++*--current-time* _TIME_:: ++Override system's time with a specified value. + +++ ++By default RNP uses system's time in all signature/key checks, however in some scenarios it could be needed to override this. + +++ ++*TIME* may be specified in the same way as *--creation*. ++ + == EXIT STATUS + + _0_:: + Success. + +diff --git a/comm/third_party/rnp/src/rnp/rnp.cpp b/comm/third_party/rnp/src/rnp/rnp.cpp +--- a/comm/third_party/rnp/src/rnp/rnp.cpp ++++ b/comm/third_party/rnp/src/rnp/rnp.cpp +@@ -47,14 +47,15 @@ + #include + #include + #include + + #include "fficli.h" ++#include "str-utils.h" + #include "logging.h" + + static const char *usage = +- "Sign, verify, encrypt, decrypt, investigate OpenPGP data.\n" ++ "Sign, verify, encrypt, decrypt, inspect OpenPGP data.\n" + "Usage: rnp --command [options] [files]\n" + "Commands:\n" + " -h, --help This help message.\n" + " -V, --version Print RNP version information.\n" + " -e, --encrypt Encrypt data using the public key(s).\n" +@@ -62,19 +63,21 @@ static const char *usage = + " --cipher name Specify symmetric cipher, used for encryption.\n" + " --aead[=EAX, OCB] Use AEAD for encryption.\n" + " -z 0..9 Set the compression level.\n" + " --[zip,zlib,bzip] Use the corresponding compression algorithm.\n" + " --armor Apply ASCII armor to the encryption/signing output.\n" ++ " --no-wrap Do not wrap the output in a literal data packet.\n" + " -c, --symmetric Encrypt data using the password(s).\n" + " --passwords num Encrypt to the specified number of passwords.\n" + " -s, --sign Sign data. May be combined with encryption.\n" + " --detach Produce detached signature.\n" + " -u, --userid Specify signing key(s) via uid/keyid/fingerprint.\n" + " --hash Specify hash algorithm, used during signing.\n" + " --clearsign Cleartext-sign data.\n" + " -d, --decrypt Decrypt and output data, verifying signatures.\n" + " -v, --verify Verify signatures, without outputting data.\n" ++ " --source Specify source for the detached signature.\n" + " --dearmor Strip ASCII armor from the data, outputting binary.\n" + " --enarmor Add ASCII armor to the data.\n" + " --list-packets List OpenPGP packets from the input.\n" + " --json Use JSON output instead of human-readable.\n" + " --grips Dump key fingerprints and grips.\n" +@@ -87,10 +90,11 @@ static const char *usage = + " --output [file, -] Write data to the specified file or stdout.\n" + " --overwrite Overwrite output file without a prompt.\n" + " --password Password used during operation.\n" + " --pass-fd num Read password(s) from the file descriptor.\n" + " --notty Do not output anything to the TTY.\n" ++ " --current-time Override system's time.\n" + "\n" + "See man page for a detailed listing and explanation.\n" + "\n"; + + enum optdefs { +@@ -141,10 +145,13 @@ enum optdefs { + OPT_JSON, + OPT_GRIPS, + OPT_MPIS, + OPT_RAW, + OPT_NOTTY, ++ OPT_SOURCE, ++ OPT_NOWRAP, ++ OPT_CURTIME, + + /* debug */ + OPT_DEBUG + }; + +@@ -201,10 +208,13 @@ static struct option options[] = { + {"json", no_argument, NULL, OPT_JSON}, + {"grips", no_argument, NULL, OPT_GRIPS}, + {"mpi", no_argument, NULL, OPT_MPIS}, + {"raw", no_argument, NULL, OPT_RAW}, + {"notty", no_argument, NULL, OPT_NOTTY}, ++ {"source", required_argument, NULL, OPT_SOURCE}, ++ {"no-wrap", no_argument, NULL, OPT_NOWRAP}, ++ {"current-time", required_argument, NULL, OPT_CURTIME}, + + {NULL, 0, NULL, 0}, + }; + + /* print a usage message */ +@@ -298,26 +308,22 @@ setcmd(rnp_cfg &cfg, int cmd, const char + break; + case CMD_DEARMOR: + cfg.set_bool(CFG_KEYSTORE_DISABLED, true); + break; + case CMD_ENARMOR: { +- std::string msgt = ""; +- +- if (arg) { +- msgt = arg; +- if (msgt == "msg") { +- msgt = "message"; +- } else if (msgt == "pubkey") { +- msgt = "public key"; +- } else if (msgt == "seckey") { +- msgt = "secret key"; +- } else if (msgt == "sign") { +- msgt = "signature"; +- } else { +- ERR_MSG("Wrong enarmor argument: %s", arg); +- return false; +- } ++ std::string msgt = arg; ++ if (msgt == "msg") { ++ msgt = "message"; ++ } else if (msgt == "pubkey") { ++ msgt = "public key"; ++ } else if (msgt == "seckey") { ++ msgt = "secret key"; ++ } else if (msgt == "sign") { ++ msgt = "signature"; ++ } else { ++ ERR_MSG("Wrong enarmor argument: %s", arg); ++ return false; + } + + if (!msgt.empty()) { + cfg.set_str(CFG_ARMOR_DATA_TYPE, msgt); + } +@@ -361,130 +367,64 @@ setoption(rnp_cfg &cfg, int val, const c + ERR_MSG("warning: --coredumps doesn't make sense on windows systems."); + #endif + cfg.set_bool(CFG_COREDUMPS, true); + return true; + case OPT_KEY_STORE_FORMAT: +- if (!arg) { +- ERR_MSG("No keyring format argument provided"); +- return false; +- } + cfg.set_str(CFG_KEYSTOREFMT, arg); + return true; + case OPT_USERID: +- if (!arg) { +- ERR_MSG("No userid argument provided"); +- return false; +- } + cfg.add_str(CFG_SIGNERS, arg); + return true; + case OPT_RECIPIENT: +- if (!arg) { +- ERR_MSG("No recipient argument provided"); +- return false; +- } + cfg.add_str(CFG_RECIPIENTS, arg); + return true; + case OPT_ARMOR: + cfg.set_bool(CFG_ARMOR, true); + return true; + case OPT_DETACHED: + cfg.set_bool(CFG_DETACHED, true); + return true; + case OPT_HOMEDIR: +- if (!arg) { +- ERR_MSG("No home directory argument provided"); +- return false; +- } + cfg.set_str(CFG_HOMEDIR, arg); + return true; + case OPT_KEYFILE: +- if (!arg) { +- ERR_MSG("No keyfile argument provided"); +- return false; +- } + cfg.set_str(CFG_KEYFILE, arg); + cfg.set_bool(CFG_KEYSTORE_DISABLED, true); + return true; +- case OPT_HASH_ALG: { +- if (!arg) { +- ERR_MSG("No hash algorithm argument provided"); +- return false; +- } +- bool supported = false; +- const std::string &alg = cli_rnp_alg_to_ffi(arg); +- if (rnp_supports_feature(RNP_FEATURE_HASH_ALG, alg.c_str(), &supported) || +- !supported) { +- ERR_MSG("Unsupported hash algorithm: %s", arg); +- return false; +- } +- cfg.set_str(CFG_HASH, alg); +- return true; +- } ++ case OPT_HASH_ALG: ++ return cli_rnp_set_hash(cfg, arg); + case OPT_PASSWDFD: +- if (!arg) { +- ERR_MSG("No pass-fd argument provided"); +- return false; +- } + cfg.set_str(CFG_PASSFD, arg); + return true; + case OPT_PASSWD: +- if (!arg) { +- ERR_MSG("No password argument provided"); +- return false; +- } + cfg.set_str(CFG_PASSWD, arg); + return true; + case OPT_PASSWORDS: { +- if (!arg) { +- ERR_MSG("You must provide a number with --passwords option"); +- return false; +- } +- int count = atoi(arg); +- if (count <= 0) { ++ int count = 0; ++ if (!rnp::str_to_int(arg, count) || (count <= 0)) { + ERR_MSG("Incorrect value for --passwords option: %s", arg); + return false; + } + + cfg.set_int(CFG_PASSWORDC, count); + cfg.set_bool(CFG_ENCRYPT_SK, true); + return true; + } + case OPT_OUTPUT: +- if (!arg) { +- ERR_MSG("No output filename argument provided"); +- return false; +- } + cfg.set_str(CFG_OUTFILE, arg); + return true; + case OPT_RESULTS: +- if (!arg) { +- ERR_MSG("No output filename argument provided"); +- return false; +- } + cfg.set_str(CFG_RESULTS, arg); + return true; + case OPT_EXPIRATION: + cfg.set_str(CFG_EXPIRATION, arg); + return true; + case OPT_CREATION: + cfg.set_str(CFG_CREATION, arg); + return true; +- case OPT_CIPHER: { +- if (!arg) { +- ERR_MSG("No encryption algorithm argument provided"); +- return false; +- } +- bool supported = false; +- const std::string &alg = cli_rnp_alg_to_ffi(arg); +- if (rnp_supports_feature(RNP_FEATURE_SYMM_ALG, alg.c_str(), &supported) || +- !supported) { +- ERR_MSG("Unsupported encryption algorithm: %s", arg); +- return false; +- } +- cfg.set_str(CFG_CIPHER, alg); +- return true; +- } ++ case OPT_CIPHER: ++ return cli_rnp_set_cipher(cfg, arg); + case OPT_NUMTRIES: + cfg.set_str(CFG_NUMTRIES, arg); + return true; + case OPT_ZALG_ZIP: + cfg.set_str(CFG_ZALG, "ZIP"); +@@ -494,32 +434,26 @@ setoption(rnp_cfg &cfg, int val, const c + return true; + case OPT_ZALG_BZIP: + cfg.set_str(CFG_ZALG, "BZip2"); + return true; + case OPT_AEAD: { +- const char *alg = NULL; + std::string argstr = arg ? arg : ""; +- if (argstr.empty() || (argstr == "1") || rnp_casecmp(argstr, "eax")) { +- alg = "EAX"; +- } else if ((argstr == "2") || rnp_casecmp(argstr, "ocb")) { +- alg = "OCB"; ++ if (argstr.empty() || (argstr == "1") || rnp::str_case_eq(argstr, "eax")) { ++ argstr = "EAX"; ++ } else if ((argstr == "2") || rnp::str_case_eq(argstr, "ocb")) { ++ argstr = "OCB"; + } else { +- ERR_MSG("Wrong AEAD algorithm: %s", arg); ++ ERR_MSG("Wrong AEAD algorithm: %s", argstr.c_str()); + return false; + } +- cfg.set_str(CFG_AEAD, alg); ++ cfg.set_str(CFG_AEAD, argstr); + return true; + } + case OPT_AEAD_CHUNK: { +- if (!arg) { +- ERR_MSG("Option aead-chunk-bits requires parameter"); +- return false; +- } +- +- int bits = atoi(arg); +- if ((bits < 0) || (bits > 56)) { +- ERR_MSG("Wrong argument value %s for aead-chunk-bits", arg); ++ int bits = 0; ++ if (!rnp::str_to_int(arg, bits) || (bits < 0) || (bits > 16)) { ++ ERR_MSG("Wrong argument value %s for aead-chunk-bits, must be 0..16.", arg); + return false; + } + cfg.set_int(CFG_AEAD_CHUNK, bits); + return true; + } +@@ -539,10 +473,20 @@ setoption(rnp_cfg &cfg, int val, const c + cfg.set_bool(CFG_RAW, true); + return true; + case OPT_NOTTY: + cfg.set_bool(CFG_NOTTY, true); + return true; ++ case OPT_SOURCE: ++ cfg.set_str(CFG_SOURCE, arg); ++ return true; ++ case OPT_NOWRAP: ++ cfg.set_bool(CFG_NOWRAP, true); ++ cfg.set_int(CFG_ZLEVEL, 0); ++ return true; ++ case OPT_CURTIME: ++ cfg.set_str(CFG_CURTIME, arg); ++ return true; + case OPT_DEBUG: + ERR_MSG("Option --debug is deprecated, ignoring."); + return true; + default: + return setcmd(cfg, CMD_HELP, arg); +@@ -612,11 +556,11 @@ rnp_main(int argc, char **argv) + break; + case 'v': + cmd = CMD_VERIFY; + break; + case 'r': +- if (strlen(optarg) < 1) { ++ if (!strlen(optarg)) { + ERR_MSG("Recipient should not be empty"); + } else { + cfg.add_str(CFG_RECIPIENTS, optarg); + } + break; +diff --git a/comm/third_party/rnp/src/rnp/rnpcfg.cpp b/comm/third_party/rnp/src/rnp/rnpcfg.cpp +--- a/comm/third_party/rnp/src/rnp/rnpcfg.cpp ++++ b/comm/third_party/rnp/src/rnp/rnpcfg.cpp +@@ -36,10 +36,11 @@ + #endif + #include + #include + #include + #include ++#include + + #include "rnpcfg.h" + #include "defaults.h" + #include "utils.h" + #include "time-utils.h" +@@ -318,11 +319,11 @@ rnp_cfg::get_expiration(const std::strin + } + const std::string &val = get_str(key); + uint64_t delta; + uint64_t t; + if (parse_date(val, t)) { +- uint64_t now = time(NULL); ++ uint64_t now = time(); + if (t > now) { + delta = t - now; + if (delta > UINT32_MAX) { + RNP_LOG("Expiration time exceeds 32-bit value"); + return false; +@@ -390,29 +391,47 @@ rnp_cfg::get_expiration(const std::strin + } + seconds = delta; + return true; + } + ++bool ++rnp_cfg::extract_timestamp(const std::string &st, uint64_t &t) const ++{ ++ if (st.empty()) { ++ return false; ++ } ++ if (parse_date(st, t)) { ++ return true; ++ } ++ /* Check if string is UNIX timestamp */ ++ for (auto c : st) { ++ if (!isdigit(c)) { ++ return false; ++ } ++ } ++ t = std::stoll(st); ++ return true; ++} ++ + uint64_t + rnp_cfg::get_sig_creation() const + { +- if (!has(CFG_CREATION)) { +- return time(NULL); +- } +- const std::string &cr = get_str(CFG_CREATION); +- /* Check if string is date */ +- uint64_t t; +- if (parse_date(cr, t)) { ++ uint64_t t = 0; ++ if (extract_timestamp(get_str(CFG_CREATION), t)) { + return t; + } +- /* Check if string is UNIX timestamp */ +- for (auto c : cr) { +- if (!isdigit(c)) { +- return time(NULL); +- } ++ return time(); ++} ++ ++uint64_t ++rnp_cfg::time() const ++{ ++ uint64_t t = 0; ++ if (extract_timestamp(get_str(CFG_CURTIME), t)) { ++ return t; + } +- return std::stoll(cr); ++ return ::time(NULL); + } + + void + rnp_cfg::copy(const rnp_cfg &src) + { +@@ -490,11 +509,11 @@ days_in_month(int year, int month) + + bool + rnp_cfg::parse_date(const std::string &s, uint64_t &t) const + { + /* fill time zone information */ +- const time_t now = time(NULL); ++ const time_t now = ::time(NULL); + struct tm tm = *localtime(&now); + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + const char *reg = "^([0-9]{4})[-/\\.]([0-9]{2})[-/\\.]([0-9]{2})$"; +diff --git a/comm/third_party/rnp/src/rnp/rnpcfg.h b/comm/third_party/rnp/src/rnp/rnpcfg.h +--- a/comm/third_party/rnp/src/rnp/rnpcfg.h ++++ b/comm/third_party/rnp/src/rnp/rnpcfg.h +@@ -85,10 +85,13 @@ + #define CFG_REV_REASON "rev-reason" /* revocation reason human-readable string */ + #define CFG_PERMISSIVE "permissive" /* ignore bad packets during key import */ + #define CFG_NOTTY "notty" /* disable tty usage and do input/output via stdin/stdout */ + #define CFG_FIX_25519_BITS "fix-25519-bits" /* fix Cv25519 secret key via --edit-key */ + #define CFG_CHK_25519_BITS "check-25519-bits" /* check Cv25519 secret key bits */ ++#define CFG_SOURCE "source" /* source for the detached signature */ ++#define CFG_NOWRAP "no-wrap" /* do not wrap the output in a literal data packet */ ++#define CFG_CURTIME "curtime" /* date or timestamp to override the system's time */ + + /* rnp keyring setup variables */ + #define CFG_KR_PUB_FORMAT "kr-pub-format" + #define CFG_KR_SEC_FORMAT "kr-sec-format" + #define CFG_KR_PUB_PATH "kr-pub-path" +@@ -124,10 +127,11 @@ class rnp_cfg { + * @param s string with the date + * @param t UNIX timestamp of successfully parsed date + * @return true when parsed successfully or false otherwise + */ + bool parse_date(const std::string &s, uint64_t &t) const; ++ bool extract_timestamp(const std::string &st, uint64_t &t) const; + + public: + /** @brief load default settings */ + void load_defaults(); + /** @brief set string value for the key in config */ +@@ -173,11 +177,11 @@ class rnp_cfg { + * Expiration may be specified in different formats: + * - 10d : 10 days (you can use [h]ours, d[ays], [w]eeks, [m]onthes) + * - 2017-07-12 : as the exact date + * - 60000 : number of seconds + * +- * @param seconds On successfull return result will be placed here ++ * @param seconds On successful return result will be placed here + * @return true on success or false otherwise + */ + bool get_expiration(const std::string &key, uint32_t &seconds) const; + + /** @brief Get signature creation time from the config. +@@ -186,10 +190,17 @@ class rnp_cfg { + * - 1499334073 : timestamp + * + * @return timestamp of the signature creation. + */ + uint64_t get_sig_creation() const; ++ ++ /** @brief Get current time from the config. ++ * ++ * @return timestamp which should be considered as current time. ++ */ ++ uint64_t time() const; ++ + /** @brief copy or override a configuration. + * @param src vals will be overridden (if key exist) or copied (if not) from this object + */ + void copy(const rnp_cfg &src); + void clear(); +diff --git a/comm/third_party/rnp/src/rnpkeys/main.cpp b/comm/third_party/rnp/src/rnpkeys/main.cpp +--- a/comm/third_party/rnp/src/rnpkeys/main.cpp ++++ b/comm/third_party/rnp/src/rnpkeys/main.cpp +@@ -74,11 +74,11 @@ rnpkeys_main(int argc, char **argv) + + while ((ch = getopt_long(argc, argv, "Vgl", options, &optindex)) != -1) { + if (ch >= CMD_LIST_KEYS) { + /* getopt_long returns 0 for long options */ + if (!setoption(cfg, &cmd, options[optindex].val, optarg)) { +- ERR_MSG("Bad setoption result %d", ch); ++ ERR_MSG("Failed to process argument --%s", options[optindex].name); + goto end; + } + } else { + switch (ch) { + case 'V': +diff --git a/comm/third_party/rnp/src/rnpkeys/rnpkeys.1.adoc b/comm/third_party/rnp/src/rnpkeys/rnpkeys.1.adoc +--- a/comm/third_party/rnp/src/rnpkeys/rnpkeys.1.adoc ++++ b/comm/third_party/rnp/src/rnpkeys/rnpkeys.1.adoc +@@ -369,10 +369,17 @@ Disable use of tty. + + + + By default RNP would detect whether TTY is attached and use it for user prompts. + + + + This option overrides default behaviour so user input may be passed in batch mode. + ++*--current-time* _TIME_:: ++Override system's time with a specified value. + +++ ++By default RNP uses system's time in all signature/key checks, however in some scenarios it could be needed to override this. + +++ ++*TIME* could be specified in the ISO 8601-1:2019 date format (_yyyy-mm-dd_), or in the UNIX timestamp format. ++ + == EXIT STATUS + + _0_:: + Success. + +@@ -391,10 +398,17 @@ Following oneliner may be used to import + + To import all secret keys the following command should be used (please note, that you'll be asked for secret key password(s)): + + *gpg* *-a* *--export-secret-keys* | *rnpkeys* *--import* _-_ + ++=== EXAMPLE 2: GENERATE A NEW KEY ++ ++This example generates a new key with specified userid and expiration. ++Also it enables "expert" mode, allowing the selection of key/subkey algorithms. ++ ++*rnpkeys* *--generate* *--userid* *"john@doe.com"* *--expert* *--expiration* *1y* ++ + == BUGS + + Please report _issues_ via the RNP public issue tracker at: + https://github.com/rnpgp/rnp/issues. + +diff --git a/comm/third_party/rnp/src/rnpkeys/rnpkeys.cpp b/comm/third_party/rnp/src/rnpkeys/rnpkeys.cpp +--- a/comm/third_party/rnp/src/rnpkeys/rnpkeys.cpp ++++ b/comm/third_party/rnp/src/rnpkeys/rnpkeys.cpp +@@ -36,10 +36,11 @@ + #include + #endif + #include + #include + #include "rnpkeys.h" ++#include "str-utils.h" + + const char *usage = + "Manipulate OpenPGP keys and keyrings.\n" + "Usage: rnpkeys --command [options] [files]\n" + "Commands:\n" +@@ -76,10 +77,11 @@ const char *usage = + " --pass-fd Read password(s) from the file descriptor.\n" + " --force Force operation (like secret key removal).\n" + " --output [file, -] Write data to the specified file or stdout.\n" + " --overwrite Overwrite output file without a prompt.\n" + " --notty Do not write anything to the TTY.\n" ++ " --current-time Override system's time.\n" + "\n" + "See man page for a detailed listing and explanation.\n" + "\n"; + + struct option options[] = { +@@ -129,10 +131,11 @@ struct option options[] = { + {"rev-reason", required_argument, NULL, OPT_REV_REASON}, + {"permissive", no_argument, NULL, OPT_PERMISSIVE}, + {"notty", no_argument, NULL, OPT_NOTTY}, + {"fix-cv25519-bits", no_argument, NULL, OPT_FIX_25519_BITS}, + {"check-cv25519-bits", no_argument, NULL, OPT_CHK_25519_BITS}, ++ {"current-time", required_argument, NULL, OPT_CURTIME}, + {NULL, 0, NULL, 0}, + }; + + /* list keys */ + static bool +@@ -174,12 +177,12 @@ static bool + import_keys(cli_rnp_t *rnp, rnp_input_t input, const std::string &inname) + { + bool res = false; + bool updated = false; + +- uint32_t flags = +- RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_SINGLE; ++ uint32_t flags = RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | ++ RNP_LOAD_SAVE_SINGLE | RNP_LOAD_SAVE_BASE64; + + bool permissive = rnp->cfg().get_bool(CFG_PERMISSIVE); + if (permissive) { + flags |= RNP_LOAD_SAVE_PERMISSIVE; + } +@@ -190,10 +193,16 @@ import_keys(cli_rnp_t *rnp, rnp_input_t + rnp_result_t ret = rnp_import_keys(rnp->ffi, input, flags, &results); + if (ret == RNP_ERROR_EOF) { + res = true; + break; + } ++ if (ret && updated) { ++ /* some keys were imported, but then error occurred */ ++ ERR_MSG("warning: not all data was processed."); ++ res = true; ++ break; ++ } + if (ret) { + ERR_MSG("failed to import key(s) from %s, stopping.", inname.c_str()); + break; + } + +@@ -456,63 +465,30 @@ setoption(rnp_cfg &cfg, optdefs_t *cmd, + case CMD_VERSION: + *cmd = (optdefs_t) val; + return true; + /* options */ + case OPT_KEY_STORE_FORMAT: +- if (!arg) { +- ERR_MSG("No keyring format argument provided"); +- return false; +- } + cfg.set_str(CFG_KEYSTOREFMT, arg); + return true; + case OPT_USERID: +- if (!arg) { +- ERR_MSG("no userid argument provided"); +- return false; +- } + cfg.add_str(CFG_USERID, arg); + return true; + case OPT_HOMEDIR: +- if (!arg) { +- ERR_MSG("no home directory argument provided"); +- return false; +- } + cfg.set_str(CFG_HOMEDIR, arg); + return true; + case OPT_NUMBITS: { +- if (!arg) { +- ERR_MSG("no number of bits argument provided"); +- return false; +- } +- int bits = atoi(arg); +- if ((bits < 1024) || (bits > 16384)) { ++ int bits = 0; ++ if (!rnp::str_to_int(arg, bits) || (bits < 1024) || (bits > 16384)) { + ERR_MSG("wrong bits value: %s", arg); + return false; + } + cfg.set_int(CFG_NUMBITS, bits); + return true; + } +- case OPT_HASH_ALG: { +- if (!arg) { +- ERR_MSG("No hash algorithm argument provided"); +- return false; +- } +- bool supported = false; +- const std::string &alg = cli_rnp_alg_to_ffi(arg); +- if (rnp_supports_feature(RNP_FEATURE_HASH_ALG, alg.c_str(), &supported) || +- !supported) { +- ERR_MSG("Unsupported hash algorithm: %s", arg); +- return false; +- } +- cfg.set_str(CFG_HASH, alg); +- return true; +- } ++ case OPT_HASH_ALG: ++ return cli_rnp_set_hash(cfg, arg); + case OPT_S2K_ITER: { +- if (!arg) { +- ERR_MSG("No s2k iteration argument provided"); +- return false; +- } + int iterations = atoi(arg); + if (!iterations) { + ERR_MSG("Wrong iterations value: %s", arg); + return false; + } +@@ -522,54 +498,29 @@ setoption(rnp_cfg &cfg, optdefs_t *cmd, + case OPT_EXPIRATION: + cfg.set_str(CFG_KG_PRIMARY_EXPIRATION, arg); + cfg.set_str(CFG_KG_SUBKEY_EXPIRATION, arg); + return true; + case OPT_S2K_MSEC: { +- if (!arg) { +- ERR_MSG("No s2k msec argument provided"); +- return false; +- } +- int msec = atoi(arg); +- if (!msec) { ++ int msec = 0; ++ if (!rnp::str_to_int(arg, msec) || !msec) { + ERR_MSG("Invalid s2k msec value: %s", arg); + return false; + } + cfg.set_int(CFG_S2K_MSEC, msec); + return true; + } + case OPT_PASSWDFD: +- if (!arg) { +- ERR_MSG("no pass-fd argument provided"); +- return false; +- } + cfg.set_str(CFG_PASSFD, arg); + return true; + case OPT_PASSWD: +- if (!arg) { +- ERR_MSG("No password argument provided"); +- return false; +- } + cfg.set_str(CFG_PASSWD, arg); + return true; + case OPT_RESULTS: +- if (!arg) { +- ERR_MSG("No output filename argument provided"); +- return false; +- } + cfg.set_str(CFG_IO_RESS, arg); + return true; +- case OPT_CIPHER: { +- bool supported = false; +- const std::string &alg = cli_rnp_alg_to_ffi(arg); +- if (rnp_supports_feature(RNP_FEATURE_SYMM_ALG, alg.c_str(), &supported) || +- !supported) { +- ERR_MSG("Unsupported symmetric algorithm: %s", arg); +- return false; +- } +- cfg.set_str(CFG_CIPHER, alg); +- return true; +- } ++ case OPT_CIPHER: ++ return cli_rnp_set_cipher(cfg, arg); + case OPT_DEBUG: + ERR_MSG("Option --debug is deprecated, ignoring."); + return true; + case OPT_OUTPUT: + if (!arg) { +@@ -589,14 +540,10 @@ setoption(rnp_cfg &cfg, optdefs_t *cmd, + return true; + case OPT_WITH_SIGS: + cfg.set_bool(CFG_WITH_SIGS, true); + return true; + case OPT_REV_TYPE: { +- if (!arg) { +- ERR_MSG("No revocation type argument provided"); +- return false; +- } + std::string revtype = arg; + if (revtype == "0") { + revtype = "no"; + } else if (revtype == "1") { + revtype = "superseded"; +@@ -607,14 +554,10 @@ setoption(rnp_cfg &cfg, optdefs_t *cmd, + } + cfg.set_str(CFG_REV_TYPE, revtype); + return true; + } + case OPT_REV_REASON: +- if (!arg) { +- ERR_MSG("No revocation reason argument provided"); +- return false; +- } + cfg.set_str(CFG_REV_REASON, arg); + return true; + case OPT_PERMISSIVE: + cfg.set_bool(CFG_PERMISSIVE, true); + return true; +@@ -625,10 +568,13 @@ setoption(rnp_cfg &cfg, optdefs_t *cmd, + cfg.set_bool(CFG_FIX_25519_BITS, true); + return true; + case OPT_CHK_25519_BITS: + cfg.set_bool(CFG_CHK_25519_BITS, true); + return true; ++ case OPT_CURTIME: ++ cfg.set_str(CFG_CURTIME, arg); ++ return true; + default: + *cmd = CMD_HELP; + return true; + } + } +diff --git a/comm/third_party/rnp/src/rnpkeys/rnpkeys.h b/comm/third_party/rnp/src/rnpkeys/rnpkeys.h +--- a/comm/third_party/rnp/src/rnpkeys/rnpkeys.h ++++ b/comm/third_party/rnp/src/rnpkeys/rnpkeys.h +@@ -51,10 +51,11 @@ typedef enum { + OPT_REV_REASON, + OPT_PERMISSIVE, + OPT_NOTTY, + OPT_FIX_25519_BITS, + OPT_CHK_25519_BITS, ++ OPT_CURTIME, + + /* debug */ + OPT_DEBUG + } optdefs_t; + +diff --git a/comm/third_party/rnp/src/rnpkeys/tui.cpp b/comm/third_party/rnp/src/rnpkeys/tui.cpp +--- a/comm/third_party/rnp/src/rnpkeys/tui.cpp ++++ b/comm/third_party/rnp/src/rnpkeys/tui.cpp +@@ -33,10 +33,11 @@ + #include + #include + #include "rnp/rnpcfg.h" + #include "rnpkeys.h" + #include "defaults.h" ++#include "file-utils.h" + #include "logging.h" + + /* ----------------------------------------------------------------------------- + * @brief Reads input from file pointer and converts it securelly to ints + * Partially based on ERR34-C from SEI CERT C Coding Standard +@@ -268,11 +269,11 @@ cli_rnp_set_generate_params(rnp_cfg &cfg + } else { + FILE *input = stdin; + if (cfg.has(CFG_USERINPUTFD)) { + int inputfd = dup(cfg.get_int(CFG_USERINPUTFD)); + if (inputfd != -1) { +- input = fdopen(inputfd, "r"); ++ input = rnp_fdopen(inputfd, "r"); + if (!input) { + close(inputfd); + } + } + } +diff --git a/comm/third_party/rnp/src/tests/CMakeLists.txt b/comm/third_party/rnp/src/tests/CMakeLists.txt +--- a/comm/third_party/rnp/src/tests/CMakeLists.txt ++++ b/comm/third_party/rnp/src/tests/CMakeLists.txt +@@ -45,19 +45,24 @@ if (GTEST_SOURCES) + elseif (NOT DOWNLOAD_GTEST) + # use preinstalled googletest + find_package(GTest REQUIRED) + set(GTestMain GTest::Main) + else() ++ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS_EQUAL "4.8.5") ++ set(GTEST_GIT_TAG "c43f710") ++ else() ++ set(GTEST_GIT_TAG "HEAD") ++ endif() + # download and build googletest + include(FetchContent) + FetchContent_Declare(googletest + GIT_REPOSITORY https://github.com/google/googletest.git +- GIT_TAG c43f710 ++ GIT_TAG "${GTEST_GIT_TAG}" + ) + # maintain compiler/linker settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +- # explicitely disable unneeded gmock build ++ # explicitly disable unneeded gmock build + set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + set(GTestMain gtest_main) + endif() + +@@ -91,10 +96,11 @@ add_executable(rnp_tests + ffi.cpp + ffi-enc.cpp + ffi-uid.cpp + ffi-key-sig.cpp + ffi-key-prop.cpp ++ ffi-key.cpp + file-utils.cpp + generatekey.cpp + kbx-nsigs-test.cpp + key-add-userid.cpp + key-grip.cpp +@@ -183,10 +189,16 @@ endif() + target_compile_definitions(rnp_tests + PRIVATE + RNP_RUN_TESTS + RNP_STATIC + ) ++ ++# Centos 7 with CLang 7.0.1 reports strange memory leak in GoogleTest, maybe there is a better solution ++if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0.1) ++ set_target_properties(rnp_tests PROPERTIES CXX_VISIBILITY_PRESET hidden) ++endif() ++ + gtest_discover_tests(rnp_tests + PROPERTIES + FIXTURES_REQUIRED testdata + TIMEOUT 3000 + ENVIRONMENT "RNP_TEST_DATA=${CMAKE_CURRENT_SOURCE_DIR}/data" +@@ -210,11 +222,11 @@ if (DOWNLOAD_RUBYRNP AND NOT ENABLE_SANI + GIT_SHALLOW yes + SOURCE_DIR "${_sourcedir}" + BUILD_IN_SOURCE yes + CONFIGURE_COMMAND "" + BUILD_COMMAND +- COMMAND bundle add ffi --version 1.9.25 ++ COMMAND bundle add ffi --version 1.15.5 + COMMAND bundle show parallel_tests || bundle add parallel_tests + COMMAND bundle install --path . + INSTALL_COMMAND "" + TEST_COMMAND "" + ) +diff --git a/comm/third_party/rnp/src/tests/cipher.cpp b/comm/third_party/rnp/src/tests/cipher.cpp +--- a/comm/third_party/rnp/src/tests/cipher.cpp ++++ b/comm/third_party/rnp/src/tests/cipher.cpp +@@ -1,7 +1,7 @@ + /* +- * Copyright (c) 2017-2019 [Ribose Inc](https://www.ribose.com). ++ * Copyright (c) 2017-2022 [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: + * +@@ -62,29 +62,29 @@ TEST_F(rnp_tests, hash_test_success) + "4FC1A836BA3C2" + "3A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F", + "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7", + "66C7F0F462EEEDD9D1F2D46BDC10E4E24167C4875CF2F7A2297DA02B8F4BA8E0", + "3A985DA74FE225B2045C172D6BD390BD855F086E3E9D525B46BFE24511431532", +- "B751850B1A57168A5693CD924B6B096E08F621827444F70D884F5D0240D2712E1" +- "0E116E9192AF3C91A7EC57647E3934057340B4CF408D5A56592F8274EEC53F0"}; ++ ("B751850B1A57168A5693CD924B6B096E08F621827444F70D884F5D0240D2712E1" ++ "0E116E9192AF3C91A7EC57647E3934057340B4CF408D5A56592F8274EEC53F0")}; + + for (int i = 0; hash_algs[i] != PGP_HASH_UNKNOWN; ++i) { + #if !defined(ENABLE_SM2) + if (hash_algs[i] == PGP_HASH_SM3) { +- assert_throw({ rnp::Hash hash(hash_algs[i]); }); ++ assert_throw({ auto hash = rnp::Hash::create(hash_algs[i]); }); + size_t hash_size = rnp::Hash::size(hash_algs[i]); + assert_int_equal(hash_size * 2, strlen(hash_alg_expected_outputs[i])); + continue; + } + #endif +- rnp::Hash hash(hash_algs[i]); +- size_t hash_size = rnp::Hash::size(hash_algs[i]); ++ auto hash = rnp::Hash::create(hash_algs[i]); ++ size_t hash_size = rnp::Hash::size(hash_algs[i]); + assert_int_equal(hash_size * 2, strlen(hash_alg_expected_outputs[i])); + +- hash.add(test_input, 1); +- hash.add(test_input + 1, sizeof(test_input) - 1); +- hash.finish(hash_output); ++ hash->add(test_input, 1); ++ hash->add(test_input + 1, sizeof(test_input) - 1); ++ hash->finish(hash_output); + + assert_int_equal(0, + test_value_equal(rnp::Hash::name(hash_algs[i]), + hash_alg_expected_outputs[i], + hash_output, +@@ -345,11 +345,11 @@ TEST_F(rnp_tests, ecdh_decryptionNegativ + pgp_ecdh_encrypted_t enc; + + rnp_keygen_crypto_params_t key_desc; + key_desc.key_alg = PGP_PKA_ECDH; + key_desc.hash_alg = PGP_HASH_SHA512; +- key_desc.ecc = {.curve = PGP_CURVE_NIST_P_256}; ++ key_desc.ecc.curve = PGP_CURVE_NIST_P_256; + key_desc.ctx = &global_ctx; + + pgp_key_pkt_t ecdh_key1; + assert_true(pgp_generate_seckey(key_desc, ecdh_key1, true)); + +@@ -396,11 +396,11 @@ TEST_F(rnp_tests, sm2_roundtrip) + size_t decrypted_size; + + rnp_keygen_crypto_params_t key_desc; + key_desc.key_alg = PGP_PKA_SM2; + key_desc.hash_alg = PGP_HASH_SM3; +- key_desc.ecc = {.curve = PGP_CURVE_SM2_P_256}; ++ key_desc.ecc.curve = PGP_CURVE_SM2_P_256; + key_desc.ctx = &global_ctx; + + global_ctx.rng.get(key, sizeof(key)); + + pgp_key_pkt_t seckey; +@@ -448,15 +448,15 @@ TEST_F(rnp_tests, sm2_sm3_signature_test + "c82f49ee0a5b11df22cb0c3c6d9d5526d9e24d02ff8c83c06a859c26565f1"); + hex2mpi(&sm2_key.x, "110E7973206F68C19EE5F7328C036F26911C8C73B4E4F36AE3291097F8984FFC"); + + assert_int_equal(sm2_validate_key(&global_ctx.rng, &sm2_key, true), RNP_SUCCESS); + +- rnp::Hash hash(hash_alg); ++ auto hash = rnp::Hash::create(hash_alg); + +- assert_int_equal(sm2_compute_za(sm2_key, hash, "sm2_p256_test@example.com"), RNP_SUCCESS); +- hash.add(msg, strlen(msg)); +- assert_int_equal(hash.finish(digest), hash_len); ++ assert_int_equal(sm2_compute_za(sm2_key, *hash, "sm2_p256_test@example.com"), RNP_SUCCESS); ++ hash->add(msg, strlen(msg)); ++ assert_int_equal(hash->finish(digest), hash_len); + + // First generate a signature, then verify it + assert_int_equal(sm2_sign(&global_ctx.rng, &sig, hash_alg, digest, hash_len, &sm2_key), + RNP_SUCCESS); + assert_int_equal(sm2_verify(&sig, hash_alg, digest, hash_len, &sm2_key), RNP_SUCCESS); +@@ -491,14 +491,14 @@ TEST_F(rnp_tests, sm2_sha256_signature_t + "05e6213eee145b748e36e274e5f101dc10d7bbc9dab9a04022e73b76e02cd"); + hex2mpi(&sm2_key.x, "110E7973206F68C19EE5F7328C036F26911C8C73B4E4F36AE3291097F8984FFC"); + + assert_int_equal(sm2_validate_key(&global_ctx.rng, &sm2_key, true), RNP_SUCCESS); + +- rnp::Hash hash(hash_alg); +- assert_int_equal(sm2_compute_za(sm2_key, hash, "sm2test@example.com"), RNP_SUCCESS); +- hash.add(msg, strlen(msg)); +- assert_int_equal(hash.finish(digest), hash_len); ++ auto hash = rnp::Hash::create(hash_alg); ++ assert_int_equal(sm2_compute_za(sm2_key, *hash, "sm2test@example.com"), RNP_SUCCESS); ++ hash->add(msg, strlen(msg)); ++ assert_int_equal(hash->finish(digest), hash_len); + + // First generate a signature, then verify it + assert_int_equal(sm2_sign(&global_ctx.rng, &sig, hash_alg, digest, hash_len, &sm2_key), + RNP_SUCCESS); + assert_int_equal(sm2_verify(&sig, hash_alg, digest, hash_len, &sm2_key), RNP_SUCCESS); +@@ -637,10 +637,16 @@ TEST_F(rnp_tests, s2k_iteration_tuning) + assert_greater_than(ratio, S2K_MINIMUM_TUNING_RATIO); + + // Should not crash for unknown hash algorithm + assert_int_equal(pgp_s2k_compute_iters(PGP_HASH_UNKNOWN, 1000, TRIAL_MSEC), 0); + /// TODO test that hashing iters_xx data takes roughly requested time ++ ++ size_t iter_sha1 = global_ctx.s2k_iterations(PGP_HASH_SHA1); ++ assert_int_equal(iter_sha1, global_ctx.s2k_iterations(PGP_HASH_SHA1)); ++ size_t iter_sha512 = global_ctx.s2k_iterations(PGP_HASH_SHA512); ++ assert_int_equal(iter_sha512, global_ctx.s2k_iterations(PGP_HASH_SHA512)); ++ assert_int_equal(global_ctx.s2k_iterations(PGP_HASH_UNKNOWN), 0); + } + + TEST_F(rnp_tests, s2k_iteration_encode_decode) + { + const size_t MAX_ITER = 0x3e00000; // 0x1F << (0xF + 6); +@@ -933,10 +939,34 @@ TEST_F(rnp_tests, test_aead_enabled) + assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "OCB", &supported)); + assert_false(supported); + #endif + } + ++TEST_F(rnp_tests, test_idea_enabled) ++{ ++ char *features = NULL; ++ bool supported = false; ++ /* check whether FFI returns value which corresponds to defines */ ++#if defined(ENABLE_IDEA) ++ assert_true(idea_enabled()); ++ assert_rnp_success(rnp_supported_features(RNP_FEATURE_SYMM_ALG, &features)); ++ assert_non_null(features); ++ assert_true(std::string(features).find("IDEA") != std::string::npos); ++ rnp_buffer_destroy(features); ++ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "IDEA", &supported)); ++ assert_true(supported); ++#else ++ assert_false(idea_enabled()); ++ assert_rnp_success(rnp_supported_features(RNP_FEATURE_SYMM_ALG, &features)); ++ assert_non_null(features); ++ assert_true(std::string(features).find("IDEA") == std::string::npos); ++ rnp_buffer_destroy(features); ++ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "IDEA", &supported)); ++ assert_false(supported); ++#endif ++} ++ + TEST_F(rnp_tests, test_twofish_enabled) + { + char *features = NULL; + bool supported = false; + /* check whether FFI returns value which corresponds to defines */ +diff --git a/comm/third_party/rnp/src/tests/cipher_cxx.cpp b/comm/third_party/rnp/src/tests/cipher_cxx.cpp +--- a/comm/third_party/rnp/src/tests/cipher_cxx.cpp ++++ b/comm/third_party/rnp/src/tests/cipher_cxx.cpp +@@ -64,11 +64,12 @@ test_cipher(pgp_symm_alg_t alg, + const std::vector iv(decode_hex(iv_hex)); + const std::vector ad(decode_hex(ad_hex)); + const std::vector pt(decode_hex(pt_hex)); + const std::vector expected_ct(decode_hex(expected_ct_hex)); + +- auto enc = Cipher::encryption(alg, mode, tag_size, disable_padding); ++ auto enc = Cipher::encryption(alg, mode, tag_size, disable_padding); ++ assert_non_null(enc); + const size_t block_size = enc->block_size(); + const size_t ud = enc->update_granularity(); + std::vector ct; + // make room for padding + ct.resize(((pt.size() + tag_size) / block_size + 1) * block_size); +@@ -193,20 +194,26 @@ test_cipher(pgp_symm_alg_t alg, + } + } + + TEST_F(rnp_tests, test_cipher_idea) + { ++#if defined(ENABLE_IDEA) ++ assert_true(idea_enabled()); + // OpenSSL do_crypt man page example + test_cipher(PGP_SA_IDEA, + PGP_CIPHER_MODE_CBC, + 0, + false, + "000102030405060708090a0b0c0d0e0f", + "0102030405060708", + NULL, + "536f6d652043727970746f2054657874", + "8974b718d0cb68b44e27c480546dfcc7a33895f461733219"); ++#else ++ assert_false(idea_enabled()); ++ assert_null(Cipher::encryption(PGP_SA_IDEA, PGP_CIPHER_MODE_CBC, 0, false)); ++#endif + } + + TEST_F(rnp_tests, test_cipher_aes_128_ocb) + { + // RFC 7253 +diff --git a/comm/third_party/rnp/src/tests/cli.cpp b/comm/third_party/rnp/src/tests/cli.cpp +--- a/comm/third_party/rnp/src/tests/cli.cpp ++++ b/comm/third_party/rnp/src/tests/cli.cpp +@@ -137,10 +137,12 @@ TEST_F(rnp_tests, test_cli_rnp_keyfile) + MKEYS "key-sec-no-uid-no-sigs.pgp", + "--password", + "password", + "-d", + FILES "/hello.txt.pgp", ++ "--output", ++ "-", + NULL); + assert_int_equal(ret, 0); + assert_int_equal(rnp_unlink(FILES "/hello.txt.pgp"), 0); + + /* try to encrypt with keyfile, using the signing subkey */ +@@ -173,20 +175,24 @@ TEST_F(rnp_tests, test_cli_rnp_keyfile) + MKEYS "key-pub-subkey-1.pgp", + "--password", + "password", + "-d", + FILES "/hello.txt.asc", ++ "--output", ++ "-", + NULL); + assert_int_not_equal(ret, 0); + /* decrypt correctly with seckey + subkeys */ + ret = call_rnp("rnp", + "--keyfile", + MKEYS "key-sec.pgp", + "--password", + "password", + "-d", + FILES "/hello.txt.asc", ++ "--output", ++ "-", + NULL); + assert_int_equal(ret, 0); + assert_int_equal(rnp_unlink(FILES "/hello.txt.asc"), 0); + } + +@@ -208,11 +214,12 @@ test_cli_g10_key_sign(const char *userid + rnp_unlink(FILES "/hello.txt.pgp"); + return false; + } + + /* verify back */ +- ret = call_rnp("rnp", "--homedir", G10KEYS, "-v", FILES "/hello.txt.pgp", NULL); ++ ret = call_rnp( ++ "rnp", "--homedir", G10KEYS, "-v", FILES "/hello.txt.pgp", "--output", "-", NULL); + rnp_unlink(FILES "/hello.txt.pgp"); + return !ret; + } + + static bool +@@ -232,10 +239,12 @@ test_cli_g10_key_encrypt(const char *use + G10KEYS, + "--password", + "password", + "-d", + FILES "/hello.txt.pgp", ++ "--output", ++ "-", + NULL); + rnp_unlink(FILES "/hello.txt.pgp"); + return !ret; + } + +@@ -263,25 +272,27 @@ TEST_F(rnp_tests, test_cli_g10_operation + G10KEYS, + "--password", + "password", + "-d", + FILES "/hello.txt.pgp", ++ "--output", ++ "-", + NULL); + assert_int_equal(ret, 0); + assert_int_equal(rnp_unlink(FILES "/hello.txt.pgp"), 0); + + /* check dsa/eg key */ + assert_true(test_cli_g10_key_sign("c8a10a7d78273e10")); // signing key + assert_true(test_cli_g10_key_encrypt("c8a10a7d78273e10")); // will find subkey + assert_false(test_cli_g10_key_sign("02a5715c3537717e")); // fail - encrypting subkey + assert_true(test_cli_g10_key_encrypt("02a5715c3537717e")); // success + +- /* check rsa/rsa key, key is SC while subkey is E. Must fail as uses SHA1 */ +- assert_false(test_cli_g10_key_sign("2fb9179118898e8b")); +- assert_false(test_cli_g10_key_encrypt("2fb9179118898e8b")); ++ /* check rsa/rsa key, key is SC while subkey is E. Must succeed till year 2024 */ ++ assert_true(test_cli_g10_key_sign("2fb9179118898e8b")); ++ assert_true(test_cli_g10_key_encrypt("2fb9179118898e8b")); + assert_false(test_cli_g10_key_sign("6e2f73008f8b8d6e")); +- assert_false(test_cli_g10_key_encrypt("6e2f73008f8b8d6e")); ++ assert_true(test_cli_g10_key_encrypt("6e2f73008f8b8d6e")); + + /* check new rsa/rsa key, key is SC while subkey is E. */ + /* Now fails since we cannot parse new S-exps */ + assert_false(test_cli_g10_key_sign("bd860a52d1899c0f")); + assert_false(test_cli_g10_key_encrypt("bd860a52d1899c0f")); +@@ -345,14 +356,13 @@ TEST_F(rnp_tests, test_cli_rnpkeys_unico + { + #ifdef _WIN32 + std::string uid_acp = "\x80@a.com"; + std::wstring uid2_wide = + L"\x03C9\x0410@b.com"; // some Greek and Cyrillic for CreateProcessW test +- char *rnpkeys_path = rnp_compose_path(original_dir(), "../rnpkeys/rnpkeys.exe", NULL); + std::string homedir_s = std::string(m_dir) + "/unicode"; + rnp_mkdir(homedir_s.c_str()); +- std::string path_s = rnpkeys_path; ++ std::string path_s = rnp::path::append(original_dir(), "../rnpkeys/rnpkeys.exe"); + std::string cmdline_s = path_s + " --numbits 2048 --homedir " + homedir_s + + " --password password --userid " + uid_acp + " --generate-key"; + UINT acp = GetACP(); + STARTUPINFOA si; + ZeroMemory(&si, sizeof si); +@@ -513,49 +523,43 @@ TEST_F(rnp_tests, test_cli_rnp) + KEYS "/3", + "--password", + "password", + "--decrypt", + FILES "/hello.txt.pgp", ++ "--output", ++ "-", + NULL); + assert_int_equal(ret, 0); + } + + TEST_F(rnp_tests, test_cli_examples) + { +- char *examples_path = rnp_compose_path(original_dir(), "../examples", NULL); +- char *example_path = NULL; ++ auto examples_path = rnp::path::append(original_dir(), "../examples"); + /* key generation example */ +- example_path = rnp_compose_path(examples_path, "generate", NULL); +- assert_non_null(example_path); +- assert_int_equal(system(example_path), 0); +- free(example_path); ++ auto example_path = rnp::path::append(examples_path, "generate"); ++ assert_false(example_path.empty()); ++ assert_int_equal(system(example_path.c_str()), 0); + + /* encryption sample */ +- example_path = rnp_compose_path(examples_path, "encrypt", NULL); +- assert_non_null(example_path); +- assert_int_equal(system(example_path), 0); +- free(example_path); ++ example_path = rnp::path::append(examples_path, "encrypt"); ++ assert_false(example_path.empty()); ++ assert_int_equal(system(example_path.c_str()), 0); + + /* decryption sample */ +- example_path = rnp_compose_path(examples_path, "decrypt", NULL); +- assert_non_null(example_path); +- assert_int_equal(system(example_path), 0); +- free(example_path); ++ example_path = rnp::path::append(examples_path, "decrypt"); ++ assert_false(example_path.empty()); ++ assert_int_equal(system(example_path.c_str()), 0); + + /* signing sample */ +- example_path = rnp_compose_path(examples_path, "sign", NULL); +- assert_non_null(example_path); +- assert_int_equal(system(example_path), 0); +- free(example_path); ++ example_path = rnp::path::append(examples_path, "sign"); ++ assert_false(example_path.empty()); ++ assert_int_equal(system(example_path.c_str()), 0); + + /* verification sample */ +- example_path = rnp_compose_path(examples_path, "verify", NULL); +- assert_non_null(example_path); +- assert_int_equal(system(example_path), 0); +- free(example_path); +- +- free(examples_path); ++ example_path = rnp::path::append(examples_path, "verify"); ++ assert_false(example_path.empty()); ++ assert_int_equal(system(example_path.c_str()), 0); + } + + TEST_F(rnp_tests, test_cli_rnpkeys) + { + int ret; +@@ -785,40 +789,39 @@ TEST_F(rnp_tests, test_cli_rnpkeys_genke + delete_recursively(GENKEYS); + } + + TEST_F(rnp_tests, test_cli_dump) + { +- char *dump_path = rnp_compose_path(original_dir(), "../examples/dump", NULL); +- char cmd[512] = {0}; +- int chnum; +- int status; ++ auto dump_path = rnp::path::append(original_dir(), "../examples/dump"); ++ char cmd[512] = {0}; ++ int chnum; ++ int status; + /* call dump's help */ +- chnum = snprintf(cmd, sizeof(cmd), "%s -h", dump_path); ++ chnum = snprintf(cmd, sizeof(cmd), "%s -h", dump_path.c_str()); + assert_true(chnum < (int) sizeof(cmd)); + status = system(cmd); + assert_true(WIFEXITED(status)); + assert_int_equal(WEXITSTATUS(status), 1); + /* run dump on some data */ +- chnum = snprintf(cmd, sizeof(cmd), "%s \"%s\"", dump_path, KEYS "/1/pubring.gpg"); ++ chnum = snprintf(cmd, sizeof(cmd), "%s \"%s\"", dump_path.c_str(), KEYS "/1/pubring.gpg"); + assert_true(chnum < (int) sizeof(cmd)); + status = system(cmd); + assert_true(WIFEXITED(status)); + assert_int_equal(WEXITSTATUS(status), 0); + /* run dump on some data with json output */ +- chnum = snprintf(cmd, sizeof(cmd), "%s -j \"%s\"", dump_path, KEYS "/1/pubring.gpg"); ++ chnum = ++ snprintf(cmd, sizeof(cmd), "%s -j \"%s\"", dump_path.c_str(), KEYS "/1/pubring.gpg"); + assert_true(chnum < (int) sizeof(cmd)); + status = system(cmd); + assert_true(WIFEXITED(status)); + assert_int_equal(WEXITSTATUS(status), 0); + /* run dump on directory - must fail but not crash */ +- chnum = snprintf(cmd, sizeof(cmd), "%s \"%s\"", dump_path, KEYS "/1/"); ++ chnum = snprintf(cmd, sizeof(cmd), "%s \"%s\"", dump_path.c_str(), KEYS "/1/"); + assert_true(chnum < (int) sizeof(cmd)); + status = system(cmd); + assert_true(WIFEXITED(status)); + assert_int_not_equal(WEXITSTATUS(status), 0); +- +- free(dump_path); + } + + TEST_F(rnp_tests, test_cli_logname) + { + char * logname = getenv("LOGNAME"); +diff --git a/comm/third_party/rnp/src/tests/cli_common.py b/comm/third_party/rnp/src/tests/cli_common.py +--- a/comm/third_party/rnp/src/tests/cli_common.py ++++ b/comm/third_party/rnp/src/tests/cli_common.py +@@ -12,15 +12,17 @@ WORKDIR = '' + CONSOLE_ENCODING = 'UTF-8' + + class CLIError(Exception): + def __init__(self, message, log = None): + super(CLIError, self).__init__(message) ++ self.message = message + self.log = log ++ logging.info(self.message) ++ logging.debug(self.log.strip()) + + def __str__(self): +- logging.info(self.message) +- logging.debug(self.log.strip()) ++ return self.message + '\n' + self.log + + def is_windows(): + return sys.platform.startswith('win') or sys.platform.startswith('msys') + + def path_for_gpg(path): +@@ -132,10 +134,11 @@ def run_proc_windows(proc, params, stdin + with open(pass_path, "w+") as passf: + passf.write(passfo.read()) + pass_fl = os.open(pass_path, os.O_RDONLY | os.O_BINARY) + pass_cp = os.dup(passfd) + ++ retcode = -1 + try: + os.dup2(stdout_fl, stdout_no) + os.close(stdout_fl) + os.dup2(stderr_fl, stderr_no) + os.close(stderr_fl) +diff --git a/comm/third_party/rnp/src/tests/cli_tests.py b/comm/third_party/rnp/src/tests/cli_tests.py +--- a/comm/third_party/rnp/src/tests/cli_tests.py ++++ b/comm/third_party/rnp/src/tests/cli_tests.py +@@ -27,18 +27,20 @@ RNPDIR = '' + GPGHOME = None + PASSWORD = 'password' + RMWORKDIR = True + GPG_AEAD = False + GPG_NO_OLD = False ++GPG_BRAINPOOL = False + TESTS_SUCCEEDED = [] + TESTS_FAILED = [] + TEST_WORKFILES = [] + + # Supported features + RNP_TWOFISH = True + RNP_BRAINPOOL = True + RNP_AEAD = True ++RNP_IDEA = True + + if sys.version_info >= (3,): + unichr = chr + + def escape_regex(str): +@@ -83,19 +85,25 @@ AT_EXAMPLE = '@example.com' + + # Keyrings + PUBRING = 'pubring.gpg' + SECRING = 'secring.gpg' + PUBRING_1 = 'keyrings/1/pubring.gpg' ++SECRING_1 = 'keyrings/1/secring.gpg' ++KEYRING_DIR_1 = 'keyrings/1' ++KEYRING_DIR_3 = 'keyrings/3' + SECRING_G10 = 'test_stream_key_load/g10' + KEY_ALICE_PUB = 'test_key_validity/alice-pub.asc' ++KEY_ALICE_SUB_PUB = 'test_key_validity/alice-sub-pub.pgp' + KEY_ALICE_SEC = 'test_key_validity/alice-sec.asc' + KEY_ALICE_SUB_SEC = 'test_key_validity/alice-sub-sec.pgp' ++KEY_ALICE = 'Alice ' + KEY_25519_NOTWEAK_SEC = 'test_key_edge_cases/key-25519-non-tweaked-sec.asc' + + # Messages + MSG_TXT = 'test_messages/message.txt' + MSG_ES_25519 = 'test_messages/message.txt.enc-sign-25519' ++MSG_SIG_CRCR = 'test_messages/message.text-sig-crcr.sig' + + # Extensions + EXT_SIG = '.txt.sig' + EXT_ASC = '.txt.asc' + EXT_PGP = '.txt.pgp' +@@ -122,11 +130,11 @@ r'.*' \ + r':signature packet: algo 1, keyid \2\s+' \ + r'.*$' + + RE_RSA_KEY_LIST = r'^\s*' \ + r'2 keys found\s+' \ +-r'pub\s+(\d{4})/RSA \(Encrypt or Sign\) ([0-9a-z]{16}) \d{4}-\d{2}-\d{2} \[.*\]\s+' \ ++r'pub\s+(\d{4})/RSA ([0-9a-z]{16}) \d{4}-\d{2}-\d{2} \[.*\]\s+' \ + r'([0-9a-z]{40})\s+' \ + r'uid\s+(.+)\s+' \ + r'sub.+\s+' \ + r'[0-9a-z]{40}\s+$' + +@@ -164,12 +172,13 @@ r'UserID packet.*' \ + r'id: enc@rnp.*' \ + r'Secret subkey packet.*' \ + r'secret key material:.*' \ + r'encrypted secret key data:.*$' + +-RE_RNP_REVOCATION_SIG = r'(?s)^.*' \ +-r'packet header .* \(tag 2, len .*' \ ++RE_RNP_REVOCATION_SIG = r'(?s)' \ ++r':armored input\n' \ ++r':off 0: packet header .* \(tag 2, len .*' \ + r'Signature packet.*' \ + r'version: 4.*' \ + r'type: 32 \(Key revocation signature\).*' \ + r'public key algorithm:.*' \ + r'hashed subpackets:.*' \ +@@ -189,10 +198,12 @@ r'key 0451409669FFDE3C: "Alice 2) or (int(match.group(2)) > 3) or (int(match.group(3)) > 0): + GPG_NO_OLD = True + return + # Version 2.3.0 release or beta + GPG_NO_OLD = not match.group(5) or (int(match.group(5)) >= 1598) ++ # Check whether Brainpool curves are supported ++ _, out, _ = run_proc(GPG, ["--with-colons", "--list-config", "curve"]) ++ GPG_BRAINPOOL = re.match(r'(?s)^.*brainpoolP256r1.*', out) + + def rnp_check_features(): +- global RNP_TWOFISH, RNP_BRAINPOOL, RNP_AEAD ++ global RNP_TWOFISH, RNP_BRAINPOOL, RNP_AEAD, RNP_IDEA + ret, out, _ = run_proc(RNP, ['--version']) + if ret != 0: + raise_err('Failed to get RNP version.') + # AEAD + RNP_AEAD = re.match(r'(?s)^.*AEAD:.*EAX,.*OCB.*', out) + # Twofish + RNP_TWOFISH = re.match(r'(?s)^.*Encryption:.*TWOFISH.*', out) + # Brainpool curves + RNP_BRAINPOOL = re.match(r'(?s)^.*Curves:.*brainpoolP256r1.*brainpoolP384r1.*brainpoolP512r1.*', out) +- # Check that everything is enabled for Botan: +- if re.match(r'(?s)^.*Backend:\s+Botan.*', out) and (not RNP_AEAD or not RNP_TWOFISH or not RNP_BRAINPOOL): +- raise_err('Something is wrong with features detection.') ++ # IDEA encryption algorithm ++ RNP_IDEA = re.match(r'(?s)^.*Encryption:.*IDEA.*', out) + + def setup(loglvl): + # Setting up directories. + global RMWORKDIR, WORKDIR, RNPDIR, RNP, RNPK, GPG, GPGDIR, GPGHOME, GPGCONF + logging.basicConfig(stream=sys.stderr, format="%(message)s") +@@ -966,11 +980,11 @@ class Keystore(unittest.TestCase): + # Import key to the gnupg + ret, _, _ = run_proc(GPG, ['--batch', '--passphrase', PASSWORD, '--homedir', + GPGHOME, '--import', + path_for_gpg(os.path.join(RNPDIR, PUBRING)), + path_for_gpg(os.path.join(RNPDIR, SECRING))]) +- self.assertEqual(ret, 0, 'gpg key import failed') ++ self.assertEqual(ret, 0, GPG_IMPORT_FAILED) + # Cleanup and return + clear_keyrings() + + def test_generate_default_rsa_key(self): + self._rnpkey_generate_rsa() +@@ -1003,11 +1017,11 @@ class Keystore(unittest.TestCase): + self.assertRegex(err, r'(?s)^.*Invalid s2k msec value: WRONG_MSEC.*') + # Wrong cipher + ret, _, err = run_proc(RNPK, ['--cipher', 'WRONG_AES', '--homedir', RNPDIR, '--password', 'password', + '--userid', 'wrong_aes', '--generate-key']) + self.assertNotEqual(ret, 0) +- self.assertRegex(err, r'(?s)^.*Unsupported symmetric algorithm: WRONG_AES.*') ++ self.assertRegex(err, r'(?s)^.*Unsupported encryption algorithm: WRONG_AES.*Failed to process argument --cipher.*') + + def test_generate_multiple_rsa_key__check_if_available(self): + ''' + Generate multiple RSA keys and check if they are all available + ''' +@@ -1269,10 +1283,12 @@ class Keystore(unittest.TestCase): + ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '0451409669FFDE3C', '--pass-fd', str(pipe), + '--output', OUT_ALICE_REV, '--overwrite']) + os.close(pipe) + self.assertEqual(ret, 0) + self.assertTrue(os.path.isfile(OUT_ALICE_REV)) ++ with open(OUT_ALICE_REV, "rb") as armored: ++ self.assertRegex(armored.read().decode('utf-8'), r'-----END PGP PUBLIC KEY BLOCK-----\r\n$', 'Armor tail not found') + # Check revocation contents + ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '--list-packets', OUT_ALICE_REV]) + self.assertEqual(ret, 0) + self.assertNotEqual(len(out), 0) + match = re.match(RE_RNP_REVOCATION_SIG, out) +@@ -1330,10 +1346,12 @@ class Keystore(unittest.TestCase): + '--output', OUT_ALICE_REV, '--overwrite', '--rev-type', revcode, '--rev-reason', revreason]) + os.close(pipe) + self.assertEqual(ret, 0, 'Failed to export revocation with code ' + revcode) + self.assertTrue(os.path.isfile(OUT_ALICE_REV), 'Failed to export revocation with code ' + revcode) + # Check revocation contents ++ with open(OUT_ALICE_REV, "rb") as armored: ++ self.assertRegex(armored.read().decode('utf-8'), r'-----END PGP PUBLIC KEY BLOCK-----\r\n$', 'Armor tail not found') + ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '--list-packets', OUT_ALICE_REV]) + self.assertEqual(ret, 0, 'Failed to list exported revocation packets') + self.assertNotEqual(len(out), 0, 'Failed to list exported revocation packets') + match = re.match(RE_RNP_REVOCATION_SIG, out) + self.assertTrue(match) +@@ -1374,11 +1392,11 @@ class Keystore(unittest.TestCase): + r'Would you like to overwrite it\? \(y/N\).*' \ + r'Please enter the new filename:.*$' + + clear_keyrings() + # Import Alice's public key +- ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sub-pub.pgp')]) ++ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_PUB)]) + self.assertEqual(ret, 0) + # Attempt to export no key + ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key']) + self.assertNotEqual(ret, 0) + self.assertRegex(err, r'(?s)^.*No key specified\.$') +@@ -1418,11 +1436,11 @@ class Keystore(unittest.TestCase): + # Export with --overwrite parameter + with open(kpub, 'w+') as f: + f.truncate(10) + ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub, '--overwrite']) + self.assertEqual(ret, 0) +- # Re-import it, making sure file was correctly overwriten ++ # Re-import it, making sure file was correctly overwritten + ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', kpub]) + self.assertEqual(ret, 0) + # Enter 'y' in ovewrite prompt + with open(kpub, 'w+') as f: + f.truncate(10) +@@ -1721,10 +1739,12 @@ class Misc(unittest.TestCase): + hashes = ['SHA1', 'RIPEMD160', 'SHA256', 'SHA384', 'SHA512', 'SHA224'] + s2kmodes = [0, 1, 3] + + if not RNP_TWOFISH: + ciphers.remove('TWOFISH') ++ if not RNP_IDEA: ++ ciphers.remove('IDEA') + + def rnp_encryption_s2k_gpg(cipher, hash_alg, s2k=None, iterations=None): + params = ['--homedir', GPGHOME, '-c', '--s2k-cipher-algo', cipher, + '--s2k-digest-algo', hash_alg, '--batch', '--passphrase', PASSWORD, + '--output', dst, src] +@@ -1780,20 +1800,39 @@ class Misc(unittest.TestCase): + + compare_files(dst_beg, dst_fin, "RNP armor/dearmor test failed") + compare_files(src_beg, dst_mid, "RNP armor/dearmor test failed") + remove_files(dst_beg, dst_mid, dst_fin) + ++ # 3-byte last chunk with missing crc ++ msg = '-----BEGIN PGP MESSAGE-----\n\nMTIzNDU2Nzg5\n-----END PGP MESSAGE-----\n' ++ ret, out, err = run_proc(RNP, ['--dearmor'], msg) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*123456789.*') ++ self.assertRegex(err, r'(?s)^.*Warning: missing or malformed CRC line.*') ++ # No invalid CRC message ++ R_CRC = r'(?s)^.*Warning: CRC mismatch.*$' ++ dec = 'decoded.pgp' ++ ret, _, err = run_proc(RNP, ['--dearmor', data_path('test_stream_key_load/ecc-25519-pub.asc'), '--output', dec]) ++ remove_files(dec) ++ self.assertEqual(ret, 0) ++ self.assertNotRegex(err, R_CRC) ++ # Invalid CRC message ++ ret, _, err = run_proc(RNP, ['--dearmor', data_path('test_stream_armor/ecc-25519-pub-bad-crc.asc'), '--output', dec]) ++ remove_files(dec) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, R_CRC) ++ + def test_rnpkeys_lists(self): +- KEYRING_1 = data_path('keyrings/1') ++ KEYRING_1 = data_path(KEYRING_DIR_1) + KEYRING_2 = data_path('keyrings/2') +- KEYRING_3 = data_path('keyrings/3') ++ KEYRING_3 = data_path(KEYRING_DIR_3) + KEYRING_5 = data_path('keyrings/5') + path = data_path('test_cli_rnpkeys') + '/' + + _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '--list-keys']) + compare_file_any(allow_y2k38_on_32bit(path + 'keyring_1_list_keys'), out, 'keyring 1 key listing failed') +- _, out, _ = run_proc(RNPK, ['--hom', KEYRING_1, '-l', '--with-sigs']) ++ _, out, _ = run_proc(RNPK, ['--home', KEYRING_1, '-l', '--with-sigs']) + compare_file_any(allow_y2k38_on_32bit(path + 'keyring_1_list_sigs'), out, 'keyring 1 sig listing failed') + _, out, _ = run_proc(RNPK, ['--home', KEYRING_1, '--list-keys', '--secret']) + compare_file_any(allow_y2k38_on_32bit(path + 'keyring_1_list_keys_sec'), out, 'keyring 1 sec key listing failed') + _, out, _ = run_proc(RNPK, ['--home', KEYRING_1, '--list-keys', + '--secret', '--with-sigs']) +@@ -1852,10 +1891,21 @@ class Misc(unittest.TestCase): + compare_file(path + 'getkey_zzzzzzzz', out, 'list key zzzzzzzz failed') + + _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l', '--userid', '2fcadf05ffa501bb']) + compare_file_any(allow_y2k38_on_32bit(path + 'getkey_2fcadf05ffa501bb'), out, 'list key 2fcadf05ffa501bb failed') + ++ def test_rnpkeys_list_invalid_keys(self): ++ RNPDIR2 = RNPDIR + '2' ++ os.mkdir(RNPDIR2, 0o700) ++ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR2, '--import', data_path('test_forged_keys/eddsa-2012-md5-pub.pgp')]) ++ self.assertEqual(ret, 0) ++ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR2, '--list-keys', '--with-sigs']) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)2 keys found.*8801eafbd906bd21.*\[INVALID\].*expired-md5-key-sig.*\[INVALID\].*sig.*\[unknown\] \[invalid\]') ++ self.assertRegex(err, r'(?s)Insecure hash algorithm 1, marking signature as invalid') ++ shutil.rmtree(RNPDIR2, ignore_errors=True) ++ + def test_rnpkeys_g10_list_order(self): + ret, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10), '--list-keys']) + self.assertEqual(ret, 0) + if RNP_BRAINPOOL: + self.assertEqual(file_text(data_path('test_cli_rnpkeys/g10_list_keys')), out, 'g10 key listing failed') +@@ -1892,38 +1942,38 @@ class Misc(unittest.TestCase): + + def test_large_packet(self): + # Verifying large packet file with GnuPG + kpath = path_for_gpg(data_path(PUBRING_1)) + dpath = path_for_gpg(data_path('test_large_packet/4g.bzip2.gpg')) +- ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--keyring', kpath, '--verify', dpath]) ++ ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--verify', dpath]) + self.assertEqual(ret, 0, 'large packet verification failed') + + def test_partial_length_signature(self): + # Verifying partial length signature with GnuPG + kpath = path_for_gpg(data_path(PUBRING_1)) + mpath = path_for_gpg(data_path('test_partial_length/message.txt.partial-signed')) +- ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--keyring', kpath, '--verify', mpath]) ++ ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--verify', mpath]) + self.assertNotEqual(ret, 0, 'partial length signature packet should result in failure but did not') + + def test_partial_length_public_key(self): + # Reading keyring that has a public key packet with partial length using GnuPG + kpath = data_path('test_partial_length/pubring.gpg.partial') +- ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--keyring', kpath, '--list-keys']) ++ ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--list-keys']) + self.assertNotEqual(ret, 0, 'partial length public key packet should result in failure but did not') + + def test_partial_length_zero_last_chunk(self): + # Verifying message in partial packets having 0-size last chunk with GnuPG + kpath = path_for_gpg(data_path(PUBRING_1)) + mpath = path_for_gpg(data_path('test_partial_length/message.txt.partial-zero-last')) +- ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--keyring', kpath, '--verify', mpath]) ++ ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--verify', mpath]) + self.assertEqual(ret, 0, 'message in partial packets having 0-size last chunk verification failed') + + def test_partial_length_largest(self): + # Verifying message having largest possible partial packet with GnuPG + kpath = path_for_gpg(data_path(PUBRING_1)) + mpath = path_for_gpg(data_path('test_partial_length/message.txt.partial-1g')) +- ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--keyring', kpath, '--verify', mpath]) ++ ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--verify', mpath]) + self.assertEqual(ret, 0, 'message having largest possible partial packet verification failed') + + def test_rnp_single_export(self): + # Import key with subkeys, then export it, test that it is exported once. + # See issue #1153 +@@ -1959,10 +2009,54 @@ class Misc(unittest.TestCase): + ret, out, _ = run_proc(RNPK, params) + self.assertEqual(ret, 0, PKT_LIST_FAILED) + compare_file_any(allow_y2k38_on_32bit(data_path('test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt')), + out, 'listing mismatch') + ++ def test_rnp_autocrypt_key_import(self): ++ R_25519 = r'(?s)^.*pub.*255/EdDSA.*21fc68274aae3b5de39a4277cc786278981b0728.*$' ++ R_256K1 = r'(?s)^.*pub.*3ea5bb6f9692c1a0.*7635401f90d3e533.*$' ++ # Import misc configurations of base64-encoded autocrypt keys ++ clear_keyrings() ++ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-25519-pub.b64')]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, R_25519) ++ # No trailing EOL after the base64 data ++ clear_keyrings() ++ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-25519-pub-2.b64')]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, R_25519) ++ # Extra spaces/eols/tabs after the base64 data ++ clear_keyrings() ++ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-25519-pub-3.b64')]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, R_25519) ++ # Invalid symbols after the base64 data ++ clear_keyrings() ++ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-25519-pub-4.b64')]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*wrong base64 padding: ==zz.*Failed to init/check dearmor.*failed to import key\(s\) from .*, stopping.$') ++ # Binary data size is multiple of 3, single base64 line ++ clear_keyrings() ++ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-p256k1-pub.b64')]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, R_256K1) ++ # Binary data size is multiple of 3, multiple base64 lines ++ clear_keyrings() ++ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-p256k1-pub-2.b64')]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, R_256K1) ++ # Too long base64 trailer ('===') ++ clear_keyrings() ++ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_armor/long_b64_trailer.b64')]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*wrong base64 padding length 3.*Failed to init/check dearmor.*$') ++ # Extra data after the base64-encoded data ++ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_armor/b64_trailer_extra_data.b64')]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*warning: extra data after the base64 stream.*Failed to init/check dearmor.*warning: not all data was processed.*$') ++ self.assertRegex(out, R_25519) ++ + def test_rnp_list_packets(self): + KEY_P256 = data_path('test_list_packets/ecc-p256-pub.asc') + # List packets in humand-readable format + params = ['--list-packets', KEY_P256] + ret, out, _ = run_proc(RNP, params) +@@ -2035,24 +2129,44 @@ class Misc(unittest.TestCase): + # List test file with critical notation + params = ['--list-packets', data_path('test_messages/message.txt.signed.crit-notation')] + ret, out, _ = run_proc(RNP, params) + self.assertEqual(ret, 0) + self.assertRegex(out, r'(?s)^.*:type 20, len 35, critical.*notation data: critical text = critical value.*$') ++ # List signature with signer's userid subpacket ++ params = ['--list-packets', data_path(MSG_SIG_CRCR)] ++ ret, out, _ = run_proc(RNP, params) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*:type 28, len 9.*signer\'s user ID: alice@rnp.*$') ++ # JSON list signature with signer's userid subpacket ++ params = ['--list-packets', '--json', data_path(MSG_SIG_CRCR)] ++ ret, out, _ = run_proc(RNP, params) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*"type.str":"signer\'s user ID".*"length":9.*"uid":"alice@rnp".*$') ++ # List signature with reason for revocation subpacket ++ params = ['--list-packets', data_path('test_uid_validity/key-sig-revocation.pgp')] ++ ret, out, _ = run_proc(RNP, params) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*:type 29, len 24.*reason for revocation: 32 \(No longer valid\).*message: Testing revoked userid.*$') ++ # JSON list signature with reason for revocation subpacket ++ params = ['--list-packets', '--json', data_path('test_uid_validity/key-sig-revocation.pgp')] ++ ret, out, _ = run_proc(RNP, params) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*"type.str":"reason for revocation".*"code":32.*"message":"Testing revoked userid.".*$') + + def test_rnp_list_packets_edge_cases(self): + KEY_EMPTY_UID = data_path('test_key_edge_cases/key-empty-uid.pgp') + # List empty key packets + params = ['--list-packets', data_path('test_key_edge_cases/key-empty-packets.pgp')] + ret, out, _ = run_proc(RNP, params) +- self.assertNotEqual(ret, 0, 'packet listing not failed') ++ self.assertEqual(ret, 0, PKT_LIST_FAILED) + compare_file_ex(data_path('test_key_edge_cases/key-empty-packets.txt'), out, + 'key-empty-packets listing mismatch') + + # List empty key packets json + params = ['--list-packets', '--json', data_path('test_key_edge_cases/key-empty-packets.pgp')] + ret, _, _ = run_proc(RNP, params) +- self.assertNotEqual(ret, 0, 'packet listing not failed') ++ self.assertEqual(ret, 0, PKT_LIST_FAILED) + + # List empty uid + params = ['--list-packets', KEY_EMPTY_UID] + ret, out, _ = run_proc(RNP, params) + self.assertEqual(ret, 0, PKT_LIST_FAILED) +@@ -2100,60 +2214,70 @@ class Misc(unittest.TestCase): + self.assertEqual(ret, 0, PKT_LIST_FAILED) + compare_file_ex(data_path('test_key_edge_cases/key-malf-sig.json'), out, + 'key-malf-sig json listing mismatch') + + def test_debug_log(self): +- run_proc(RNPK, ['--homedir', data_path('keyrings/1'), '--list-keys', '--debug', '--all']) ++ run_proc(RNPK, ['--homedir', data_path(KEYRING_DIR_1), '--list-keys', '--debug', '--all']) + run_proc(RNPK, ['--homedir', data_path('keyrings/2'), '--list-keys', '--debug', '--all']) +- run_proc(RNPK, ['--homedir', data_path('keyrings/3'), '--list-keys', '--debug', '--all']) ++ run_proc(RNPK, ['--homedir', data_path(KEYRING_DIR_3), '--list-keys', '--debug', '--all']) + run_proc(RNPK, ['--homedir', data_path(SECRING_G10), + '--list-keys', '--debug', '--all']) + + def test_pubring_loading(self): + NO_PUBRING = r'(?s)^.*warning: keyring at path \'.*/pubring.gpg\' doesn\'t exist.*$' ++ EMPTY_HOME = r'(?s)^.*Keyring directory .* is empty.*rnpkeys.*GnuPG.*' + NO_USERID = 'No userid or default key for operation' + + test_dir = tempfile.mkdtemp(prefix='rnpctmp') + test_data = data_path(MSG_TXT) + output = os.path.join(test_dir, 'output') + params = ['--symmetric', '--password', 'pass', '--homedir', test_dir, test_data, '--output', output] + ret, _, err = run_proc(RNP, ['--encrypt'] + params) + self.assertEqual(ret, 1, 'encrypt w/o pubring didn\'t fail') +- self.assertRegex(err, NO_PUBRING, 'wrong no-keyring message') ++ self.assertNotRegex(err, NO_PUBRING, 'wrong no-keyring message') ++ self.assertRegex(err, EMPTY_HOME) + self.assertIn(NO_USERID, err, 'Unexpected no key output') + self.assertIn('Failed to build recipients key list', err, 'Unexpected key list output') + + ret, _, err = run_proc(RNP, ['--sign'] + params) + self.assertEqual(ret, 1, 'sign w/o pubring didn\'t fail') +- self.assertRegex(err, NO_PUBRING, 'wrong failure output') ++ self.assertNotRegex(err, NO_PUBRING, 'wrong failure output') ++ self.assertRegex(err, EMPTY_HOME) + self.assertIn(NO_USERID, err, 'wrong no userid message') + self.assertIn('Failed to build signing keys list', err, 'wrong signing list failure message') + + ret, _, err = run_proc(RNP, ['--clearsign'] + params) + self.assertEqual(ret, 1, 'clearsign w/o pubring didn\'t fail') +- self.assertRegex(err, NO_PUBRING, 'wrong clearsign no pubring message') ++ self.assertNotRegex(err, NO_PUBRING, 'wrong clearsign no pubring message') ++ self.assertRegex(err, EMPTY_HOME) + self.assertIn(NO_USERID, err, 'Unexpected clearsign no key output') + self.assertIn('Failed to build signing keys list', err, 'Unexpected clearsign key list output') + + ret, _, _ = run_proc(RNP, params) + self.assertEqual(ret, 0, 'symmetric w/o pubring failed') + + shutil.rmtree(test_dir) + + def test_homedir_accessibility(self): +- ret, _, _ = run_proc(RNPK, ['--homedir', os.path.join(RNPDIR, 'non-existing'), '--generate', '--password=none']) ++ ret, _, err = run_proc(RNPK, ['--homedir', os.path.join(RNPDIR, 'non-existing'), '--generate', '--password=none']) + self.assertNotEqual(ret, 0, 'failed to check for homedir accessibility') ++ self.assertRegex(err, r'(?s)^.*Home directory .*.rnp.non-existing.* does not exist or is not writable!') ++ self.assertRegex(err, RE_KEYSTORE_INFO) + os.mkdir(os.path.join(RNPDIR, 'existing'), 0o700) +- ret, _, _ = run_proc(RNPK, ['--homedir', os.path.join(RNPDIR, 'existing'), '--generate', '--password=none']) ++ ret, _, err = run_proc(RNPK, ['--homedir', os.path.join(RNPDIR, 'existing'), '--generate', '--password=none']) + self.assertEqual(ret, 0, 'failed to use writeable and existing homedir') ++ self.assertNotRegex(err, r'(?s)^.*Home directory .* does not exist or is not writable!') ++ self.assertNotRegex(err, RE_KEYSTORE_INFO) + + def test_no_home_dir(self): + home = os.environ['HOME'] + del os.environ['HOME'] +- ret, _, _ = run_proc(RNP, ['-v', 'non-existing.pgp']) ++ ret, _, err = run_proc(RNP, ['-v', 'non-existing.pgp']) + os.environ['HOME'] = home + self.assertEqual(ret, 2, 'failed to run without HOME env variable') ++ self.assertRegex(err, r'(?s)^.*Home directory .* does not exist or is not writable!') ++ self.assertRegex(err, RE_KEYSTORE_INFO) + + def test_exit_codes(self): + ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--help']) + self.assertEqual(ret, 0, 'invalid exit code of \'rnp --help\'') + ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--help']) +@@ -2272,19 +2396,19 @@ class Misc(unittest.TestCase): + self.assertEqual(ret, 1) + self.assertRegex(err, r'(?s)^.*init_file_dest.*failed to create file.*output.pgp.*Error 2.*$') + + def test_empty_keyrings(self): + NO_KEYRING = r'(?s)^.*' \ +- r'warning: keyring at path \'.*/\.rnp/pubring.gpg\' doesn\'t exist.*' \ +- r'warning: keyring at path \'.*/\.rnp/secring.gpg\' doesn\'t exist.*$' ++ r'warning: keyring at path \'.*.\.rnp.pubring\.gpg\' doesn\'t exist.*' \ ++ r'warning: keyring at path \'.*.\.rnp.secring\.gpg\' doesn\'t exist.*$' + EMPTY_KEYRING = r'(?s)^.*' \ +- r'warning: no keys were loaded from the keyring \'.*/\.rnp/pubring.gpg\'.*' \ +- r'warning: no keys were loaded from the keyring \'.*/\.rnp/secring.gpg\'.*$' ++ r'Warning: no keys were loaded from the keyring \'.*.\.rnp.pubring\.gpg\'.*' \ ++ r'Warning: no keys were loaded from the keyring \'.*.\.rnp.secring\.gpg\'.*$' + PUB_IMPORT= r'(?s)^.*pub\s+255/EdDSA 0451409669ffde3c .* \[SC\].*$' +- EMPTY_SECRING = r'(?s)^.*' \ +- r'warning: no keys were loaded from the keyring \'.*/\.rnp/secring.gpg\'.*$' ++ EMPTY_SECRING = r'(?s)^.*Warning: no keys were loaded from the keyring \'.*\.rnp.secring.gpg\'.*$' + SEC_IMPORT= r'(?s)^.*sec\s+255/EdDSA 0451409669ffde3c .* \[SC\].*$' ++ EMPTY_HOME = r'(?s)^.*Keyring directory .* is empty.*rnpkeys.*GnuPG.*' + + os.rename(RNPDIR, RNPDIR + '-old') + home = os.environ['HOME'] + os.environ['HOME'] = WORKDIR + try: +@@ -2295,50 +2419,90 @@ class Misc(unittest.TestCase): + ret, _, err = run_proc(RNP, ['-c', src, '--password', 'password']) + self.assertEqual(ret, 0, 'Symmetric encryption without home failed') + self.assertNotRegex(err, NO_KEYRING, 'No keyring msg in encryption output') + ret, _, err = run_proc(RNP, ['-d', enc, '--password', 'password', '--output', dec]) + self.assertEqual(ret, 0, 'Symmetric decryption without home failed') +- self.assertRegex(err, NO_KEYRING, 'No keyring msg in decryption output') ++ self.assertNotRegex(err, NO_KEYRING, 'No keyring msg in decryption output') ++ self.assertRegex(err, EMPTY_HOME) + self.assertIn(WORKDIR, err, 'No workdir in decryption output') + compare_files(src, dec, DEC_DIFFERS) + remove_files(enc, dec) + # Import key without .rnp home directory + ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_PUB)]) + self.assertEqual(ret, 0, 'Key import failed without home') +- self.assertRegex(err, NO_KEYRING, 'No keyring msg in key import output') ++ self.assertNotRegex(err, NO_KEYRING, 'No keyring msg in key import output') ++ self.assertRegex(err, EMPTY_HOME) + self.assertIn(WORKDIR, err, 'No workdir in key import output') + self.assertRegex(out, PUB_IMPORT, 'Wrong key import output') + ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_SEC)]) + self.assertEqual(ret, 0, 'Secret key import without home failed') + self.assertNotRegex(err, NO_KEYRING, 'no keyring message in key import output') +- self.assertRegex(err, EMPTY_SECRING, 'no empty secrin in key import output') ++ self.assertNotRegex(err, EMPTY_HOME) ++ self.assertRegex(err, EMPTY_SECRING, 'no empty secring in key import output') + self.assertIn(WORKDIR, err, 'no workdir in key import output') + self.assertRegex(out, SEC_IMPORT, 'Wrong secret key import output') + # Run with empty .rnp home directory + shutil.rmtree(RNPDIR, ignore_errors=True) + os.mkdir(RNPDIR, 0o700) + ret, _, err = run_proc(RNP, ['-c', src, '--password', 'password']) + self.assertEqual(ret, 0) + self.assertNotRegex(err, NO_KEYRING) + ret, out, err = run_proc(RNP, ['-d', enc, '--password', 'password', '--output', dec]) + self.assertEqual(ret, 0, 'Symmetric decryption failed') +- self.assertRegex(err, NO_KEYRING, 'No keyring message in decryption output') ++ self.assertRegex(err, EMPTY_HOME) ++ self.assertNotRegex(err, NO_KEYRING, 'No keyring message in decryption output') + self.assertIn(WORKDIR, err, 'No workdir in decryption output') + compare_files(src, dec, DEC_DIFFERS) + remove_files(enc, dec) + # Import key with empty .rnp home directory + ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_PUB)]) + self.assertEqual(ret, 0, 'Public key import with empty home failed') +- self.assertRegex(err, NO_KEYRING, 'No keyring message in key import output') ++ self.assertNotRegex(err, NO_KEYRING, 'No keyring message in key import output') ++ self.assertRegex(err, EMPTY_HOME) + self.assertIn(WORKDIR, err, 'No workdir in key import output') + self.assertRegex(out, PUB_IMPORT, 'Wrong pub key import output') + ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_SEC)]) + self.assertEqual(ret, 0, 'Secret key import failed') + self.assertNotRegex(err, NO_KEYRING, 'No-keyring message in secret key import output') + self.assertRegex(err, EMPTY_SECRING, 'No empty secring msg in secret key import output') ++ self.assertNotRegex(err, EMPTY_HOME) + self.assertIn(WORKDIR, err, 'No workdir in secret key import output') + self.assertRegex(out, SEC_IMPORT, 'wrong secret key import output') ++ if not is_windows(): ++ # Attempt ro run with non-writable HOME ++ newhome = os.path.join(WORKDIR, 'new') ++ os.mkdir(newhome, 0o400) ++ os.environ['HOME'] = newhome ++ ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_PUB)]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*Home directory \'.*new\' does not exist or is not writable!') ++ self.assertRegex(err, RE_KEYSTORE_INFO) ++ self.assertIn(WORKDIR, err) ++ os.environ['HOME'] = WORKDIR ++ shutil.rmtree(newhome, ignore_errors=True) ++ # Attempt to load keyring with invalid permissions ++ os.chmod(os.path.join(RNPDIR, PUBRING), 0o000) ++ ret, out, err = run_proc(RNPK, ['--list-keys']) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Warning: failed to open keyring at path \'.*pubring\.gpg\' for reading.') ++ self.assertRegex(out, r'(?s)^.*Alice ') ++ os.chmod(os.path.join(RNPDIR, SECRING), 0o000) ++ ret, out, err = run_proc(RNPK, ['--list-keys']) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*Warning: failed to open keyring at path \'.*pubring\.gpg\' for reading.') ++ self.assertRegex(err, r'(?s)^.*Warning: failed to open keyring at path \'.*secring\.gpg\' for reading.') ++ self.assertRegex(out, r'(?s)^.*Key\(s\) not found.') ++ # Attempt to load keyring with random data ++ shutil.rmtree(RNPDIR, ignore_errors=True) ++ os.mkdir(RNPDIR, 0o700) ++ random_text(os.path.join(RNPDIR, PUBRING), 1000) ++ random_text(os.path.join(RNPDIR, SECRING), 1000) ++ ret, out, err = run_proc(RNPK, ['--list-keys']) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*Error: failed to load keyring from \'.*pubring\.gpg\'') ++ self.assertNotRegex(err, r'(?s)^.*Error: failed to load keyring from \'.*secring\.gpg\'') ++ self.assertRegex(out, r'(?s)^.*Key\(s\) not found.') + # Run with .rnp home directory with empty keyrings + shutil.rmtree(RNPDIR, ignore_errors=True) + os.mkdir(RNPDIR, 0o700) + random_text(os.path.join(RNPDIR, PUBRING), 0) + random_text(os.path.join(RNPDIR, SECRING), 0) +@@ -2373,11 +2537,12 @@ class Misc(unittest.TestCase): + src, enc = reg_workfiles('source', '.txt', EXT_PGP) + with open(src, 'w+') as f: + f.write('Hello world') + # Encrypt file but forget to pass cipher name + ret, _, err = run_proc(RNP, ['-c', src, '--password', 'password', '--cipher']) +- self.assertNotEqual(ret, 0) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*rnp(|\.exe): option( .--cipher.|) requires an argument.*Usage: rnp --command \[options\] \[files\].*') + # Encrypt file using the unknown symmetric algorithm + ret, _, err = run_proc(RNP, ['-c', src, '--cipher', 'bad', '--password', 'password']) + self.assertNotEqual(ret, 0) + self.assertRegex(err, r'(?s)^.*Unsupported encryption algorithm: bad.*$') + # Encrypt file but forget to pass hash algorithm name +@@ -2601,11 +2766,505 @@ class Misc(unittest.TestCase): + self.assertEqual(ret, 0) + # Remove key + ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--yes', '--delete-secret-key', 'dde0ee539c017d2bd3f604a53176fc1486aa2528']) + self.assertEqual(ret, 0) + ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', '--force', 'dde0ee539c017d2bd3f604a53176fc1486aa2528']) +- self.assertEqual(ret, 0) ++ self.assertEqual(ret, 0) ++ ++ def test_aead_last_chunk_zero_length(self): ++ # Cover case with last AEAD chunk of the zero size ++ os.rename(RNPDIR, RNPDIR + '-old') ++ os.mkdir(RNPDIR) ++ try: ++ dec, enc = reg_workfiles('cleartext', '.dec', '.enc') ++ srctxt = data_path('test_messages/message.aead-last-zero-chunk.txt') ++ srcenc = data_path('test_messages/message.aead-last-zero-chunk.enc') ++ # Import Alice's key ++ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_SEC)]) ++ self.assertEqual(ret, 0) ++ # Decrypt already existing file ++ if RNP_AEAD and RNP_BRAINPOOL: ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '-d', srcenc, '--output', dec]) ++ self.assertEqual(ret, 0) ++ self.assertEqual(file_text(srctxt), file_text(dec)) ++ os.remove(dec) ++ # Decrypt with gnupg ++ if GPG_AEAD and GPG_BRAINPOOL: ++ ret, _, _ = run_proc(GPG, ['--batch', '--passphrase', PASSWORD, '--homedir', ++ GPGHOME, '--import', data_path(KEY_ALICE_SUB_SEC)]) ++ self.assertEqual(ret, 0, GPG_IMPORT_FAILED) ++ gpg_decrypt_file(srcenc, dec, PASSWORD) ++ self.assertEqual(file_text(srctxt), file_text(dec)) ++ os.remove(dec) ++ if RNP_AEAD and RNP_BRAINPOOL: ++ # Encrypt with RNP ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '-z', '0', '-r', 'alice', '--aead=eax', '--aead-chunk-bits=1', '-e', srctxt, '--output', enc]) ++ self.assertEqual(ret, 0) ++ # Decrypt with RNP again ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '-d', enc, '--output', dec]) ++ self.assertEqual(file_text(srctxt), file_text(dec)) ++ os.remove(dec) ++ if GPG_AEAD and GPG_BRAINPOOL: ++ # Decrypt with GnuPG ++ gpg_decrypt_file(enc, dec, PASSWORD) ++ self.assertEqual(file_text(srctxt), file_text(dec)) ++ finally: ++ shutil.rmtree(RNPDIR, ignore_errors=True) ++ os.rename(RNPDIR + '-old', RNPDIR) ++ clear_workfiles() ++ ++ def test_text_sig_crcr(self): ++ # Cover case with line ending with multiple CRs ++ srcsig = data_path(MSG_SIG_CRCR) ++ srctxt = data_path('test_messages/message.text-sig-crcr') ++ # Verify with RNP ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', data_path(KEY_ALICE_SUB_PUB), '-v', srcsig]) ++ self.assertEqual(ret, 0) ++ # Verify with GPG ++ if GPG_BRAINPOOL: ++ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import', data_path(KEY_ALICE_SUB_PUB)]) ++ self.assertEqual(ret, 0, GPG_IMPORT_FAILED) ++ gpg_verify_detached(srctxt, srcsig, KEY_ALICE) ++ ++ def test_encrypted_password_wrong(self): ++ # Test symmetric decryption with wrong password used ++ srcenc = data_path('test_messages/message.enc-password') ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', 'password1', '-d', srcenc]) ++ self.assertNotEqual(ret, 0) ++ self.assertIn('checksum check failed', err) ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', 'password', '-d', srcenc, '--output', 'decrypted']) ++ self.assertEqual(ret, 0) ++ os.remove('decrypted') ++ ++ def test_clearsign_long_lines(self): ++ # Cover case with cleartext signed file with long lines and filesize > 32k (buffer size) ++ [sig] = reg_workfiles('cleartext', '.sig') ++ srctxt = data_path('test_messages/message.4k-long-lines') ++ srcsig = data_path('test_messages/message.4k-long-lines.asc') ++ pubkey = data_path(KEY_ALICE_SUB_PUB) ++ seckey = data_path(KEY_ALICE_SUB_SEC) ++ # Verify already existing file ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', pubkey, '-v', srcsig]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Good signature made.*73edcc9119afc8e2dbbdcde50451409669ffde3c.*') ++ # Verify with gnupg ++ if GPG_BRAINPOOL: ++ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import', pubkey]) ++ self.assertEqual(ret, 0, GPG_IMPORT_FAILED) ++ gpg_verify_cleartext(srcsig, KEY_ALICE) ++ # Sign again with RNP ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', seckey, '--password', PASSWORD, '--clearsign', srctxt, '--output', sig]) ++ self.assertEqual(ret, 0) ++ # Verify with RNP again ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', pubkey, '-v', sig]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Good signature made.*73edcc9119afc8e2dbbdcde50451409669ffde3c.*') ++ # Verify with gnupg again ++ if GPG_BRAINPOOL: ++ gpg_verify_cleartext(sig, KEY_ALICE) ++ clear_workfiles() ++ ++ def test_eddsa_sig_lead_zero(self): ++ # Cover case with lead zeroes in EdDSA signature ++ srcs = data_path('test_messages/eddsa-zero-s.txt.sig') ++ srcr = data_path('test_messages/eddsa-zero-r.txt.sig') ++ # Verify with RNP ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', data_path(KEY_ALICE_SUB_PUB), '-v', srcs]) ++ self.assertEqual(ret, 0) ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', data_path(KEY_ALICE_SUB_PUB), '-v', srcr]) ++ self.assertEqual(ret, 0) ++ # Verify with GPG ++ if GPG_BRAINPOOL: ++ [dst] = reg_workfiles('eddsa-zero', '.txt') ++ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import', data_path(KEY_ALICE_SUB_PUB)]) ++ self.assertEqual(ret, 0, GPG_IMPORT_FAILED) ++ gpg_verify_file(srcs, dst, KEY_ALICE) ++ os.remove(dst) ++ gpg_verify_file(srcr, dst, KEY_ALICE) ++ clear_workfiles() ++ ++ def test_eddsa_seckey_lead_zero(self): ++ # Load and use *unencrypted* EdDSA secret key with 2 leading zeroes ++ seckey = data_path('test_stream_key_load/eddsa-00-sec.pgp') ++ pubkey = data_path('test_stream_key_load/eddsa-00-pub.pgp') ++ src, sig = reg_workfiles('source', '.txt', '.sig') ++ random_text(src, 2000) ++ ++ # Sign with RNP ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', seckey, '-s', src, '--output', sig]) ++ self.assertEqual(ret, 0) ++ # Verify with RNP ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', pubkey, '-v', sig]) ++ self.assertEqual(ret, 0) ++ # Verify with GnuPG ++ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import', pubkey]) ++ ret, _, err = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--verify', sig]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Signature made.*8BF2223370F61F8D965B.*Good signature from "eddsa-lead-zero".*$') ++ clear_workfiles() ++ ++ def test_verify_detached_source(self): ++ # Test --source parameter for the detached signature verification. ++ src = data_path(MSG_TXT) ++ sig = data_path(MSG_TXT + '.sig') ++ sigasc = data_path(MSG_TXT + '.asc') ++ keys = data_path(KEYRING_DIR_1) ++ # Just verify ++ ret, _, err = run_proc(RNP, ['--homedir', keys, '-v', sig]) ++ self.assertEqual(ret, 0) ++ R_GOOD = r'(?s)^.*Good signature made.*e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a.*' ++ self.assertRegex(err, R_GOOD) ++ # Verify .asc ++ ret, _, err = run_proc(RNP, ['--homedir', keys, '-v', sigasc]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, R_GOOD) ++ # Do not provide source ++ ret, _, err = run_proc(RNP, ['--homedir', keys, '-v', sig, '--source']) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*rnp(|\.exe): option( .--source.|) requires an argument.*Usage: rnp --command \[options\] \[files\].*') ++ # Verify by specifying the correct path ++ ret, _, err = run_proc(RNP, ['--homedir', keys, '--source', src, '-v', sig]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, R_GOOD) ++ # Verify by specifying the incorrect path ++ ret, _, err = run_proc(RNP, ['--homedir', keys, '--source', src + '.wrong', '-v', sig]) ++ self.assertNotEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Failed to open source for detached signature verification.*') ++ # Verify detached signature with non-asc/sig extension ++ [csig] = reg_workfiles('message', '.dat') ++ shutil.copy(sig, csig) ++ ret, _, err = run_proc(RNP, ['--homedir', keys, '-v', csig]) ++ self.assertNotEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Unsupported detached signature extension. Use --source to override.*') ++ # Verify by reading data from stdin ++ srcdata = "" ++ with open(src, "rb") as srcf: ++ srcdata = srcf.read().decode('utf-8') ++ ret, _, err = run_proc(RNP, ['--homedir', keys, '--source', '-', '-v', csig], srcdata) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, R_GOOD) ++ # Verify by reading data from env ++ os.environ["SIGNED_DATA"] = srcdata ++ ret, _, err = run_proc(RNP, ['--homedir', keys, '--source', 'env:SIGNED_DATA', '-v', csig]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, R_GOOD) ++ del os.environ["SIGNED_DATA"] ++ # Attempt to verify by specifying bot sig and data from stdin ++ sigtext = file_text(sigasc) ++ ret, _, err = run_proc(RNP, ['--homedir', keys, '--source', '-', '-v'], sigtext) ++ self.assertNotEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Detached signature and signed source cannot be both stdin.*') ++ ++ clear_workfiles() ++ ++ def test_onepass_edge_cases(self): ++ key = data_path('test_key_validity/alice-pub.asc') ++ onepass22 = data_path('test_messages/message.txt.signed-2-2-onepass-v10') ++ # Verify one-pass which doesn't match the signature - different keyid ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.signed-wrong-onepass')]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Warning: signature doesn\'t match one-pass.*Good signature made.*0451409669ffde3c.*') ++ # Verify one-pass with unknown hash algorithm ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.signed-unknown-onepass-hash')]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*Failed to create hash 136 for onepass 0.*') ++ # Verify one-pass with hash algorithm which doesn't match sig's one ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.signed-wrong-onepass-hash')]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*failed to get hash context.*BAD signature.*0451409669ffde3c.*') ++ # Extra one-pass without the corresponding signature ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.signed-2-onepass')]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Warning: premature end of signatures.*Good signature made.*0451409669ffde3c.*') ++ # Two one-passes and two equal signatures ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.signed-2-2-onepass')]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Good signature made.*0451409669ffde3c.*Good signature made.*0451409669ffde3c.*') ++ # Two one-passes and two sigs, but first one-pass is of unknown version ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', onepass22]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*wrong packet version.*warning: unexpected data on the stream end.*Good signature made.*0451409669ffde3c.*') ++ # Dump it as well ++ ret, out, err = run_proc(RNP, ['--list-packets', onepass22]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*wrong packet version.*failed to process packet.*') ++ self.assertRegex(out, r'(?s)^.*:off 0: packet header 0xc40d.*:off 15: packet header 0xc40d.*One-pass signature packet.*') ++ # Dump it in JSON ++ ret, out, err = run_proc(RNP, ['--list-packets', '--json', onepass22]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*wrong packet version.*failed to process packet.*') ++ self.assertRegex(out, r'(?s)^.*"offset":0.*"tag":4.*"offset":15.*"tag":4.*"version":3.*"nested":true.*') ++ # Two one-passes and sig of the unknown version ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.signed-2-2-sig-v10')]) ++ self.assertEqual(ret, 1) ++ R_VER_10 = r'(?s)^.*unknown signature version: 10.*failed to parse signature.*UNKNOWN signature.*Good signature made.*0451409669ffde3c.*' ++ R_1_UNK = r'(?s)^.*Signature verification failure: 1 unknown signature.*' ++ self.assertRegex(err, R_VER_10) ++ self.assertRegex(err, R_1_UNK) ++ # Two one-passes and sig of the unknown version (second) ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.signed-2-2-sig-v10-2')]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*unknown signature version: 10.*failed to parse signature.*Good signature made.*0451409669ffde3c.*UNKNOWN signature.*') ++ self.assertRegex(err, R_1_UNK) ++ # 2 detached signatures, first is of version 10 ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.2sigs'), '--source', data_path(MSG_TXT)]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, R_VER_10) ++ self.assertRegex(err, R_1_UNK) ++ # 2 detached signatures, second is of version 10 ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.2sigs-2'), '--source', data_path(MSG_TXT)]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*unknown signature version: 10.*failed to parse signature.*Good signature made.*0451409669ffde3c.*UNKNOWN signature.*') ++ self.assertRegex(err, R_1_UNK) ++ # Two cleartext signatures, first is of unknown version ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.clear-2-sigs')]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, R_VER_10) ++ self.assertRegex(err, R_1_UNK) ++ # Two cleartext signatures, second is of unknown version ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-v', data_path('test_messages/message.txt.clear-2-sigs-2')]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*unknown signature version: 11.*failed to parse signature.*Good signature made.*0451409669ffde3c.*UNKNOWN signature.*') ++ self.assertRegex(err, R_1_UNK) ++ ++ def test_pkesk_skesk_wrong_version(self): ++ key = data_path('test_stream_key_load/ecc-p256-sec.asc') ++ msg = data_path('test_messages/message.txt.pkesk-skesk-v10') ++ msg2 = data_path('test_messages/message.txt.pkesk-skesk-v10-only') ++ # Decrypt with secret key ++ ret, out, err = run_proc(RNP, ['--keyfile', key, '--password', PASSWORD, '-d', msg]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*This is test message to be signed, and/or encrypted, cleartext signed and detached signed.*') ++ self.assertRegex(err, r'(?s)^.*wrong packet version.*Failed to parse PKESK, skipping.*wrong packet version.*Failed to parse SKESK, skipping.*') ++ # Decrypt with password ++ ret, out, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '-d', msg]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*This is test message to be signed, and/or encrypted, cleartext signed and detached signed.*') ++ self.assertRegex(err, r'(?s)^.*wrong packet version.*Failed to parse PKESK, skipping.*wrong packet version.*Failed to parse SKESK, skipping.*') ++ # Attempt to decrypt message with only invalid PKESK/SKESK ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '--password', PASSWORD, '-d', msg2]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*wrong packet version.*Failed to parse PKESK, skipping.*wrong packet version.*Failed to parse SKESK, skipping.*failed to obtain decrypting key or password.*') ++ ++ def test_ext_adding_stripping(self): ++ # Check whether rnp correctly strip .pgp/.gpg/.asc extension ++ seckey = data_path('test_stream_key_load/ecc-p256-sec.asc') ++ pubkey = data_path('test_stream_key_load/ecc-p256-pub.asc') ++ src, src2, asc, pgp, gpg, some = reg_workfiles('cleartext', '.txt', '.txt2', '.txt.asc', '.txt.pgp', '.txt.gpg', '.txt.some') ++ with open(src, 'w+') as f: ++ f.write('Hello world') ++ # Encrypt with binary output ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', pubkey, '-e', src]) ++ self.assertEqual(ret, 0) ++ self.assertTrue(os.path.isfile(pgp)) ++ # Decrypt binary output, it must be put in cleartext.txt if it doesn't exists ++ os.remove(src) ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', seckey, '--password', PASSWORD, '-d', pgp]) ++ self.assertEqual(ret, 0) ++ self.assertTrue(os.path.isfile(src)) ++ # Decrypt binary output with the rename prompt ++ ret, out, _ = run_proc(RNP, ['--keyfile', seckey, '--password', PASSWORD, '--notty', '-d', pgp], "n\n" + src2 + "\n") ++ self.assertEqual(ret, 0) ++ self.assertTrue(os.path.isfile(src2)) ++ self.assertRegex(out, r'(?s)^.*File.*cleartext.txt.*already exists. Would you like to overwrite it.*Please enter the new filename:.*$') ++ self.assertIn(src, out) ++ self.assertTrue(os.path.isfile(src2)) ++ os.remove(src2) ++ # Rename from .pgp to .gpg and try again ++ os.remove(src) ++ os.rename(pgp, gpg) ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', seckey, '--password', PASSWORD, '-d', gpg]) ++ self.assertEqual(ret, 0) ++ self.assertTrue(os.path.isfile(src)) ++ # Rename from .pgp to .some and check that all is put in stdout ++ os.rename(gpg, some) ++ ret, out, _ = run_proc(RNP, ['--keyfile', seckey, '--password', PASSWORD, '--notty', '-d', some], "\n\n") ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^\s*Hello world\s*$') ++ os.remove(some) ++ # Encrypt with armored output ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', pubkey, '-e', src, '--armor']) ++ self.assertEqual(ret, 0) ++ self.assertTrue(os.path.isfile(asc)) ++ # Decrypt armored output, it must be put in cleartext.txt if it doesn't exists ++ os.remove(src) ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--keyfile', seckey, '--password', PASSWORD, '-d', asc]) ++ self.assertEqual(ret, 0) ++ self.assertTrue(os.path.isfile(src)) ++ # Enarmor - must be put in .asc file ++ os.remove(asc) ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--enarmor=msg', src]) ++ self.assertEqual(ret, 0) ++ self.assertTrue(os.path.isfile(asc)) ++ # Dearmor asc - must be outputed to src ++ os.remove(src) ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--dearmor', asc]) ++ self.assertEqual(ret, 0) ++ self.assertTrue(os.path.isfile(src)) ++ # Dearmor unknown extension - must be put to stdout ++ os.rename(asc, some) ++ ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '--dearmor', some]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^\s*Hello world\s*$') ++ ++ ++ def test_interactive_password(self): ++ # Reuse password for subkey, say "yes" ++ stdinstr = 'password\npassword\ny\n' ++ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--generate-key'], stdinstr) ++ self.assertEqual(ret, 0) ++ # Do not reuse same password for subkey, say "no" ++ stdinstr = 'password\npassword\nN\nsubkeypassword\nsubkeypassword\n' ++ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--generate-key'], stdinstr) ++ self.assertEqual(ret, 0) ++ # Set empty password and reuse it ++ stdinstr = '\n\ny\ny\n' ++ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--generate-key'], stdinstr) ++ self.assertEqual(ret, 0) ++ ++ def test_set_current_time(self): ++ RNP2 = RNPDIR + '2' ++ os.mkdir(RNP2, 0o700) ++ ++ # Generate key back in the past ++ ret, out, _ = run_proc(RNPK, ['--homedir', RNP2, '--notty', '--password', PASSWORD, '--generate-key', '--current-time', '2015-02-02', '--userid', 'key-2015']) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*Generating a new key\.\.\..*sec.*2015\-02\-0.*EXPIRES 2017\-.*ssb.*2015\-02\-0.*EXPIRES 2017\-.*$') ++ # List keys ++ ret, out, _ = run_proc(RNPK, ['--homedir', RNP2, '--notty', '--list-keys']) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*pub.*2015-02-0.*EXPIRED 2017.*sub.*2015-02-0.*\[INVALID\] \[EXPIRES 2017.*$') ++ ret, out, _ = run_proc(RNPK, ['--homedir', RNP2, '--notty', '--current-time', '2015-02-04', '--list-keys']) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*pub.*2015-02-0.*EXPIRES 2017.*sub.*2015-02-0.*EXPIRES 2017.*$') ++ # Create workfile ++ src, sig, enc = reg_workfiles('cleartext', '.txt', '.txt.sig', '.txt.enc') ++ with open(src, 'w+') as f: ++ f.write('Hello world') ++ # Sign with key from the past ++ ret, _, err = run_proc(RNP, ['--homedir', RNP2, '--password', PASSWORD, '-u', 'key-2015', '-s', src, '--output', sig]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*Failed to add signature.*$') ++ ret, _, _ = run_proc(RNP, ['--homedir', RNP2, '--password', PASSWORD, '-u', 'key-2015', '--current-time', '2015-02-03', '-s', src, '--output', sig]) ++ self.assertEqual(ret, 0) ++ # List packets ++ ret, out, _ = run_proc(RNP, ['--homedir', RNP2, '--list-packets', sig]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*signature creation time.*2015\).*signature expiration time.*$') ++ # Verify with the expired key ++ ret, out, err = run_proc(RNP, ['--homedir', RNP2, '-v', sig, '--output', '-']) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Good signature made.*2015.*pub.*\[SC\] \[EXPIRED 2017.*$') ++ self.assertRegex(out, r'(?s)^.*Hello world.*$') ++ # Encrypt with the expired key ++ ret, _, err = run_proc(RNP, ['--homedir', RNP2, '-r', 'key-2015', '-e', src, '--output', enc]) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*Operation failed: No suitable key.*$') ++ ret, _, _ = run_proc(RNP, ['--homedir', RNP2, '-r', 'key-2015', '--current-time', '2015-02-03', '-e', src, '--output', enc]) ++ self.assertEqual(ret, 0) ++ # Decrypt with the expired key ++ ret, out, _ = run_proc(RNP, ['--homedir', RNP2, '--password', PASSWORD, '-d', enc, '--output', '-']) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*Hello world.*$') ++ ++ shutil.rmtree(RNP2, ignore_errors=True) ++ clear_workfiles() ++ ++ def test_wrong_passfd(self): ++ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', '999', '--userid', ++ 'test_wrong_passfd', '--generate-key', '--expert'], '22\n') ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*Cannot open fd 999 for reading') ++ self.assertRegex(err, r'(?s)^.*fatal: failed to initialize rnpkeys') ++ ++ def test_keystore_formats(self): ++ # Use wrong keystore format ++ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--keystore-format', 'WRONG', '--list-keys']) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*Unsupported keystore format: "WRONG"') ++ # Use G10 keystore format ++ ret, _, err = run_proc(RNPK, ['--homedir', data_path(KEYRING_DIR_3), '--keystore-format', 'G10', '--list-keys']) ++ self.assertEqual(ret, 1) ++ self.assertRegex(err, r'(?s)^.*Warning: no keys were loaded from the keyring \'.*private-keys-v1.d\'') ++ # Use G21 keystore format ++ ret, out, _ = run_proc(RNPK, ['--homedir', data_path(KEYRING_DIR_3), '--keystore-format', 'GPG21', '--list-keys']) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*2 keys found') ++ ++ def test_no_twofish(self): ++ if (RNP_TWOFISH): ++ return ++ src, dst, dec = reg_workfiles('cleartext', '.txt', '.pgp', '.dec') ++ random_text(src, 100) ++ # Attempt to encrypt to twofish ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--cipher', 'twofish', '--output', dst, '-e', src]) ++ self.assertEqual(ret, 2) ++ self.assertFalse(os.path.isfile(dst)) ++ self.assertRegex(err, r'(?s)^.*Unsupported encryption algorithm: twofish') ++ # Symmetrically encrypt with GnuPG ++ gpg_symencrypt_file(src, dst, 'TWOFISH') ++ # Attempt to decrypt ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '--output', dec, '-d', dst]) ++ self.assertEqual(ret, 1) ++ self.assertFalse(os.path.isfile(dec)) ++ self.assertRegex(err, r'(?s)^.*failed to start cipher') ++ # Public-key encrypt with GnuPG ++ kpath = path_for_gpg(data_path(PUBRING_1)) ++ os.remove(dst) ++ ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--batch', '--keyring', kpath, '-r', 'key0-uid0', ++ '--trust-model', 'always', '--cipher-algo', 'TWOFISH', '--output', dst, '-e', src]) ++ self.assertEqual(ret, 0) ++ # Attempt to decrypt ++ ret, _, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--output', dec, '-d', dst]) ++ self.assertEqual(ret, 1) ++ self.assertFalse(os.path.isfile(dec)) ++ self.assertRegex(err, r'(?s)^.*unsupported symmetric algorithm 10') ++ clear_workfiles() ++ ++ def test_no_idea(self): ++ if (RNP_IDEA): ++ return ++ src, dst, dec = reg_workfiles('cleartext', '.txt', '.pgp', '.dec') ++ random_text(src, 100) ++ # Attempt to encrypt to twofish ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--cipher', 'idea', '--output', dst, '-e', src]) ++ self.assertEqual(ret, 2) ++ self.assertFalse(os.path.isfile(dst)) ++ self.assertRegex(err, r'(?s)^.*Unsupported encryption algorithm: idea') ++ # Symmetrically encrypt with GnuPG ++ gpg_symencrypt_file(src, dst, 'IDEA') ++ # Attempt to decrypt ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '--output', dec, '-d', dst]) ++ self.assertEqual(ret, 1) ++ self.assertFalse(os.path.isfile(dec)) ++ self.assertRegex(err, r'(?s)^.*failed to start cipher') ++ # Public-key encrypt with GnuPG ++ kpath = path_for_gpg(data_path(PUBRING_1)) ++ os.remove(dst) ++ params = ['--no-default-keyring', '--batch', '--keyring', kpath, '-r', 'key0-uid0', '--trust-model', 'always', '--cipher-algo', 'IDEA', '--output', dst, '-e', src] ++ if GPG_NO_OLD: ++ params.insert(1, '--allow-old-cipher-algos') ++ ret, _, _ = run_proc(GPG, params) ++ self.assertEqual(ret, 0) ++ # Attempt to decrypt ++ ret, _, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--output', dec, '-d', dst]) ++ self.assertEqual(ret, 1) ++ self.assertFalse(os.path.isfile(dec)) ++ self.assertRegex(err, r'(?s)^.*unsupported symmetric algorithm 1') ++ # List secret key, encrypted with IDEA ++ ret, out, err = run_proc(RNP, ['--homedir', RNPDIR, '--list-packets', data_path('keyrings/4/rsav3-s.asc')]) ++ self.assertEqual(ret, 0) ++ self.assertNotRegex(out, r'(?s)^.*failed to process packet') ++ self.assertRegex(out, r'(?s)^.*secret key material.*symmetric algorithm: 1 .IDEA.') ++ # Import secret key - must succeed. ++ RNP2 = RNPDIR + '2' ++ os.mkdir(RNP2, 0o700) ++ ret, out, err = run_proc(RNPK, ['--homedir', RNP2, '--import', data_path('keyrings/4/rsav3-s.asc')]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*sec.*7d0bc10e933404c9.*INVALID') ++ shutil.rmtree(RNP2, ignore_errors=True) ++ clear_workfiles() + + class Encryption(unittest.TestCase): + ''' + Things to try later: + - different public key algorithms +@@ -2636,10 +3295,12 @@ class Encryption(unittest.TestCase): + rnp_genkey_rsa('dummy2@rnp', 1024) + gpg_import_pubring() + gpg_import_secring() + if not RNP_TWOFISH: + Encryption.CIPHERS.remove('TWOFISH') ++ if not RNP_IDEA: ++ Encryption.CIPHERS.remove('IDEA') + Encryption.CIPHERS_R = list_upto(Encryption.CIPHERS, Encryption.RUNS) + Encryption.SIZES_R = list_upto(Encryption.SIZES, Encryption.RUNS) + Encryption.Z_R = list_upto(Encryption.Z, Encryption.RUNS) + + @classmethod +@@ -2684,10 +3345,28 @@ class Encryption(unittest.TestCase): + # Encrypt and decrypt cleartext using the AEAD + for size, cipher, aead, bits, z in zip(Encryption.SIZES_R, AEAD_C, + AEAD_M, AEAD_B, Encryption.Z_R): + rnp_sym_encryption_rnp_aead(size, cipher, z, [aead, bits], GPG_AEAD) + ++ def test_aead_chunk_edge_cases(self): ++ if not RNP_AEAD: ++ print('AEAD is not available for RNP - skipping.') ++ return ++ src, dst, enc = reg_workfiles('cleartext', '.txt', '.rnp', '.enc') ++ # Cover lines from src_skip() where > 16 bytes must be skipped ++ random_text(src, 1001) ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '--output', enc, '--aead=eax', '--aead-chunk-bits', '2', '-z', '0', '-c', src]) ++ self.assertEqual(ret, 0) ++ rnp_decrypt_file(enc, dst) ++ remove_files(src, dst, enc) ++ # Cover case with AEAD chunk start on the data end ++ random_text(src, 1002) ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '--output', enc, '--aead=eax', '--aead-chunk-bits', '2', '-z', '0', '-c', src]) ++ self.assertEqual(ret, 0) ++ rnp_decrypt_file(enc, dst) ++ remove_files(src, dst, enc) ++ + def test_encryption_multiple_recipients(self): + USERIDS = ['key1@rnp', 'key2@rnp', 'key3@rnp'] + KEYPASS = ['key1pass', 'key2pass', 'key3pass'] + PASSWORDS = ['password1', 'password2', 'password3'] + # Generate multiple keys and import to GnuPG +@@ -2897,11 +3576,11 @@ class Encryption(unittest.TestCase): + ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '-d', data_path(MSG_ES_25519)]) + self.assertEqual(ret, 0) + self.assertNotRegex(err, BITS_MSG) + ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', 'eddsa-25519-non-tweaked', '--force']) + self.assertEqual(ret, 0) +- # Due to issue in GnuPG it reports successfull import of non-tweaked secret key in batch mode ++ # Due to issue in GnuPG it reports successful import of non-tweaked secret key in batch mode + ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import', data_path(KEY_25519_NOTWEAK_SEC)]) + self.assertEqual(ret, 0) + ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '-d', data_path(MSG_ES_25519)]) + self.assertNotEqual(ret, 0) + ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--yes', '--delete-secret-key', 'dde0ee539c017d2bd3f604a53176fc1486aa2528']) +@@ -2942,13 +3621,76 @@ class Encryption(unittest.TestCase): + '-u', 'eddsa_25519', '--output', dst, '-es', src]) + self.assertEqual(ret, 0) + # Decrypt and verify with RNP + rnp_decrypt_file(dst, dec, 'password') + self.assertEqual(file_text(src), file_text(dec)) ++ # Encrypt/decrypt using the p256 key, making sure message is not displayed ++ key = data_path('test_stream_key_load/ecc-p256-sec.asc') ++ remove_files(dst, dec) ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-es', '-r', 'ecc-p256', '-u', 'ecc-p256', '--password', PASSWORD, src, '--output', dst]) ++ self.assertEqual(ret, 0) ++ self.assertNotRegex(err, BITS_MSG) ++ ret, _, err = run_proc(RNP, ['--keyfile', key, '-d', '--password', PASSWORD, dst, '--output', dec]) ++ self.assertEqual(ret, 0) ++ self.assertNotRegex(err, BITS_MSG) + # Cleanup + clear_workfiles() + ++ def test_encryption_aead_defs(self): ++ if not RNP_AEAD or not RNP_BRAINPOOL: ++ return ++ # Encrypt with RNP ++ pubkey = data_path(KEY_ALICE_SUB_PUB) ++ src, enc, dec = reg_workfiles('cleartext', '.txt', '.enc', '.dec') ++ random_text(src, 120000) ++ ret, _, _ = run_proc(RNP, ['--keyfile', pubkey, '-z', '0', '-r', 'alice', '--aead', '-e', src, '--output', enc]) ++ self.assertEqual(ret, 0) ++ # List packets ++ ret, out, _ = run_proc(RNP, ['--list-packets', enc]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(out, r'(?s)^.*tag 20, partial len.*AEAD-encrypted data packet.*version: 1.*AES-256.*EAX.*chunk size: 12.*') ++ # Attempt to encrypt with too high AEAD bits value ++ ret, _, err = run_proc(RNP, ['--keyfile', pubkey, '-r', 'alice', '--aead', '--aead-chunk-bits', '17', '-e', src, '--output', enc]) ++ self.assertEqual(ret, 2) ++ self.assertRegex(err, r'(?s)^.*Wrong argument value 17 for aead-chunk-bits, must be 0..16.*') ++ # Attempt to encrypt with wrong AEAD bits value ++ ret, _, err = run_proc(RNP, ['--keyfile', pubkey, '-r', 'alice', '--aead', '--aead-chunk-bits', 'banana', '-e', src, '--output', enc]) ++ self.assertEqual(ret, 2) ++ self.assertRegex(err, r'(?s)^.*Wrong argument value banana for aead-chunk-bits, must be 0..16.*') ++ # Attempt to encrypt with another wrong AEAD bits value ++ ret, _, err = run_proc(RNP, ['--keyfile', pubkey, '-r', 'alice', '--aead', '--aead-chunk-bits', '5banana', '-e', src, '--output', enc]) ++ self.assertEqual(ret, 2) ++ self.assertRegex(err, r'(?s)^.*Wrong argument value 5banana for aead-chunk-bits, must be 0..16.*') ++ clear_workfiles() ++ ++ def test_encryption_no_wrap(self): ++ src, sig, enc, dec = reg_workfiles('cleartext', '.txt', '.sig', '.enc', '.dec') ++ random_text(src, 2000) ++ # Sign with GnuPG ++ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', PASSWORD, '-u', KEY_ENCRYPT, '--output', sig, '-s', src]) ++ # Additionally encrypt with RNP ++ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '-r', 'dummy1@rnp', '--no-wrap', '-e', sig, '--output', enc]) ++ self.assertEqual(ret, 0) ++ # List packets ++ ret, out, err = run_proc(GPG, ['--batch', '--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', PASSWORD, '--list-packets', enc]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*gpg: encrypted with .*dummy1@rnp.*') ++ self.assertRegex(out, r'(?s)^.*:pubkey enc packet: version 3.*:encrypted data packet:.*mdc_method: 2.*' \ ++ r':compressed packet.*:onepass_sig packet:.*:literal data packet.*:signature packet.*') ++ # Decrypt with GnuPG ++ ret, _, err = run_proc(GPG, ['--batch', '--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', PASSWORD, '--output', dec, '-d', enc]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*gpg: encrypted with .*dummy1@rnp.*gpg: Good signature from "encryption@rnp".*') ++ self.assertEqual(file_text(dec), file_text(src)) ++ remove_files(dec) ++ # Decrypt with RNP ++ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '--output', dec, '-d', enc]) ++ self.assertEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Good signature.*uid\s+encryption@rnp.*Signature\(s\) verified successfully.*') ++ self.assertEqual(file_text(dec), file_text(src)) ++ clear_workfiles() ++ + class Compression(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Compression is currently implemented only for encrypted messages + rnp_genkey_rsa(KEY_ENCRYPT) +@@ -3088,10 +3830,16 @@ class SignDefault(unittest.TestCase): + rnp_verify_cleartext(dst) + remove_files(dst) + + clear_workfiles() + ++ def test_verify_bad_sig_class(self): ++ ret, _, err = run_proc(RNP, ['--keyfile', data_path(KEY_ALICE_SEC), '--verify', data_path('test_messages/message.txt.signed-class19')]) ++ self.assertNotEqual(ret, 0) ++ self.assertRegex(err, r'(?s)^.*Invalid document signature type: 19.*') ++ self.assertNotRegex(err, r'(?s)^.*Good signature.*') ++ self.assertRegex(err, r'(?s)^.*BAD signature.*Signature verification failure: 1 invalid signature') + + class Encrypt(unittest.TestCase, TestIdMixin, KeyLocationChooserMixin): + def _encrypt_decrypt(self, e1, e2, failenc = False, faildec = False): + keyfile, src, enc_out, dec_out = reg_workfiles(self.test_id, '.gpg', + '.in', '.enc', '.dec') +@@ -3145,12 +3893,12 @@ class EncryptElgamal(Encrypt): + + def do_test_encrypt(self, sign_key_size, enc_key_size): + pfx = EncryptElgamal.key_pfx(sign_key_size, enc_key_size) + self.operation_key_location = tuple((key_path(pfx, False), key_path(pfx, True))) + self.rnp.userid = self.gpg.userid = pfx + AT_EXAMPLE +- # DSA 1024 key uses SHA-1 as hash so verification would fail +- self._encrypt_decrypt(self.gpg, self.rnp, sign_key_size <= 1024, sign_key_size <= 1024) ++ # DSA 1024 key uses SHA-1 as hash but verification would succeed till 2024 ++ self._encrypt_decrypt(self.gpg, self.rnp) + + def do_test_decrypt(self, sign_key_size, enc_key_size): + pfx = EncryptElgamal.key_pfx(sign_key_size, enc_key_size) + self.operation_key_location = tuple((key_path(pfx, False), key_path(pfx, True))) + self.rnp.userid = self.gpg.userid = pfx + AT_EXAMPLE +@@ -3165,12 +3913,12 @@ class EncryptElgamal(Encrypt): + def test_decrypt_P1234_1234(self): self.do_test_decrypt(1234, 1234) + + def test_generate_elgamal_key1024_in_gpg_and_encrypt(self): + cmd = EncryptElgamal.GPG_GENERATE_DSA_ELGAMAL_PATTERN.format(1024, 1024, self.gpg.userid) + self.operation_key_gencmd = cmd +- # Will fail since 1024-bit DSA key uses SHA-1 as hash. +- self._encrypt_decrypt(self.gpg, self.rnp, True, True) ++ # Will not fail till 2024 since 1024-bit DSA key uses SHA-1 as hash. ++ self._encrypt_decrypt(self.gpg, self.rnp) + + def test_generate_elgamal_key1536_in_gpg_and_encrypt(self): + cmd = EncryptElgamal.GPG_GENERATE_DSA_ELGAMAL_PATTERN.format(1536, 1536, self.gpg.userid) + self.operation_key_gencmd = cmd + self._encrypt_decrypt(self.gpg, self.rnp) +@@ -3342,18 +4090,18 @@ class SignDSA(Sign): + + def do_test_sign(self, p_size): + pfx = SignDSA.key_pfx(p_size) + self.operation_key_location = tuple((key_path(pfx, False), key_path(pfx, True))) + self.rnp.userid = self.gpg.userid = pfx + AT_EXAMPLE +- # DSA 1024-bit key uses SHA-1 so verification would fail +- self._sign_verify(self.rnp, self.gpg, p_size <= 1024, p_size <= 1024) ++ # DSA 1024-bit key uses SHA-1 so verification would not fail till 2024 ++ self._sign_verify(self.rnp, self.gpg) + + def do_test_verify(self, p_size): + pfx = SignDSA.key_pfx(p_size) + self.operation_key_location = tuple((key_path(pfx, False), key_path(pfx, True))) + self.rnp.userid = self.gpg.userid = pfx + AT_EXAMPLE +- # DSA 1024-bit key uses SHA-1 so verification would fail ++ # DSA 1024-bit key uses SHA-1, but verification would fail since SHA1 is used by GnuPG + self._sign_verify(self.gpg, self.rnp, False, p_size <= 1024) + + def test_sign_P1024_Q160(self): self.do_test_sign(1024) + def test_sign_P2048_Q256(self): self.do_test_sign(2048) + def test_sign_P3072_Q256(self): self.do_test_sign(3072) +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys +@@ -1,11 +1,11 @@ + 23 keys found + + pub 2048/DSA c8a10a7d78273e10 2018-04-03 [SC] + 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10 + uid dsa-eg +-sub 3072/Elgamal (Encrypt-Only) 02a5715c3537717e 2018-04-03 [E] ++sub 3072/ElGamal 02a5715c3537717e 2018-04-03 [E] + 3409f96f0c57242540702dba02a5715c3537717e + + pub 255/EdDSA cc786278981b0728 2018-04-03 [SC] + 21fc68274aae3b5de39a4277cc786278981b0728 + uid ecc-25519 +@@ -26,14 +26,14 @@ pub 521/ECDSA 2092ca8324263b6a 2018-04 + 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a + uid ecc-p521 + sub 521/ECDH 9853df2f6d297442 2018-04-03 [E] + a9297c86dd0de109e1ebae9c9853df2f6d297442 + +-pub 3072/RSA (Encrypt or Sign) 2fb9179118898e8b 2018-04-03 [ESCA] ++pub 3072/RSA 2fb9179118898e8b 2018-04-03 [SC] + 6bc04a5a3ddb35766b9a40d82fb9179118898e8b + uid rsa-rsa +-sub 3072/RSA (Encrypt or Sign) 6e2f73008f8b8d6e 2018-04-03 [ESCA] ++sub 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E] + 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e + + pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SC] + 0633c5f72a198f51e650e4abd0c8a3daf9e0634a + uid ecc-bp256 +@@ -62,11 +62,11 @@ pub 256/ECDSA 3ea5bb6f9692c1a0 2018-04 + 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0 + uid ecc-p256k1 + sub 256/ECDH 7635401f90d3e533 2018-04-03 [E] + c263ec4ce2b3772746ed53227635401f90d3e533 + +-pub 2048/RSA (Encrypt or Sign) bd860a52d1899c0f 2021-12-24 [SC] ++pub 2048/RSA bd860a52d1899c0f 2021-12-24 [SC] + 5aa9362aea07de23a726762cbd860a52d1899c0f + uid rsa-rsa-2 +-sub 2048/RSA (Encrypt or Sign) 8e08d46a37414996 2021-12-24 [E] ++sub 2048/RSA 8e08d46a37414996 2021-12-24 [E] + ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_no_bp b/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_no_b/commp +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_no_bp ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_no_bp +@@ -1,11 +1,11 @@ + 23 keys found + + pub 2048/DSA c8a10a7d78273e10 2018-04-03 [SC] + 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10 + uid dsa-eg +-sub 3072/Elgamal (Encrypt-Only) 02a5715c3537717e 2018-04-03 [E] ++sub 3072/ElGamal 02a5715c3537717e 2018-04-03 [E] + 3409f96f0c57242540702dba02a5715c3537717e + + pub 255/EdDSA cc786278981b0728 2018-04-03 [SC] + 21fc68274aae3b5de39a4277cc786278981b0728 + uid ecc-25519 +@@ -26,32 +26,32 @@ pub 521/ECDSA 2092ca8324263b6a 2018-04 + 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a + uid ecc-p521 + sub 521/ECDH 9853df2f6d297442 2018-04-03 [E] + a9297c86dd0de109e1ebae9c9853df2f6d297442 + +-pub 3072/RSA (Encrypt or Sign) 2fb9179118898e8b 2018-04-03 [ESCA] ++pub 3072/RSA 2fb9179118898e8b 2018-04-03 [SC] + 6bc04a5a3ddb35766b9a40d82fb9179118898e8b + uid rsa-rsa +-sub 3072/RSA (Encrypt or Sign) 6e2f73008f8b8d6e 2018-04-03 [ESCA] ++sub 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E] + 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e + +-pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SCA] ++pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [INVALID] + 0633c5f72a198f51e650e4abd0c8a3daf9e0634a +-uid ecc-bp256 +-sub 256/ECDH 2edabb94d3055f76 2018-04-03 [E] ++uid ecc-bp256 [INVALID] ++sub 256/ECDH 2edabb94d3055f76 2018-04-03 [INVALID] + 08192b478f740360b74c82cc2edabb94d3055f76 + +-pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [SCA] ++pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [INVALID] + 5b8a254c823ced98decd10ed6cf2dce85599ada2 +-uid ecc-bp384 +-sub 384/ECDH cff1bb6f16d28191 2018-04-03 [E] ++uid ecc-bp384 [INVALID] ++sub 384/ECDH cff1bb6f16d28191 2018-04-03 [INVALID] + 76969ce7033d990931df92b2cff1bb6f16d28191 + +-pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [SCA] ++pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [INVALID] + 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48 +-uid ecc-bp512 +-sub 512/ECDH 20cdaa1482ba79ce 2018-04-03 [E] ++uid ecc-bp512 [INVALID] ++sub 512/ECDH 20cdaa1482ba79ce 2018-04-03 [INVALID] + 270a7cd0dc6c2e01dce8603620cdaa1482ba79ce + + pub 255/EdDSA 941822a0fc1b30a5 2018-10-15 [SC] + 4c9738a6f2be4e1a796c9b7b941822a0fc1b30a5 + uid eddsa-x25519 +@@ -62,11 +62,11 @@ pub 256/ECDSA 3ea5bb6f9692c1a0 2018-04 + 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0 + uid ecc-p256k1 + sub 256/ECDH 7635401f90d3e533 2018-04-03 [E] + c263ec4ce2b3772746ed53227635401f90d3e533 + +-pub 2048/RSA (Encrypt or Sign) bd860a52d1899c0f 2021-12-24 [SC] ++pub 2048/RSA bd860a52d1899c0f 2021-12-24 [SC] + 5aa9362aea07de23a726762cbd860a52d1899c0f + uid rsa-rsa-2 +-sub 2048/RSA (Encrypt or Sign) 8e08d46a37414996 2021-12-24 [E] ++sub 2048/RSA 8e08d46a37414996 2021-12-24 [E] + ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec +@@ -1,11 +1,11 @@ + 21 keys found + + sec 2048/DSA c8a10a7d78273e10 2018-04-03 [SC] + 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10 + uid dsa-eg +-ssb 3072/Elgamal (Encrypt-Only) 02a5715c3537717e 2018-04-03 [E] ++ssb 3072/ElGamal 02a5715c3537717e 2018-04-03 [E] + 3409f96f0c57242540702dba02a5715c3537717e + + sec 255/EdDSA cc786278981b0728 2018-04-03 [SC] + 21fc68274aae3b5de39a4277cc786278981b0728 + uid ecc-25519 +@@ -26,14 +26,14 @@ sec 521/ECDSA 2092ca8324263b6a 2018-04 + 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a + uid ecc-p521 + ssb 521/ECDH 9853df2f6d297442 2018-04-03 [E] + a9297c86dd0de109e1ebae9c9853df2f6d297442 + +-sec 3072/RSA (Encrypt or Sign) 2fb9179118898e8b 2018-04-03 [ESCA] ++sec 3072/RSA 2fb9179118898e8b 2018-04-03 [SC] + 6bc04a5a3ddb35766b9a40d82fb9179118898e8b + uid rsa-rsa +-ssb 3072/RSA (Encrypt or Sign) 6e2f73008f8b8d6e 2018-04-03 [ESCA] ++ssb 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E] + 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e + + sec 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SC] + 0633c5f72a198f51e650e4abd0c8a3daf9e0634a + uid ecc-bp256 +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec_no_bp b/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec_no_b/commp +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec_no_bp ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec_no_bp +@@ -1,11 +1,11 @@ + 21 keys found + + sec 2048/DSA c8a10a7d78273e10 2018-04-03 [SC] + 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10 + uid dsa-eg +-ssb 3072/Elgamal (Encrypt-Only) 02a5715c3537717e 2018-04-03 [E] ++ssb 3072/ElGamal 02a5715c3537717e 2018-04-03 [E] + 3409f96f0c57242540702dba02a5715c3537717e + + sec 255/EdDSA cc786278981b0728 2018-04-03 [SC] + 21fc68274aae3b5de39a4277cc786278981b0728 + uid ecc-25519 +@@ -26,32 +26,32 @@ sec 521/ECDSA 2092ca8324263b6a 2018-04 + 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a + uid ecc-p521 + ssb 521/ECDH 9853df2f6d297442 2018-04-03 [E] + a9297c86dd0de109e1ebae9c9853df2f6d297442 + +-sec 3072/RSA (Encrypt or Sign) 2fb9179118898e8b 2018-04-03 [ESCA] ++sec 3072/RSA 2fb9179118898e8b 2018-04-03 [SC] + 6bc04a5a3ddb35766b9a40d82fb9179118898e8b + uid rsa-rsa +-ssb 3072/RSA (Encrypt or Sign) 6e2f73008f8b8d6e 2018-04-03 [ESCA] ++ssb 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E] + 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e + +-sec 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SCA] ++sec 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [INVALID] + 0633c5f72a198f51e650e4abd0c8a3daf9e0634a +-uid ecc-bp256 +-ssb 256/ECDH 2edabb94d3055f76 2018-04-03 [E] ++uid ecc-bp256 [INVALID] ++ssb 256/ECDH 2edabb94d3055f76 2018-04-03 [INVALID] + 08192b478f740360b74c82cc2edabb94d3055f76 + +-sec 384/ECDSA 6cf2dce85599ada2 2018-04-03 [SCA] ++sec 384/ECDSA 6cf2dce85599ada2 2018-04-03 [INVALID] + 5b8a254c823ced98decd10ed6cf2dce85599ada2 +-uid ecc-bp384 +-ssb 384/ECDH cff1bb6f16d28191 2018-04-03 [E] ++uid ecc-bp384 [INVALID] ++ssb 384/ECDH cff1bb6f16d28191 2018-04-03 [INVALID] + 76969ce7033d990931df92b2cff1bb6f16d28191 + +-sec 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [SCA] ++sec 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [INVALID] + 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48 +-uid ecc-bp512 +-ssb 512/ECDH 20cdaa1482ba79ce 2018-04-03 [E] ++uid ecc-bp512 [INVALID] ++ssb 512/ECDH 20cdaa1482ba79ce 2018-04-03 [INVALID] + 270a7cd0dc6c2e01dce8603620cdaa1482ba79ce + + sec 255/EdDSA 941822a0fc1b30a5 2018-10-15 [SC] + 4c9738a6f2be4e1a796c9b7b941822a0fc1b30a5 + uid eddsa-x25519 +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb b/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb/comm +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb +@@ -3,10 +3,10 @@ 3 keys found + pub 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES 2083-05-11] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + uid key1-uid2 + uid key1-uid1 +-sub 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] ++sub 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] + a3e94de61a8cb229413d348e54505a936a4a970e +-sub 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec b/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb/comm_sec +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec +@@ -3,10 +3,10 @@ 3 keys found + sec 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES 2083-05-11] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + uid key1-uid2 + uid key1-uid1 +-ssb 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] ++ssb 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] + a3e94de61a8cb229413d348e54505a936a4a970e +-ssb 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++ssb 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec_y2k38 b/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb/comm_sec_y2k38 +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec_y2k38 ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec_y2k38 +@@ -3,10 +3,10 @@ 3 keys found + sec 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES >=2038-01-19] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + uid key1-uid2 + uid key1-uid1 +-ssb 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] ++ssb 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] + a3e94de61a8cb229413d348e54505a936a4a970e +-ssb 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++ssb 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig b/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb/comm_sig +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig +@@ -6,10 +6,12 @@ uid key1-uid0 + sig 2fcadf05ffa501bb 2017-07-29 key1-uid0 + uid key1-uid2 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + uid key1-uid1 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 +-sub 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] ++sub 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] + a3e94de61a8cb229413d348e54505a936a4a970e +-sub 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 ++sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig_y2k38 b/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb/comm_sig_y2k38 +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig_y2k38 ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig_y2k38 +@@ -6,10 +6,12 @@ uid key1-uid0 + sig 2fcadf05ffa501bb 2017-07-29 key1-uid0 + uid key1-uid2 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + uid key1-uid1 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 +-sub 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] ++sub 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] + a3e94de61a8cb229413d348e54505a936a4a970e +-sub 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 ++sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_y2k38 b/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb/comm_y2k38 +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_y2k38 ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_y2k38 +@@ -3,10 +3,10 @@ 3 keys found + pub 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES >=2038-01-19] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + uid key1-uid2 + uid key1-uid1 +-sub 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] ++sub 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] + a3e94de61a8cb229413d348e54505a936a4a970e +-sub 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys +@@ -1,24 +1,24 @@ + 7 keys found + +-pub 1024/RSA (Encrypt or Sign) 7bc6709b15c23a4a 2017-07-20 [SC] ++pub 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC] + e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a + uid key0-uid0 + uid key0-uid1 + uid key0-uid2 +-sub 1024/RSA (Encrypt or Sign) 1ed63ee56fadc34d 2017-07-20 [E] ++sub 1024/RSA 1ed63ee56fadc34d 2017-07-20 [E] + e332b27caf4742a11baa677f1ed63ee56fadc34d + sub 1024/DSA 1d7e8a5393c997a8 2017-07-20 [S] [EXPIRED 2017-11-20] + c5b15209940a7816a7af3fb51d7e8a5393c997a8 +-sub 1024/RSA (Encrypt or Sign) 8a05b89fad5aded1 2017-07-20 [E] ++sub 1024/RSA 8a05b89fad5aded1 2017-07-20 [E] + 5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1 + + pub 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES 2083-05-11] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + uid key1-uid2 + uid key1-uid1 +-sub 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] ++sub 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] + a3e94de61a8cb229413d348e54505a936a4a970e +-sub 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec +@@ -1,24 +1,24 @@ + 7 keys found + +-sec 1024/RSA (Encrypt or Sign) 7bc6709b15c23a4a 2017-07-20 [SC] ++sec 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC] + e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a + uid key0-uid0 + uid key0-uid1 + uid key0-uid2 +-ssb 1024/RSA (Encrypt or Sign) 1ed63ee56fadc34d 2017-07-20 [E] ++ssb 1024/RSA 1ed63ee56fadc34d 2017-07-20 [E] + e332b27caf4742a11baa677f1ed63ee56fadc34d + ssb 1024/DSA 1d7e8a5393c997a8 2017-07-20 [S] [EXPIRED 2017-11-20] + c5b15209940a7816a7af3fb51d7e8a5393c997a8 +-ssb 1024/RSA (Encrypt or Sign) 8a05b89fad5aded1 2017-07-20 [E] ++ssb 1024/RSA 8a05b89fad5aded1 2017-07-20 [E] + 5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1 + + sec 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES 2083-05-11] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + uid key1-uid2 + uid key1-uid1 +-ssb 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] ++ssb 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] + a3e94de61a8cb229413d348e54505a936a4a970e +-ssb 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++ssb 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec_y2k38 b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec_y2k38 +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec_y2k38 ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec_y2k38 +@@ -1,24 +1,24 @@ + 7 keys found + +-sec 1024/RSA (Encrypt or Sign) 7bc6709b15c23a4a 2017-07-20 [SC] ++sec 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC] + e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a + uid key0-uid0 + uid key0-uid1 + uid key0-uid2 +-ssb 1024/RSA (Encrypt or Sign) 1ed63ee56fadc34d 2017-07-20 [E] ++ssb 1024/RSA 1ed63ee56fadc34d 2017-07-20 [E] + e332b27caf4742a11baa677f1ed63ee56fadc34d + ssb 1024/DSA 1d7e8a5393c997a8 2017-07-20 [S] [EXPIRED 2017-11-20] + c5b15209940a7816a7af3fb51d7e8a5393c997a8 +-ssb 1024/RSA (Encrypt or Sign) 8a05b89fad5aded1 2017-07-20 [E] ++ssb 1024/RSA 8a05b89fad5aded1 2017-07-20 [E] + 5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1 + + sec 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES >=2038-01-19] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + uid key1-uid2 + uid key1-uid1 +-ssb 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] ++ssb 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] + a3e94de61a8cb229413d348e54505a936a4a970e +-ssb 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++ssb 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_y2k38 b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_y2k38 +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_y2k38 ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_y2k38 +@@ -1,24 +1,24 @@ + 7 keys found + +-pub 1024/RSA (Encrypt or Sign) 7bc6709b15c23a4a 2017-07-20 [SC] ++pub 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC] + e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a + uid key0-uid0 + uid key0-uid1 + uid key0-uid2 +-sub 1024/RSA (Encrypt or Sign) 1ed63ee56fadc34d 2017-07-20 [E] ++sub 1024/RSA 1ed63ee56fadc34d 2017-07-20 [E] + e332b27caf4742a11baa677f1ed63ee56fadc34d + sub 1024/DSA 1d7e8a5393c997a8 2017-07-20 [S] [EXPIRED 2017-11-20] + c5b15209940a7816a7af3fb51d7e8a5393c997a8 +-sub 1024/RSA (Encrypt or Sign) 8a05b89fad5aded1 2017-07-20 [E] ++sub 1024/RSA 8a05b89fad5aded1 2017-07-20 [E] + 5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1 + + pub 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES >=2038-01-19] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + uid key1-uid2 + uid key1-uid1 +-sub 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] ++sub 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] + a3e94de61a8cb229413d348e54505a936a4a970e +-sub 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs +@@ -1,30 +1,35 @@ + 7 keys found + +-pub 1024/RSA (Encrypt or Sign) 7bc6709b15c23a4a 2017-07-20 [SC] ++pub 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC] + e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a + uid key0-uid0 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + uid key0-uid1 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + uid key0-uid2 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 +-sub 1024/RSA (Encrypt or Sign) 1ed63ee56fadc34d 2017-07-20 [E] ++sub 1024/RSA 1ed63ee56fadc34d 2017-07-20 [E] + e332b27caf4742a11baa677f1ed63ee56fadc34d ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + sub 1024/DSA 1d7e8a5393c997a8 2017-07-20 [S] [EXPIRED 2017-11-20] + c5b15209940a7816a7af3fb51d7e8a5393c997a8 +-sub 1024/RSA (Encrypt or Sign) 8a05b89fad5aded1 2017-07-20 [E] ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 ++sub 1024/RSA 8a05b89fad5aded1 2017-07-20 [E] + 5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1 ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + + pub 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES 2083-05-11] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + sig 2fcadf05ffa501bb 2017-07-29 key1-uid0 + uid key1-uid2 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + uid key1-uid1 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 +-sub 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] ++sub 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] + a3e94de61a8cb229413d348e54505a936a4a970e +-sub 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 ++sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec +@@ -1,30 +1,35 @@ + 7 keys found + +-sec 1024/RSA (Encrypt or Sign) 7bc6709b15c23a4a 2017-07-20 [SC] ++sec 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC] + e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a + uid key0-uid0 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + uid key0-uid1 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + uid key0-uid2 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 +-ssb 1024/RSA (Encrypt or Sign) 1ed63ee56fadc34d 2017-07-20 [E] ++ssb 1024/RSA 1ed63ee56fadc34d 2017-07-20 [E] + e332b27caf4742a11baa677f1ed63ee56fadc34d ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + ssb 1024/DSA 1d7e8a5393c997a8 2017-07-20 [S] [EXPIRED 2017-11-20] + c5b15209940a7816a7af3fb51d7e8a5393c997a8 +-ssb 1024/RSA (Encrypt or Sign) 8a05b89fad5aded1 2017-07-20 [E] ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 ++ssb 1024/RSA 8a05b89fad5aded1 2017-07-20 [E] + 5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1 ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + + sec 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES 2083-05-11] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + sig 2fcadf05ffa501bb 2017-07-29 key1-uid0 + uid key1-uid2 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + uid key1-uid1 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 +-ssb 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] ++ssb 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] + a3e94de61a8cb229413d348e54505a936a4a970e +-ssb 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 ++ssb 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec_y2k38 b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec_y2k38 +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec_y2k38 ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec_y2k38 +@@ -1,30 +1,35 @@ + 7 keys found + +-sec 1024/RSA (Encrypt or Sign) 7bc6709b15c23a4a 2017-07-20 [SC] ++sec 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC] + e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a + uid key0-uid0 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + uid key0-uid1 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + uid key0-uid2 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 +-ssb 1024/RSA (Encrypt or Sign) 1ed63ee56fadc34d 2017-07-20 [E] ++ssb 1024/RSA 1ed63ee56fadc34d 2017-07-20 [E] + e332b27caf4742a11baa677f1ed63ee56fadc34d ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + ssb 1024/DSA 1d7e8a5393c997a8 2017-07-20 [S] [EXPIRED 2017-11-20] + c5b15209940a7816a7af3fb51d7e8a5393c997a8 +-ssb 1024/RSA (Encrypt or Sign) 8a05b89fad5aded1 2017-07-20 [E] ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 ++ssb 1024/RSA 8a05b89fad5aded1 2017-07-20 [E] + 5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1 ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + + sec 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES >=2038-01-19] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + sig 2fcadf05ffa501bb 2017-07-29 key1-uid0 + uid key1-uid2 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + uid key1-uid1 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 +-ssb 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] ++ssb 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] + a3e94de61a8cb229413d348e54505a936a4a970e +-ssb 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 ++ssb 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_y2k38 b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_y2k38 +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_y2k38 ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_y2k38 +@@ -1,30 +1,35 @@ + 7 keys found + +-pub 1024/RSA (Encrypt or Sign) 7bc6709b15c23a4a 2017-07-20 [SC] ++pub 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC] + e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a + uid key0-uid0 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + uid key0-uid1 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + uid key0-uid2 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 +-sub 1024/RSA (Encrypt or Sign) 1ed63ee56fadc34d 2017-07-20 [E] ++sub 1024/RSA 1ed63ee56fadc34d 2017-07-20 [E] + e332b27caf4742a11baa677f1ed63ee56fadc34d ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + sub 1024/DSA 1d7e8a5393c997a8 2017-07-20 [S] [EXPIRED 2017-11-20] + c5b15209940a7816a7af3fb51d7e8a5393c997a8 +-sub 1024/RSA (Encrypt or Sign) 8a05b89fad5aded1 2017-07-20 [E] ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 ++sub 1024/RSA 8a05b89fad5aded1 2017-07-20 [E] + 5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1 ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid0 + + pub 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES >=2038-01-19] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + sig 2fcadf05ffa501bb 2017-07-29 key1-uid0 + uid key1-uid2 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + uid key1-uid1 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 +-sub 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] ++sub 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] + a3e94de61a8cb229413d348e54505a936a4a970e +-sub 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 ++sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_2_list_keys b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_2_list_keys +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_2_list_keys ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_2_list_keys +@@ -1,6 +1,6 @@ + 1 key found + +-pub 888/RSA (Encrypt or Sign) dc70c124a50283f1 2001-11-08 [ESCA] ++pub 888/RSA dc70c124a50283f1 2001-11-08 [ESCA] + c80aa54aa5c6ac73a373687134abe4bd + uid pgp2.6.3-test-key + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_2_list_sigs b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_2_list_sigs +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_2_list_sigs ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_2_list_sigs +@@ -1,7 +1,7 @@ + 1 key found + +-pub 888/RSA (Encrypt or Sign) dc70c124a50283f1 2001-11-08 [ESCA] ++pub 888/RSA dc70c124a50283f1 2001-11-08 [ESCA] + c80aa54aa5c6ac73a373687134abe4bd + uid pgp2.6.3-test-key + sig dc70c124a50283f1 2001-11-08 pgp2.6.3-test-key + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys +@@ -1,8 +1,8 @@ + 2 keys found + +-pub 2048/RSA (Encrypt or Sign) 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES 2069-09-28] ++pub 2048/RSA 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES 2069-09-28] + 4f2e62b74e6a4cd333bc19004be147bb22df1e60 + uid test1 +-sub 2048/RSA (Encrypt or Sign) a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES 2069-09-28] ++sub 2048/RSA a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES 2069-09-28] + 10793e367ee867c32e358f2aa49bae05c16e8bc8 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys_y2k38 b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys_y2k38 +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys_y2k38 ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys_y2k38 +@@ -1,8 +1,8 @@ + 2 keys found + +-pub 2048/RSA (Encrypt or Sign) 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES >=2038-01-19] ++pub 2048/RSA 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES >=2038-01-19] + 4f2e62b74e6a4cd333bc19004be147bb22df1e60 + uid test1 +-sub 2048/RSA (Encrypt or Sign) a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES >=2038-01-19] ++sub 2048/RSA a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES >=2038-01-19] + 10793e367ee867c32e358f2aa49bae05c16e8bc8 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs +@@ -1,9 +1,10 @@ + 2 keys found + +-pub 2048/RSA (Encrypt or Sign) 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES 2069-09-28] ++pub 2048/RSA 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES 2069-09-28] + 4f2e62b74e6a4cd333bc19004be147bb22df1e60 + uid test1 + sig 4be147bb22df1e60 2019-10-11 test1 +-sub 2048/RSA (Encrypt or Sign) a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES 2069-09-28] ++sub 2048/RSA a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES 2069-09-28] + 10793e367ee867c32e358f2aa49bae05c16e8bc8 ++sig 4be147bb22df1e60 2019-10-11 test1 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs_y2k38 b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs_y2k38 +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs_y2k38 ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs_y2k38 +@@ -1,9 +1,10 @@ + 2 keys found + +-pub 2048/RSA (Encrypt or Sign) 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES >=2038-01-19] ++pub 2048/RSA 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES >=2038-01-19] + 4f2e62b74e6a4cd333bc19004be147bb22df1e60 + uid test1 + sig 4be147bb22df1e60 2019-10-11 test1 +-sub 2048/RSA (Encrypt or Sign) a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES >=2038-01-19] ++sub 2048/RSA a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES >=2038-01-19] + 10793e367ee867c32e358f2aa49bae05c16e8bc8 ++sig 4be147bb22df1e60 2019-10-11 test1 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_5_list_sigs b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_5_list_sigs +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_5_list_sigs ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/keyring_5_list_sigs +@@ -4,6 +4,7 @@ pub 256/ECDSA 0e33fd46ff10f19c 2017-11 + b6b5e497a177551ecb8862200e33fd46ff10f19c + uid test0 + sig 0e33fd46ff10f19c 2017-11-22 test0 + sub 256/ECDH 074131bc8d16c5c9 2017-11-22 [E] + 481e6a41b10ecd71a477db02074131bc8d16c5c9 ++sig 0e33fd46ff10f19c 2017-11-22 test0 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt b/third_party/rnp/src/tests/data/test_cli_rnpkeys/pub/commring-malf-cert-permissive-import.txt +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt +@@ -1,29 +1,34 @@ + 7 keys found + +-pub 1024/RSA (Encrypt or Sign) 7bc6709b15c23a4a 2017-07-20 [SC] ++pub 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC] + e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a +-uid key0-uid0 ++uid key0-uid0 [INVALID] + uid key0-uid1 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid1 + uid key0-uid2 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid1 +-sub 1024/RSA (Encrypt or Sign) 1ed63ee56fadc34d 2017-07-20 [E] ++sub 1024/RSA 1ed63ee56fadc34d 2017-07-20 [E] + e332b27caf4742a11baa677f1ed63ee56fadc34d ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid1 + sub 1024/DSA 1d7e8a5393c997a8 2017-07-20 [S] [EXPIRED 2017-11-20] + c5b15209940a7816a7af3fb51d7e8a5393c997a8 +-sub 1024/RSA (Encrypt or Sign) 8a05b89fad5aded1 2017-07-20 [E] ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid1 ++sub 1024/RSA 8a05b89fad5aded1 2017-07-20 [E] + 5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1 ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid1 + + pub 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES 2083-05-11] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + sig 2fcadf05ffa501bb 2017-07-29 key1-uid0 + uid key1-uid2 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + uid key1-uid1 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 +-sub 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] ++sub 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11] + a3e94de61a8cb229413d348e54505a936a4a970e +-sub 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 ++sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt_y2k38 b/third_party/rnp/src/tests/data/test_cli_rnpkeys/pub/commring-malf-cert-permissive-import.txt_y2k38 +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt_y2k38 ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt_y2k38 +@@ -1,29 +1,34 @@ + 7 keys found + +-pub 1024/RSA (Encrypt or Sign) 7bc6709b15c23a4a 2017-07-20 [SC] ++pub 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC] + e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a +-uid key0-uid0 ++uid key0-uid0 [INVALID] + uid key0-uid1 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid1 + uid key0-uid2 + sig 7bc6709b15c23a4a 2017-07-20 key0-uid1 +-sub 1024/RSA (Encrypt or Sign) 1ed63ee56fadc34d 2017-07-20 [E] ++sub 1024/RSA 1ed63ee56fadc34d 2017-07-20 [E] + e332b27caf4742a11baa677f1ed63ee56fadc34d ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid1 + sub 1024/DSA 1d7e8a5393c997a8 2017-07-20 [S] [EXPIRED 2017-11-20] + c5b15209940a7816a7af3fb51d7e8a5393c997a8 +-sub 1024/RSA (Encrypt or Sign) 8a05b89fad5aded1 2017-07-20 [E] ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid1 ++sub 1024/RSA 8a05b89fad5aded1 2017-07-20 [E] + 5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1 ++sig 7bc6709b15c23a4a 2017-07-20 key0-uid1 + + pub 1024/DSA 2fcadf05ffa501bb 2017-07-20 [SC] [EXPIRES >=2038-01-19] + be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb + uid key1-uid0 + sig 2fcadf05ffa501bb 2017-07-29 key1-uid0 + uid key1-uid2 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + uid key1-uid1 + sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 +-sub 1024/Elgamal (Encrypt-Only) 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] ++sub 1024/ElGamal 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19] + a3e94de61a8cb229413d348e54505a936a4a970e +-sub 1024/Elgamal (Encrypt-Only) 326ef111425d14a5 2017-07-20 [E] ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 ++sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E] + 57f8ed6e5c197db63c60ffaf326ef111425d14a5 ++sig 2fcadf05ffa501bb 2017-07-20 key1-uid0 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys +@@ -1,11 +1,11 @@ + 23 keys found + + pub 2048/DSA c8a10a7d78273e10 2018-04-03 [SC] + 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10 + uid dsa-eg +-sub 3072/Elgamal (Encrypt-Only) 02a5715c3537717e 2018-04-03 [E] ++sub 3072/ElGamal 02a5715c3537717e 2018-04-03 [E] + 3409f96f0c57242540702dba02a5715c3537717e + + pub 255/EdDSA cc786278981b0728 2018-04-03 [SC] + 21fc68274aae3b5de39a4277cc786278981b0728 + uid ecc-25519 +@@ -26,14 +26,14 @@ pub 521/ECDSA 2092ca8324263b6a 2018-04 + 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a + uid ecc-p521 + sub 521/ECDH 9853df2f6d297442 2018-04-03 [E] + a9297c86dd0de109e1ebae9c9853df2f6d297442 + +-pub 3072/RSA (Encrypt or Sign) 2fb9179118898e8b 2018-04-03 [ESCA] ++pub 3072/RSA 2fb9179118898e8b 2018-04-03 [SC] + 6bc04a5a3ddb35766b9a40d82fb9179118898e8b + uid rsa-rsa +-sub 3072/RSA (Encrypt or Sign) 6e2f73008f8b8d6e 2018-04-03 [ESCA] ++sub 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E] + 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e + + pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SC] + 0633c5f72a198f51e650e4abd0c8a3daf9e0634a + uid ecc-bp256 +@@ -62,11 +62,11 @@ pub 256/ECDSA 3ea5bb6f9692c1a0 2018-04 + 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0 + uid ecc-p256k1 + sub 256/ECDH 7635401f90d3e533 2018-04-03 [E] + c263ec4ce2b3772746ed53227635401f90d3e533 + +-pub 2048/RSA (Encrypt or Sign) bd860a52d1899c0f 2021-12-24 [SC] ++pub 2048/RSA bd860a52d1899c0f 2021-12-24 [SC] + 5aa9362aea07de23a726762cbd860a52d1899c0f + uid rsa-rsa-2 +-sub 2048/RSA (Encrypt or Sign) 8e08d46a37414996 2021-12-24 [E] ++sub 2048/RSA 8e08d46a37414996 2021-12-24 [E] + ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_no_bp b/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_no_b/commp +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_no_bp ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_no_bp +@@ -1,11 +1,11 @@ + 23 keys found + + pub 2048/DSA c8a10a7d78273e10 2018-04-03 [SC] + 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10 + uid dsa-eg +-sub 3072/Elgamal (Encrypt-Only) 02a5715c3537717e 2018-04-03 [E] ++sub 3072/ElGamal 02a5715c3537717e 2018-04-03 [E] + 3409f96f0c57242540702dba02a5715c3537717e + + pub 255/EdDSA cc786278981b0728 2018-04-03 [SC] + 21fc68274aae3b5de39a4277cc786278981b0728 + uid ecc-25519 +@@ -26,32 +26,32 @@ pub 521/ECDSA 2092ca8324263b6a 2018-04 + 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a + uid ecc-p521 + sub 521/ECDH 9853df2f6d297442 2018-04-03 [E] + a9297c86dd0de109e1ebae9c9853df2f6d297442 + +-pub 3072/RSA (Encrypt or Sign) 2fb9179118898e8b 2018-04-03 [ESCA] ++pub 3072/RSA 2fb9179118898e8b 2018-04-03 [SC] + 6bc04a5a3ddb35766b9a40d82fb9179118898e8b + uid rsa-rsa +-sub 3072/RSA (Encrypt or Sign) 6e2f73008f8b8d6e 2018-04-03 [ESCA] ++sub 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E] + 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e + +-pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SCA] ++pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [INVALID] + 0633c5f72a198f51e650e4abd0c8a3daf9e0634a +-uid ecc-bp256 +-sub 256/ECDH 2edabb94d3055f76 2018-04-03 [E] ++uid ecc-bp256 [INVALID] ++sub 256/ECDH 2edabb94d3055f76 2018-04-03 [INVALID] + 08192b478f740360b74c82cc2edabb94d3055f76 + +-pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [SCA] ++pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [INVALID] + 5b8a254c823ced98decd10ed6cf2dce85599ada2 +-uid ecc-bp384 +-sub 384/ECDH cff1bb6f16d28191 2018-04-03 [E] ++uid ecc-bp384 [INVALID] ++sub 384/ECDH cff1bb6f16d28191 2018-04-03 [INVALID] + 76969ce7033d990931df92b2cff1bb6f16d28191 + +-pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [SCA] ++pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [INVALID] + 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48 +-uid ecc-bp512 +-sub 512/ECDH 20cdaa1482ba79ce 2018-04-03 [E] ++uid ecc-bp512 [INVALID] ++sub 512/ECDH 20cdaa1482ba79ce 2018-04-03 [INVALID] + 270a7cd0dc6c2e01dce8603620cdaa1482ba79ce + + pub 255/EdDSA 941822a0fc1b30a5 2018-10-15 [SC] + 4c9738a6f2be4e1a796c9b7b941822a0fc1b30a5 + uid eddsa-x25519 +@@ -62,11 +62,11 @@ pub 256/ECDSA 3ea5bb6f9692c1a0 2018-04 + 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0 + uid ecc-p256k1 + sub 256/ECDH 7635401f90d3e533 2018-04-03 [E] + c263ec4ce2b3772746ed53227635401f90d3e533 + +-pub 2048/RSA (Encrypt or Sign) bd860a52d1899c0f 2021-12-24 [SC] ++pub 2048/RSA bd860a52d1899c0f 2021-12-24 [SC] + 5aa9362aea07de23a726762cbd860a52d1899c0f + uid rsa-rsa-2 +-sub 2048/RSA (Encrypt or Sign) 8e08d46a37414996 2021-12-24 [E] ++sub 2048/RSA 8e08d46a37414996 2021-12-24 [E] + ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_sec b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_sec +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_sec ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_sec +@@ -35,24 +35,24 @@ uid ecc-bp256 + sec 384/ECDSA 242a3aa5ea85f44a 2018-04-03 [SC] [EXPIRES 2019-01-28] + ab25cba042dd924c3acc3ed3242a3aa5ea85f44a + uid ecc-p384 + ssb 256/ECDH 37e285e9e9851491 2018-04-03 [E] [EXPIRES 2019-01-28] + 40e608afbc8d62cdcc08904f37e285e9e9851491 +-ssb 3072/RSA (Encrypt or Sign) 6e2f73008f8b8d6e 2018-04-03 [E] [EXPIRES 2019-01-28] ++ssb 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E] [EXPIRES 2019-01-28] + 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e + + sec 255/EdDSA 941822a0fc1b30a5 2018-10-15 [SC] + 4c9738a6f2be4e1a796c9b7b941822a0fc1b30a5 + uid eddsa-x25519 + ssb 384/ECDH cff1bb6f16d28191 2018-04-03 [E] [EXPIRES 2019-01-28] + 76969ce7033d990931df92b2cff1bb6f16d28191 + ssb 256/ECDH 2edabb94d3055f76 2018-04-03 [E] [EXPIRES 2019-01-28] + 08192b478f740360b74c82cc2edabb94d3055f76 +-ssb 3072/Elgamal (Encrypt-Only) 02a5715c3537717e 2018-04-03 [E] [EXPIRES 2019-01-28] ++ssb 3072/ElGamal 02a5715c3537717e 2018-04-03 [E] [EXPIRES 2019-01-28] + 3409f96f0c57242540702dba02a5715c3537717e + +-sec 3072/RSA (Encrypt or Sign) 2fb9179118898e8b 2018-04-03 [SC] [EXPIRES 2019-01-28] ++sec 3072/RSA 2fb9179118898e8b 2018-04-03 [SC] [EXPIRES 2019-01-28] + 6bc04a5a3ddb35766b9a40d82fb9179118898e8b + uid rsa-rsa + + sec 521/ECDSA 2092ca8324263b6a 2018-04-03 [SC] [EXPIRES 2019-01-28] + 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs +@@ -2,12 +2,13 @@ 23 keys found + + pub 2048/DSA c8a10a7d78273e10 2018-04-03 [SC] + 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10 + uid dsa-eg + sig c8a10a7d78273e10 2019-02-02 dsa-eg +-sub 3072/Elgamal (Encrypt-Only) 02a5715c3537717e 2018-04-03 [E] ++sub 3072/ElGamal 02a5715c3537717e 2018-04-03 [E] + 3409f96f0c57242540702dba02a5715c3537717e ++sig c8a10a7d78273e10 2019-02-02 dsa-eg + + pub 255/EdDSA cc786278981b0728 2018-04-03 [SC] + 21fc68274aae3b5de39a4277cc786278981b0728 + uid ecc-25519 + sig cc786278981b0728 2019-02-02 ecc-25519 +@@ -16,69 +17,79 @@ pub 256/ECDSA 23674f21b2441527 2018-04 + b54fdebbb673423a5d0aa54423674f21b2441527 + uid ecc-p256 + sig 23674f21b2441527 2019-02-02 ecc-p256 + sub 256/ECDH 37e285e9e9851491 2018-04-03 [E] + 40e608afbc8d62cdcc08904f37e285e9e9851491 ++sig 23674f21b2441527 2019-02-02 ecc-p256 + + pub 384/ECDSA 242a3aa5ea85f44a 2018-04-03 [SC] + ab25cba042dd924c3acc3ed3242a3aa5ea85f44a + uid ecc-p384 + sig 242a3aa5ea85f44a 2019-02-02 ecc-p384 + sub 384/ECDH e210e3d554a4fad9 2018-04-03 [E] + cbc2ac55dcd8e4e34fb2f816e210e3d554a4fad9 ++sig 242a3aa5ea85f44a 2019-02-02 ecc-p384 + + pub 521/ECDSA 2092ca8324263b6a 2018-04-03 [SC] + 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a + uid ecc-p521 + sig 2092ca8324263b6a 2019-02-02 ecc-p521 + sub 521/ECDH 9853df2f6d297442 2018-04-03 [E] + a9297c86dd0de109e1ebae9c9853df2f6d297442 ++sig 2092ca8324263b6a 2019-02-02 ecc-p521 + +-pub 3072/RSA (Encrypt or Sign) 2fb9179118898e8b 2018-04-03 [ESCA] ++pub 3072/RSA 2fb9179118898e8b 2018-04-03 [SC] + 6bc04a5a3ddb35766b9a40d82fb9179118898e8b + uid rsa-rsa +-sig 2fb9179118898e8b 2019-02-02 [unknown] +-sub 3072/RSA (Encrypt or Sign) 6e2f73008f8b8d6e 2018-04-03 [ESCA] ++sig 2fb9179118898e8b 2019-02-02 rsa-rsa ++sub 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E] + 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e ++sig 2fb9179118898e8b 2019-02-02 rsa-rsa + + pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SC] + 0633c5f72a198f51e650e4abd0c8a3daf9e0634a + uid ecc-bp256 + sig d0c8a3daf9e0634a 2019-02-02 ecc-bp256 + sub 256/ECDH 2edabb94d3055f76 2018-04-03 [E] + 08192b478f740360b74c82cc2edabb94d3055f76 ++sig d0c8a3daf9e0634a 2019-02-02 ecc-bp256 + + pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [SC] + 5b8a254c823ced98decd10ed6cf2dce85599ada2 + uid ecc-bp384 + sig 6cf2dce85599ada2 2019-02-02 ecc-bp384 + sub 384/ECDH cff1bb6f16d28191 2018-04-03 [E] + 76969ce7033d990931df92b2cff1bb6f16d28191 ++sig 6cf2dce85599ada2 2019-02-02 ecc-bp384 + + pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [SC] + 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48 + uid ecc-bp512 + sig aa5c58d14f7b8f48 2019-02-02 ecc-bp512 + sub 512/ECDH 20cdaa1482ba79ce 2018-04-03 [E] + 270a7cd0dc6c2e01dce8603620cdaa1482ba79ce ++sig aa5c58d14f7b8f48 2019-02-02 ecc-bp512 + + pub 255/EdDSA 941822a0fc1b30a5 2018-10-15 [SC] + 4c9738a6f2be4e1a796c9b7b941822a0fc1b30a5 + uid eddsa-x25519 + sig 941822a0fc1b30a5 2018-10-15 eddsa-x25519 + sub 255/ECDH c711187e594376af 2018-10-15 [E] + cfdb2a1f8325cc949ce0b597c711187e594376af ++sig 941822a0fc1b30a5 2018-10-15 eddsa-x25519 + + pub 256/ECDSA 3ea5bb6f9692c1a0 2018-04-03 [SC] + 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0 + uid ecc-p256k1 + sig 3ea5bb6f9692c1a0 2019-02-02 ecc-p256k1 + sub 256/ECDH 7635401f90d3e533 2018-04-03 [E] + c263ec4ce2b3772746ed53227635401f90d3e533 ++sig 3ea5bb6f9692c1a0 2019-02-02 ecc-p256k1 + +-pub 2048/RSA (Encrypt or Sign) bd860a52d1899c0f 2021-12-24 [SC] ++pub 2048/RSA bd860a52d1899c0f 2021-12-24 [SC] + 5aa9362aea07de23a726762cbd860a52d1899c0f + uid rsa-rsa-2 + sig bd860a52d1899c0f 2021-12-24 rsa-rsa-2 +-sub 2048/RSA (Encrypt or Sign) 8e08d46a37414996 2021-12-24 [E] ++sub 2048/RSA 8e08d46a37414996 2021-12-24 [E] + ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996 ++sig bd860a52d1899c0f 2021-12-24 rsa-rsa-2 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_no_bp b/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_no_b/commp +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_no_bp ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_no_bp +@@ -2,12 +2,13 @@ 23 keys found + + pub 2048/DSA c8a10a7d78273e10 2018-04-03 [SC] + 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10 + uid dsa-eg + sig c8a10a7d78273e10 2019-02-02 dsa-eg +-sub 3072/Elgamal (Encrypt-Only) 02a5715c3537717e 2018-04-03 [E] ++sub 3072/ElGamal 02a5715c3537717e 2018-04-03 [E] + 3409f96f0c57242540702dba02a5715c3537717e ++sig c8a10a7d78273e10 2019-02-02 dsa-eg + + pub 255/EdDSA cc786278981b0728 2018-04-03 [SC] + 21fc68274aae3b5de39a4277cc786278981b0728 + uid ecc-25519 + sig cc786278981b0728 2019-02-02 ecc-25519 +@@ -16,69 +17,79 @@ pub 256/ECDSA 23674f21b2441527 2018-04 + b54fdebbb673423a5d0aa54423674f21b2441527 + uid ecc-p256 + sig 23674f21b2441527 2019-02-02 ecc-p256 + sub 256/ECDH 37e285e9e9851491 2018-04-03 [E] + 40e608afbc8d62cdcc08904f37e285e9e9851491 ++sig 23674f21b2441527 2019-02-02 ecc-p256 + + pub 384/ECDSA 242a3aa5ea85f44a 2018-04-03 [SC] + ab25cba042dd924c3acc3ed3242a3aa5ea85f44a + uid ecc-p384 + sig 242a3aa5ea85f44a 2019-02-02 ecc-p384 + sub 384/ECDH e210e3d554a4fad9 2018-04-03 [E] + cbc2ac55dcd8e4e34fb2f816e210e3d554a4fad9 ++sig 242a3aa5ea85f44a 2019-02-02 ecc-p384 + + pub 521/ECDSA 2092ca8324263b6a 2018-04-03 [SC] + 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a + uid ecc-p521 + sig 2092ca8324263b6a 2019-02-02 ecc-p521 + sub 521/ECDH 9853df2f6d297442 2018-04-03 [E] + a9297c86dd0de109e1ebae9c9853df2f6d297442 ++sig 2092ca8324263b6a 2019-02-02 ecc-p521 + +-pub 3072/RSA (Encrypt or Sign) 2fb9179118898e8b 2018-04-03 [ESCA] ++pub 3072/RSA 2fb9179118898e8b 2018-04-03 [SC] + 6bc04a5a3ddb35766b9a40d82fb9179118898e8b + uid rsa-rsa +-sig 2fb9179118898e8b 2019-02-02 [unknown] +-sub 3072/RSA (Encrypt or Sign) 6e2f73008f8b8d6e 2018-04-03 [ESCA] ++sig 2fb9179118898e8b 2019-02-02 rsa-rsa ++sub 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E] + 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e ++sig 2fb9179118898e8b 2019-02-02 rsa-rsa + +-pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SCA] ++pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [INVALID] + 0633c5f72a198f51e650e4abd0c8a3daf9e0634a +-uid ecc-bp256 +-sig d0c8a3daf9e0634a 2019-02-02 [unknown] +-sub 256/ECDH 2edabb94d3055f76 2018-04-03 [E] ++uid ecc-bp256 [INVALID] ++sig d0c8a3daf9e0634a 2019-02-02 [unknown] [invalid] ++sub 256/ECDH 2edabb94d3055f76 2018-04-03 [INVALID] + 08192b478f740360b74c82cc2edabb94d3055f76 ++sig d0c8a3daf9e0634a 2019-02-02 [unknown] [invalid] + +-pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [SCA] ++pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [INVALID] + 5b8a254c823ced98decd10ed6cf2dce85599ada2 +-uid ecc-bp384 +-sig 6cf2dce85599ada2 2019-02-02 [unknown] +-sub 384/ECDH cff1bb6f16d28191 2018-04-03 [E] ++uid ecc-bp384 [INVALID] ++sig 6cf2dce85599ada2 2019-02-02 [unknown] [invalid] ++sub 384/ECDH cff1bb6f16d28191 2018-04-03 [INVALID] + 76969ce7033d990931df92b2cff1bb6f16d28191 ++sig 6cf2dce85599ada2 2019-02-02 [unknown] [invalid] + +-pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [SCA] ++pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [INVALID] + 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48 +-uid ecc-bp512 +-sig aa5c58d14f7b8f48 2019-02-02 [unknown] +-sub 512/ECDH 20cdaa1482ba79ce 2018-04-03 [E] ++uid ecc-bp512 [INVALID] ++sig aa5c58d14f7b8f48 2019-02-02 [unknown] [invalid] ++sub 512/ECDH 20cdaa1482ba79ce 2018-04-03 [INVALID] + 270a7cd0dc6c2e01dce8603620cdaa1482ba79ce ++sig aa5c58d14f7b8f48 2019-02-02 [unknown] [invalid] + + pub 255/EdDSA 941822a0fc1b30a5 2018-10-15 [SC] + 4c9738a6f2be4e1a796c9b7b941822a0fc1b30a5 + uid eddsa-x25519 + sig 941822a0fc1b30a5 2018-10-15 eddsa-x25519 + sub 255/ECDH c711187e594376af 2018-10-15 [E] + cfdb2a1f8325cc949ce0b597c711187e594376af ++sig 941822a0fc1b30a5 2018-10-15 eddsa-x25519 + + pub 256/ECDSA 3ea5bb6f9692c1a0 2018-04-03 [SC] + 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0 + uid ecc-p256k1 + sig 3ea5bb6f9692c1a0 2019-02-02 ecc-p256k1 + sub 256/ECDH 7635401f90d3e533 2018-04-03 [E] + c263ec4ce2b3772746ed53227635401f90d3e533 ++sig 3ea5bb6f9692c1a0 2019-02-02 ecc-p256k1 + +-pub 2048/RSA (Encrypt or Sign) bd860a52d1899c0f 2021-12-24 [SC] ++pub 2048/RSA bd860a52d1899c0f 2021-12-24 [SC] + 5aa9362aea07de23a726762cbd860a52d1899c0f + uid rsa-rsa-2 + sig bd860a52d1899c0f 2021-12-24 rsa-rsa-2 +-sub 2048/RSA (Encrypt or Sign) 8e08d46a37414996 2021-12-24 [E] ++sub 2048/RSA 8e08d46a37414996 2021-12-24 [E] + ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996 ++sig bd860a52d1899c0f 2021-12-24 rsa-rsa-2 + +diff --git a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_sec b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_sec +--- a/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_sec ++++ b/comm/third_party/rnp/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_sec +@@ -42,25 +42,25 @@ sec 384/ECDSA 242a3aa5ea85f44a 2018-04 + ab25cba042dd924c3acc3ed3242a3aa5ea85f44a + uid ecc-p384 + sig 242a3aa5ea85f44a 2018-04-03 ecc-p384 + ssb 256/ECDH 37e285e9e9851491 2018-04-03 [E] [EXPIRES 2019-01-28] + 40e608afbc8d62cdcc08904f37e285e9e9851491 +-ssb 3072/RSA (Encrypt or Sign) 6e2f73008f8b8d6e 2018-04-03 [E] [EXPIRES 2019-01-28] ++ssb 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E] [EXPIRES 2019-01-28] + 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e + + sec 255/EdDSA 941822a0fc1b30a5 2018-10-15 [SC] + 4c9738a6f2be4e1a796c9b7b941822a0fc1b30a5 + uid eddsa-x25519 + sig 941822a0fc1b30a5 2018-10-15 eddsa-x25519 + ssb 384/ECDH cff1bb6f16d28191 2018-04-03 [E] [EXPIRES 2019-01-28] + 76969ce7033d990931df92b2cff1bb6f16d28191 + ssb 256/ECDH 2edabb94d3055f76 2018-04-03 [E] [EXPIRES 2019-01-28] + 08192b478f740360b74c82cc2edabb94d3055f76 +-ssb 3072/Elgamal (Encrypt-Only) 02a5715c3537717e 2018-04-03 [E] [EXPIRES 2019-01-28] ++ssb 3072/ElGamal 02a5715c3537717e 2018-04-03 [E] [EXPIRES 2019-01-28] + 3409f96f0c57242540702dba02a5715c3537717e + +-sec 3072/RSA (Encrypt or Sign) 2fb9179118898e8b 2018-04-03 [SC] [EXPIRES 2019-01-28] ++sec 3072/RSA 2fb9179118898e8b 2018-04-03 [SC] [EXPIRES 2019-01-28] + 6bc04a5a3ddb35766b9a40d82fb9179118898e8b + uid rsa-rsa + sig 2fb9179118898e8b 2018-04-03 rsa-rsa + + sec 521/ECDSA 2092ca8324263b6a 2018-04-03 [SC] [EXPIRES 2019-01-28] +diff --git a/comm/third_party/rnp/src/tests/data/test_ffi_json/generate-bad-pk-alg.json b/third_party/rnp/src/tests/data/test_ffi_json/generate-b/commad-pk-alg.json +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_ffi_json/generate-bad-pk-alg.json +@@ -0,0 +1,10 @@ ++{ ++ "primary": { ++ "type": "EdDSA", ++ "userid": "test-eddsa" ++ }, ++ "sub": { ++ "type": "Wrong" ++ } ++} ++ +\ No newline at end of file +diff --git a/comm/third_party/rnp/src/tests/data/test_ffi_json/generate-eddsa-wrong-prefs.json b/comm/third_party/rnp/src/tests/data/test_ffi_json/generate-eddsa-wrong-prefs.json +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_ffi_json/generate-eddsa-wrong-prefs.json +@@ -0,0 +1,14 @@ ++{ ++ "primary": { ++ "type": "EdDSA", ++ "userid": "test-eddsa", ++ "usage": ["sign"], ++ "expiration": 0, ++ "preferences" : { ++ "hashes": ["SHA512", "SHA256"], ++ "ciphers": ["Wrong", "AES128"], ++ "compression": ["Zlib"] ++ } ++ } ++} ++ +\ No newline at end of file +diff --git a/comm/third_party/rnp/src/tests/data/test_key_edge_cases/key-empty-packets.txt b/comm/third_party/rnp/src/tests/data/test_key_edge_cases/key-empty-packets.txt +--- a/comm/third_party/rnp/src/tests/data/test_key_edge_cases/key-empty-packets.txt ++++ b/comm/third_party/rnp/src/tests/data/test_key_edge_cases/key-empty-packets.txt +@@ -1,1 +1,7 @@ + :off 0: packet header 0x9800 (tag 6, len 0) ++:off 2: packet header 0xb400 (tag 13, len 0) ++UserID packet ++ id: ++:off 4: packet header 0x8800 (tag 2, len 0) ++Signature packet ++ failed to parse +diff --git a/comm/third_party/rnp/src/tests/data/test_key_validity/case5/generate.cpp b/comm/third_party/rnp/src/tests/data/test_key_validity/case5/generate.cpp +--- a/comm/third_party/rnp/src/tests/data/test_key_validity/case5/generate.cpp ++++ b/comm/third_party/rnp/src/tests/data/test_key_validity/case5/generate.cpp +@@ -34,11 +34,11 @@ + + static bool + load_transferable_key(pgp_transferable_key_t *key, const char *fname) + { + pgp_source_t src = {}; +- bool res = !init_file_src(&src, fname) && !process_pgp_key(&src, key, false); ++ bool res = !init_file_src(&src, fname) && !process_pgp_key(src, key, false); + src_close(&src); + return res; + } + + bool calculate_primary_binding(const pgp_key_pkt_t &key, +@@ -148,11 +148,11 @@ main(int argc, char **argv) + return 1; + } + + pgp_dest_t dst = {}; + init_stdout_dest(&dst); +- write_pgp_key(&tpkey, &dst, true); ++ write_transferable_key(tpkey, dst, true); + dst_close(&dst, false); + + transferable_key_destroy(&tpkey); + transferable_key_destroy(&tskey); + +diff --git a/comm/third_party/rnp/src/tests/data/test_messages/message.4k-long-lines b/comm/third_party/rnp/src/tests/data/test_messages/message.4k-long-lines +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_messages/message.4k-long-lines +@@ -0,0 +1,16 @@ ++Hello! This is message with very long lines. ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++New line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Next line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Another line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Another line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Another line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Another line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Another line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +diff --git a/comm/third_party/rnp/src/tests/data/test_messages/message.4k-long-lines.asc b/comm/third_party/rnp/src/tests/data/test_messages/message.4k-long-lines.asc +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_messages/message.4k-long-lines.asc +@@ -0,0 +1,27 @@ ++-----BEGIN PGP SIGNED MESSAGE----- ++Hash: SHA256 ++ ++Hello! This is message with very long lines. ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++New line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Next line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Another line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Another line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Another line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Another line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++Another line ++12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ++ ++-----BEGIN PGP SIGNATURE----- ++ ++wnsEARYIACMWIQRz7cyRGa/I4tu9zeUEUUCWaf/ePAUCYfAl7gUDAAAAAAAKCRAEUUCWaf/ePADt ++AP4r5IEvEmNReIhLbvAPkh6gE995GyeCG/iJ38WYUxi6PAEAy8Gl3UG41Qc+zW1sjUz3MkgfrjPy ++33boIsyiHiJqSwY= ++=NM8O ++-----END PGP SIGNATURE----- +diff --git a/comm/third_party/rnp/src/tests/data/test_messages/message.aead-last-zero-chunk.txt b/comm/third_party/rnp/src/tests/data/test_messages/message.aead-last-zero-chunk.txt +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_messages/message.aead-last-zero-chunk.txt +@@ -0,0 +1,1243 @@ ++0,e083f 9f90823 f8, d8 f3f-56 7d ,58--12907 ,e,-ea1842-. 1a25 c ++24 7 ++3-2c 9,c993bf ++ b3d ++6b8 ++e630 09e ea37a ++. c8 5 ++466-24-815 29a26.7.ca6b5 3e3- e7b d52 fe1a899f.1,7ca3,0 ++b1.- b 5464fd ++3- ++e8 ++f- 30 8 a89f.106d,,f465 9a ++8950496416-4 ++ab68 a8cae8b ++e f914,a6a7daf54 ++4666,,91d01da62e 1 4,adf9 ++09 e,4 .8f9 ++ 7a048 ++6 27-7f. ++9,.ebc9 ++a.dc .75 72e-9331-eef740d 1a56c0b9161 3142 d8 ++2a1 ++fae ++7.f2ba1 0,e- 725 e ++075 . 7fc 9e.990 0 9-a564b.c6b0 ++7 ++.f ff7d5643.138869c4, c240 - ++12323 420d1a. 3, 4c15220 c354.e84205,4f1b4d.b6aa 1ae8 6bd49 ++6.ed ++.1a ++ ,60 ++0a8,dbd 8 ++4- 80,3c f3a 82,f92,-, df30f939c321,6,e 803,2 a ++. a.92f 42eb1 6eaf21 f7922c9 ++d ++4 a9d79 f.7b 0 ++c0 8 23 ++.43bb1.0ab.f7. ++012f87856 ++,ed-0 ++f8a8. ++51 . 99 b,a 93, b 84 ++cf-a 456. ++37e010fcfcc976-5f50-ef532e ++47d5c -,a00 c3897 f17.dc42.15f0-6 ++6 b09 f..f.,eca-2f44eca 115009.0 ++b27dd9 ,51e d8,7 c7154f 16b8,2eff2f ++c 55, 365e8e.19.68 ,72.86,,. ++58b27 387e2 d- 4bd4 32,f0-c7.a6.d .ff. ++2 f4 ++0- ++ 6a37b6 5 9ff54e 3c8bb f8-ebe9af. e0- c2387996724057.153 ++a 35 ++d.-57c48e ++d9 .. 752e1c--28d85aa3 9,,76d4c -352d9.16c.45 ++6 ++e96,b ++ 26b f ++d1.ce0f ++ 6222. 1, 1-fdd74a95f c1 ++-a4d,e21c 32d b ++ 0 7ec007 28f2.-5fe 9.66 46c9 ,de 141.dabf. 571-e ++ e1-8,7451620 8 6f3-, f626.,b 8,771b,2 ++ .aa0b 1a 9 de,e661 .72 99866d,4-4-22 60 1a754 5839f7c-e426e.f ++984ad98b 58 - ++7 ++ c49-6,666f34da1 c59 -,472a 65b e280 ,273 5 ,7 . 7d 8658d773f593.1 4856a5d8-b1f440 52271bb 1 28559074-8a7d-68.2da46 1a8 9a,509a5f944 ++b,9cd d869.,b2eb6fbd-,1. ++00 ++f- ,08b2a 772d14 ++-c04 1a356492d ++791-1-db-2.8b8e303c1.1 b7-,.f ++6 .09 - 1c60.,16384b- 959-c6,0 ++66, 0 f9ecccca76 2 5d ++1 4af6 63,c1623 a 480acea-.2 fa4 a cbec6-c b7c9 f.93bbd1 ++8 4e162600d24aec3f382c464ad42,70a6-,6 a cefadec ++248 ++9d.1224dca3 f6,c408c072 8b2 0518b4a69422f,8 ++d0552 f6a2a152-38f6c a82dd0 63 .482ca.57- 27 ,3a75-05a56 b ++,.06,,c7- e2b, ++f7e3,1794,363-032b4b05.09c1954 -82 ++3 76c 69 ++4 d8711,,f dff97776c87b31584433abe ++abe0 d.f5763 c ++790.-f2 ++2.8fc34 177 30 ++9 5f66c0043f-2d8 ++029-. ++ ++7 2 8 1,6be1364.be-f6 ++e7 1f-214- f180b53fe ++ ++ ++13d-f.de fa990,d.878e5955.63-97 -1a8e8f24c ++b ++6-b6edbed0cd94a ba.6 885 ++ 05252c61,2d6a,d5645699 6.c-a9da9 52 ++ -1f.f-07f b e 083. e9f91 ++3be 6df19aae7,766f-6 - e8.d3a7 ++387225.82 f58-9 ++03 fb7e4e 34818 fef6b9de6aefb,80 ++b6846526. - d0f3de,355-5a 1982746eb3a0 a 2,933e.a a03904d69a83703 ++1e52 4e5 c135.abb 3 7c,.5-34b70e-0b0,2 ++eb.1a5-53 ,-.6e.d- .4,3 706 a2ae,df1d3ea6d8 c 0 - -8 -39 b f a.-3 ++. ++903,b5b2 ,f c2.,c9c.64,e ++ba b 79.4c83--73,57f5f05390440dfc6fb 2a75d805 9 ,.0 ++40,2b ++d,cc.-01. 65 d3cc5c f e8b1f,d4f64 14da c, ++f17 ++d ++ ++115d5 85b e7e ++890 5. ++,fd4d1d7e88 .9c41f8 ++bbb,449bb8 d2295 9372a 0df7 d17a, ++d7 5.4d, ++,303bbb407-3236 379c5 49d156.06d,c90.292bfd.5fc33e09ec5f -6 ++e- e1 ++ce ++dd6e6 a ++d, 2f6cd808d388 0460 ++.e.ed ++ 514f b6 ++a7-7- 0c.8 c58 c8,a4 ++6 2.d b732 d6 0, c.71-a--a ++ 540ac5cf-74 f1f1-82 2864 ,1947 -d3c69e,fa1323f98 75 ++316-8c c ++f2 ++fe76ee ++ 53b69 ++ 0e684 ,e..94.ca8 84a df,abb3.b ++32,9 ++c ++,6d8.d4368437abd 4 74,9b,6 1,417-bf47857 a5d5db62, ++ 4a0.c ++ 47c.741 ad 15f 0 5-f, 0f 571.d271,,32aa84,5e73c20.e. c4bd3,3d e2febec 666e-8a648e29e28, ,a1 ++7748.7 da a 2e ++eae-4b4-4b5540 b10c ++7970 -1 0c3 ++48526.203868da-9 271bb6 0c6 fd6 6,70e6b ++81 b8eb.6df6d8034eaf f75 30c 49- af04e- ++d ++9a dd e0-2d3 8fcb598d93,-42af ++d864fdb9245ec69,c ++05,09 7 3484,6f5 9624 ++ ++,e4c9 ++b16075dc .bc,.. 2 6 .e .404eabd0e2b7 ++-,- ++b5412bf,9ca775a3e28210-,.f10c.31 ,28982 -1973c8c,fc, b- 3d6f a,e- 644.8f9 8 5 ++ .cfe 6d ++.e 7- ++8-.4f 9 b6106ea39 , f9 ,2-d bc13d-2 53 06 610cc5 -d d 714 .f8d 3bbc0c6 371 ++ae.d ++8,8-14-9 84e 7fd2d0d4d6- ++98.1 8 77,d0a1 ++.f- e 1c6 808-4 55 5-3fbb6117,1aeb 40ca4a7 5d01,-b5-- 508 4 52 ++ 67c-ceba5,1 ++ b c7,2 ++4ce 22b-3 f ++ 1-fe.65cc2a5 493a0feee3- ,813 ++4572.c f 2e9 -f23,5e1c8aa5 c7 ++2 4794a0 .e. ++9.0 5- ++49 d3 e701a 7 ++60f,e3,d2-,97d03 d422aa.5 ++d9-b6 -7c 20 d- 942,a6 9f ++,b , a-efe0-9-6800eb16ca993 4-6- -530-,46 cc15b 6b696a23,73 f72e --d2d5f8158c9 550 ++b90 ,9-549b.263 d0085 8b 90.2.ee600 bbe,a6b88.4d1a 339e0db a26fe1 e37d6e..-e b6 62 8-f2218f ++0, 4 7f,7 ++ -6404-b,7 ++3 ,3-5.44, ++46-e5 b54f da 8db59b.b,b,c- 8,fb ++--,- 31,a 7 ,.5a be-b,8 f0,- b,346fff,6-c ++--64-e-0--fd.ab 434 8e -1062, 42c-8c -.-1 ++ ++36 5- ++32c,d -20 f61 5 a1 68fe6e 903. ++23 .5fba4cfcd690 ++,.6 cc18c09 ++a b781 23af 38 f24 ++871b ++fd6, b 66 50c 0e7 ++60 7bc8- a03edad 9 a 90f- f ac6e, 16 ,b1 b a3e1, 8.0c435- e f37 2.2,aa981c84 6f ++b ++ ,876-.8 4730387f 659ed 54-814 ++a2 ,9-2a08 a.174 ++ d e374,9c f76 ++ 2d3, c3 6cde97d. .f,48cf 418993- .6 cb8c33c,5136.e9.d17 ++9, e ++842047 22f9dcee05c--fd694415 da8a.6 2,5-b,bb36 96b77c89 547d54ca.-9-97c b.e ++96 3818 ++da ++d .,555b094,6a7,f35ad b 1c5752 d7856c7406d7ba 45 88 9 efe5cf ++8,614d8 ++9f 2 82.73.f,5e47a5019 ++ 2a75b1c87632-f64b3ceb16e-00.97.367-60568c.c4. 8b6aa8.1 9.f- ++,73f7c,624b ++bf34.1-5-b 4--f-683 .913e4218d4-f, 757760f,7f86,, 7df9.36-4a9093-a8d- 9 74- 5299 bcd8.,9,6f56 ++87a7 05 e755e0-0c25..,9df 3ac06d372 ++5 e86.d1.c,114 db86,.,4ee 910.a.0d,, bcc-19c5b-42 a e ++ 8cbf4f6.93db0997 323cb689b ++40d ++c .c49b-4e8 ++-937,bb1, 6f ++,846-6c9 ++a.,.2d ++ 2 -8 ++b. 3f 603f556c e88a,-.9d f97 ,f6c700-0 -b57600539cf1- ++a4deca .a87f4c62105796 966,-6385 525 3b28-1bd ,5daa23a74c97.4e43c 808.17. .03c 103b1a 75a 77e ++d,-,8d0 f ++7feb2bff dfb a3 ++.,3-a0d6d ++ca642 e58. ++ed6a238 9 ++7 -4 . .25942243138b4 2 ++-e68e-d4 2a4d9 9b28 8f ++01,f2e,3b75-da7798.d3 e 4b,,d 55b19.24a12-23 00 ++2-9,66d7 81 f 7- 9be3.f6.,b304,-d7,3d5 5 e0..94f7 80 44 3 f8022 ++6.3 2 b9-481-b, ++51,f4e408 ++-f7d3c82.93 b-958.,ec599-06--f-5.0 6f1d905, ++cf -f46b6fb,8c, .ce13 ++55b78e7de5ef ++- 80 6be.7 5c561 e2. ++32 9d 4c8b44e2f 7a38f.9,7-796289- ++822bca8 cee44 ++.e1e-4c26--9 b0 88d727eb975e4 d c584becdab09e5979. 1591,1e 644b40d56b283-c51f7cb- 106fec 9d2c32d2e .65e59-d6 12 ++2 6b-c87 ++474d 077d8-c ef f0c bc7 ++,.0c 125. 16 ++e00,78 34a7 .e216da615abee.a861eae4.9 -. ++d 07de64 cc ++, b f7ef-9 ++5 13cc ++304f96499 ++9- .,964d,a8,9 ++1 02ed ++,9 , a.de12295 2cb7-5 ++863b ++6.4404156.8- 962f92e61639ca d2c9 cf8d,d6 ++fa.a,.a5 -c.1afbf5d9d86 4. 2eb54 . ++9.22ea5c,acf 36e,c c0 ++ 787813dd5,8477.,fac7df3-86d.ed8243 ++49-53b26 048c15 ++5363 ++f3c3f,,-a73-.fc3eb e230,.1.2 ++,0f2,3 0b8-beb ce93e6d50b7c 04c.aa 9ae-6, 4,3.b0 2 ac2f ++8 f5ec faac-b7 .8 ++a2b1d7a5f390b4 .84.c, 66ca7. ++b,6-d240 77 , b08afe ++b ++b5122,c0 bc640b22cb171a82607-0da,f7.a,17--29f05e ++,b.b 3 8a5.,8-74 b2..-9 .15e59,198d 30,f.7b5 8d30a5-71.98,7a09.f d ++ ,056 07 . ++8ea5 a ,9 e, f1-.a.b649,df6590cea,dbc5 ++eef.9c8 8f1ee01 8.8 716a9ef142,48826.cc0407b94 ++fa75d4 . 8.d 5dd37.-58f,. 52b -7f-cdc.a 7de29.,90.bff1-,a4-,5-eb 3cd81 46 ++f14,9c ++bd10527 fcc-6add81b c2a c58970, ++63b 31662 e877 3-50-c.6f .c8 ++a28b0e23 ++613b76,6f ++ 53,6 ++a3e..8f 0 960,9b7d -e.b36 da1. 637 135 ++.e8a e13812d9 b ++ed58 4 f8 ++fa.3, .c81 .5 453c63caa484-1d 663 ++74a7e,.f5,0. 3, ++a2 373dc2a8f,02a43 7 d226,9fae69.6 5998.30be666-471- 42,3.545825-ee d5de97f 4. f6 1b c ++6aac145d 2.0c7 94 ++0,46257682 6eb6,e9 .,bb e-7 ++86,14a -c-90, ++5,f ,7d a,0e ++d-.975ad8a211fcd0 ++1c 2e 32a2.,5b1-f5bdfe4bef9,2 ++cdebf1d088b6 8e56 -bd1 9 73d0f7b5.e3--2 ccc f2a-0d 74 ,4ae 7,e e 7 ++f28 ++.3.7c -42-a99 7- d ee 3 a15430abd.bf,9,411bc-8a.6.df,3f51 0d. ++-1250-51,2 ++44ec0c.cb698c.03 c3.1e491 86 -4b c0a884 ++f , 3 5 ,95.f1e-c140 779e33 b .2 1, 1553a45d8 3533 6d5c 86b985. a2-1c5 c2901b,0b91e5d 66,a68. c666 ++,3605,,18,53-82 5e -39e.a1e-b ++ ++e06b9feee.3 ,57d8 -91.6ac c ++ 93 ++f.75 ++a4c ++8.e4b1 ,3 ++7.ce0a-3 ++c ++c 1cade69 f .9 d d0638e79244 3 b0a9 a1-,276f.6 0. 93ab6 ++-85 ba 4e ++8a-0 ++3120e.63,b7acc ++b0 .-- ++ 32 8 ,1fb. 91 8 16 ef c49ce6a080f16d ,,5.866e 1c461 ,f40,ca5ad ++d12 27.2edd ++8402.8 d d 5.-1d 9 5c3b fa4- 4. 2-d1 add5c f d63f741,b ++-e 5 7-3 ++f-.d89 a91 42 ++f6f3 906 ++e ++9 d,d69f e9de5b70c99,4 ++ bd16-f. ++5- bdb,-5828a 6f1 ++5a-0,d-b09c.d83- b29ef1.1-1d 4dfe73c8ce5a ++f0506e1. 0 0.fff f2 ++68,c 8b , 459 ++,5504d1a10 , 1 6 ++cffa- f 4. 2ece . 15 -5, -b 8086c880.e8146- ++02ff386538.608fa 6f0e5ed 59.-abfa.c4 ++748 ++933b,8 .cd4.7.9 2 ++4a-efa,1e, 08,6bbc 5 ad23,,28bcb74c7 df 9 .c74 f892faf18-11c0b 6a ++6d ++ ++e da028aa114 .8 ++ 6d7- e.2 82 30b 9 06 90 486d ++1 ++95-e1821. c6 ++bf2b9,7-2 ++b bd20- 0 082 0d84f272c3 ++381d09ce8ab. ++3e 9- ++ac.b4 8b a601e9-f4d-9e9c-21,3f ++8f -98f2.0,3dab70 c 05d d ,2-8 -b 0 a 48608005-.e6. 8ed ++-72bb 3ee,5,- ++e4 ++056bf-1-80 ++e 4.ba8862 250 ++cbbff8,885, 315,23 ++cd89bdad b48f8f 1.1. 836 6b8d,8e,e85 ++5 1f4a1 a8f.59e 8c46 4bb2 ++442 7 5bb e ++1 ++,934 aaf39 3e46ec8a,.ecd9. -2 9d5df7e -1f5,e8-2c1413acec2510c4fbc88bd ++982aa44ccf5.53 , ++ 85c90-a40,2 05. ,d5dfb2f 4f91,c.2 f ea9.012aa8-19361 71ca0 f--e-8-746,4e6cd29 e772e0-,1. dabb,89 c1 ++4f e3d258532159656 8 ++b7f4 1-1-,- b ++3ce1-1 -e ++13e02d5a,,f.,5. ++e63980d0ea75,6362d399c d,e c0 1 .1c ++1 b db ++0 ced9b4 ++ b7d deffd786, .e16c.65. c f.,-858afc 143661.b 3 ++eeb5904-3031a .7d e4 0-87 -d. ++8-3d 5- ++ ++f ++ .0 ++ ++11-b-2 e 7 6662c1 ec3f0f ++ ++27 6-409-22f8e833 7a71 ++ ++51a06f ++65e,1c3a36f4006bdbd49,bd-c-4-96a4e c,e,cf348b6b425, beef, -8b4153.ccbb108d6 ++ce e8db9f14 1afd 9-619 .1f603b ++ ++6 ++-72dc ++.1a1.b6cd73 75ba 00660 .9 2.63f, b- ++a 68dceb8775 ++637 1 ++,045d6e06f5c30fca324 ++8d96dc ++.b3.07cd19. fe.4 ++ 6dd145-a9 ,a5,a43a7a2bd 21 9 ++7- ec09- 82.cd76e9ae 84 89315 ++b.67cfd1a b0 ++,3c2- 8 ++bff868 ++da0 a ++4f,fb ,d-83e effffeaf664,9302d26665c75ba e -3009eb 934-,8e -c 4 1-a a06 796,8. ++afd3349.4a24 13 106c f44a 527.e 0 c-5,3-8,a ae3 ++ cb04 59 8,e72f4bb47-be34b7b18,8 ++5a04 94c 2acb6a5bf ++687fc- .3 45 - cc.., b45f93da 19- ef80e9,a aa46 ce1b e 7 ,-b9.e.6a.4 ++8f7ce82a00c3 ++.3- a105704104- 4.cc ++04 a4ee8,ec,20 440799,-,414243.c427 bedf5.64a98 ,- ++a886f ++17 4b9a7 ++4ab7e427c41-.9- 6 ++c b f e65 ,5-cb c4, ++,b3 a5 ++9a2f95e.a0bc,358 06,bf42f2fbe-45af.. 898.,--257 ,-,e.30f ++9. bb.dd3a1d2.1c6f6de.e66.5-40 1ff 825a8.e8 d ++bbbd0da9a 57 ++7d8 64 .f ++a340c. .-e5 ++.210ae 7,ab ++1,ee-.37b5104e d4d9 8.66b1 2 3b7fbf64d5587d7c319.d3d.ca ++48.5f0758fe0- ++b8 .c8. aef9.-4cdc178a,-7 ++d-f9-.2 f.4 5d.-,6ea,-01152 8.5.9.e3 ,7546-7df49,e,8faa816.dc1b6a7c0d6 .b.1b31cf ++3c34-29.ecf34a16 bd a9 6a521c85bc3c1 7 4f300e0b75 ++6, 8.b 5d.,e2 19b177b5 64da.3 .a933034ea, .0e 8413 e-97bf 8dc2.4 a9a 7c36 ++6-, b 7d 1,f-33 ++.2020b 7726 -86ebc ++ddc e 810-503a4f91-c1 572fa33079.8-49e5 ++ddefe.6 231e-f ++3a,39f0df e ,e2e ++f3 de ++30232 d5.2 80401..e.1a 30-41,88 952c6f 05 ++ d f3907c2- ++006b ed334- ed-256,3b ef66908 11706 f0.437,6 5 a ++ ++cf9395 51-8,f3 .. 549ad- a06a94db16-79163.1. ,,b7ea 19e7 774a94-5 99e4eb2 ++5 ++ 8271.366 6d 07 ,.47 15ca3d ++4 52cf96 ++ 91,53 40a07a96b0b ++00f3,1.ca5 3ef9a60f1 ++5 a ++0-50 ++68 ++ -69 -2fe4844,9ab., ++62f63 c8357d83b89,a87 5c917 8f2 .c7ec0 e0b74b6ff137.ca e-ec.,e,f 4ff ++,3.b1 9 8cac2cc8d-- ++1-d49493bd 6bab41 f5e0d0ce 3.2,a4.4246a-09b166ad 2c19a03c6541 ++,69c 0d1199 -f -d70528495,4ac63061d8c847 b5b0-c328,50 1 .9bcf db8 ++0e1c1d 5-fe2 ++9be548b2f06c7fb,f ++e04c7e 7- ,7b5 ++-4a6-3650 ,b.823,feb0 e., 2, ++72d3e,948a3 d90b .4ae-d098c01572dae45.,,b59b32ec7c.473fcbb,180 ++c 9224b ,c8-84bacb1 ++46619 a ,08587204. 34-8 2aea fc7-.31 ++e-972a-.55 e40db,c1.,2e.fbcb-3 dc9b,6 14e ++.e538fc.1.86 -045a ++18184-54158,7.152bb512924. 82 - ++44,fd ++47.f c f ,6.10173,2 513.e0 2460689 9, ++441c ++, e ++1 1, 5bd9.-592.7f79 ++d 7 ++.8b.69- . -,416 ,e a ++7 ++f 37 dfc36ffb6-808fcb-385b35,a0 246 e52604af00.a,15 -65 0be82d33b1 fee3 d56a1 fb ,,1e-7.99,.e73.c,.4c1f..ef .4 ++5e4 . ++d d 4 d6 ef3ff05 b,014f2f44--,, 5b ++ ++810e 39 79514 46f.512a18.2 fd.b ++.-a e2b ++4 462-e9f132 ++29,b 49 15566 331f7 606b5.c2b, b, 8-d5b856- 711 c ++,bf ++dde5c2,- e0d35 ++ .052 fda 3d24ee4d,b ++. ++ ++ ,686d28fb73f ++bfb e0,9 ++d13.7 ++bd fe c9,9585b4 7 3489695fd . ++9-7b ++a .9-fe51 a3af04 ++74d 546,,ec-b4- 4b3,0 e 462f 02dd8e-94 36 7f6 c3 ++6 c ++529555eac10. 4 f41 ++6de94e5-f6959 .c3b1896c,c 80 ++0c8 67 c84-6a5-00 f050e8-9f 57346-a7d- -45 -5e2-8ce244b1 ++ --,a11bb4 0 ++21 6c40-3c,1 2.9 b05-3f2a62 ++ 7 ++1 d,7b32 -5- 8c5,0-1f,e14652-9 3 366--519f073,4. ++a8, ,.9a6b.,a3 a--6c1 2-d .accdc ++52 0 aa46 636b ab94 b5.7d-5ed,09abd0985.6bb--0401-8 , e ++c ++7-13 fae .2 1d263c9e c f 6a23.0e6b44 54b6 ++ .a 1a558 0344 2,730db,bd74e13 1.,. 2 ++f 1c.a09-b1f246f 2 fefb32fee4cdd0f1c6e6d 2,87f 6562152-fc, b b3 --9bc80bb0fa 91 3f.4a17 59 5e318e6d67bc2bd0b1ddcf550355708b7c..b ee37d 2 347- 1 ++7ba2c12 c ++9 c8- 8 -9549 ++ 1584-e3325 ++890733196,56cd1e8c..f7. ++-.887765316 9e ++80 85e08226 8a8.4ee.295-7e7f0..e73 7d,dbfcf.d3d 3 -f2-b ++063. ++ b4 -8 ,8a88f27 240-a ++a c 87c81 e8 ++b726f0a99949,233d8 ++8ca,41ff72 bbd16c.d ++0aa ++ ,9b8,-fd354e8e- 5d0c 40 ++9 9ad.4- 1 3 ++ae f-.7fa63 fe,bdf a c4442fa.5de b5f74d2c89fde2 e.5. 6ca .2 ++20bb-6.a4c65ff3 7f79,d2d6 71b0f508ab34ad1bd9,563c40b8.d11. 8ab3-cc1 ++4a ++2 0996b bf35d6- 70cd31 ++d 27. ++ ++d 79 7 64db839,6 2173c5 1. 848 4df8405.98a08e89 -- ++e.7b6 ++ 9ff75bfa c6 ,- ++46b0,1b-4 93d5ccc4-9db7e37910,14 b ++b ++f312ed5c,6e8-cf86ae6,5440a 0.5-87 065d2, ++6 . b 02a 9f e3ba5f38e73a0d-d2b6b42,7 87.fe28281 ef.7a0 3 ++f 4f178748 d 5 ,-302 8-cdb ++dd75d61, 32-6-ef.22e9bae,9 ,0,d b89 ++f0b3 07 ++bf--8 cff4 4 a f8 ++c8a.fdcf2508234 ++ 9d46db5d55 adb2a4 ++4dbf1516e283 ++5bd-4 ++4 08 ..d ++fa ++90e954a6846,0c71.-23,7 d 437 611bb-5e734..6- fc37c,55cb ,e02.0408e -5f6-,-8a310c97740 38f21., 8c 6838 ++4acc,6e ++f ++-5c 406 ++ ++a,20870e6-ea6 1 0 9.588a,49 6 ++-58,67a ++0-c28c,ed,6772 d2-9.af0064b5,-ad07 6 1a87bee7a ++2 2-2c5 8, 48005c5e b3ade7,d b580691 c -4d1,921 ++c,c .70f53.2d0d ae f fc.e ++2 ++84-18.9c-e4e.6dfa,a da256-2b a -. e3 ac7a.d7,af934.5 ++-d9,071-b84c104eb7 ++9 7,,42 48a08cc,.ba8-65560f9def9.06,ae 5f80 ++e1ea -2f,9e ca0e16 , , 319d01e11a7b9 25f-a 0d,a ++9.57- 7 808f42e89,462f.aa ,14 2c 25 cba4082f 0 ++-51,059833f ,3 cb0b a4-db 16 ++a7,a 7e 56 ++.9b179,7.23a 2 0 33e5df ,aa. -913 660- ++a5af72 4ba3821-d70-0b.45 9a0 2-53b9f5c cc 8, ++e42304-43d9dee9 1 91fd 8420 2-4 bdb.0f81c 4 .a3 5 c95,7d1-734ebd57c9 8f6604 f-d--e4.2234 b cb1e ++d ++82, ++c9 ++d b ++,-b.7 2b42 1c d fd, ++b8c06 43 - ++,,0e 9-9c6f-8b4f714,d9.c 60d-a,8 9aa92d0f0 7 7.6fd33c642b.bc6b 1c9ceb0 ++ f7 60 1.-96 0- ++a1 9ed -0 ++9 ++ ++5 -41220 ++5b7c4 ++79 ++e2 ++9d1b4 - ++ 506ed4273d ++8 ++d 3d 3-5ca9 9 c56a326c 773c7 e 300 f4 6fee74,-911 8a.3 ++01f ,369c0 0d6,30073ce65 15 d1e 534-8c40dd7234-8f,f3 ++b4a150 -a 3., 9,d.,56, ++cc6 ++a91283d,214 3f93f .c6ebbe73d ++c6c4- ++f ,0414ff6-b 2-.45b33e9e1,6,e3 ++0b29,4a-498d c5 ++ f0b75b800-987,.a2-97.c 6b68 ++1,f44313890 de7f.0.-.ee.252a7,1 ++..a 176d ++5 d .0f19. c2 ++- ++b8-9, 40f30ae cba ++93 a5c-7d56f 87 8c5-c9---a. ++33f 2,ff2e,9 0 ++cc, ++ f- 55 ++2c .8a 8329 1a8d46b-5a54 ,66bab 082abc.b0960 15a d 51 3cf .. -bf4 c 2ede ,.,c e1911 ,b19ed2b,e 1e,7ecca6 3a69 ++ 4 ++83 ++-58e5 d1e30c3-7d1c79603798-c,a.424 cc97a-6-.a89957. 1970bd ++fc9fa5-8-.a0b3d ++a88 ,1be .4a 6961d,c.- ++89 ++ e79a c4b0 ++f38.6.8,8e1d609f 8a6ce270cc194b58c4 ++-231f 7 70.. e,78-,bc1532dd cfc ++ ++9,3a9-73a2ed3e8-cb7ae ++68c 6 3 ++- ++4e8e79 ++0.05.782bc5ecb,f81cddc9c8 0345570a ++85d6f 3ae2.9 ++574,11d.526 b1-51a6 ++8 3 60.--0 ++c1 0 eaf c227 -a522,f3. ++5f57886,,e86b9.b. ++3 9b,. 5- ,2. ++e ++70579 -c 2d-1-8 b 0 d4,5686cb8 4 ++7 0-9c 4c ++f .f5 3a4641,ce1 a2d9cc8c ++8-9.fb ++545., 170c4ce.281. ee108b.401b ++.b79dde e dda,d15c8-,727-44 ++635 6455.08,85 7f5,4dce.9-5 ,.8.5,31 8f6 ++.4cec.f28f3 ea,3 ++6,d - 3025f6b78c6 a6 - 7182 ++227 c6 . , 2df973d06 ++ -ff8 ++884eb8bc611e af6d.e54a3eb,b6e56919d5b,c,3455 fa-b89b20 0eef7f 9 318379470fd8-cf-3a,a32 ++9be4626e0-,2 7a-ac5f4 da26fdc55 -d8738 4-e6 ++6 ++2.6.f ++eb4 .4 ++74 0b8e-7d ++ ++c3aea6ccf,f ++6f2f ++ 8f c75177aa ++7698e,da, ++64 ,ad 66.020 d,c8 ++ ,b19 7903f6 5 0ab5005046 ++. ++2a5d5792 ++dea 56931 d ++80257 ++6-,f8eb 4bcb78 ++db.cc-6e4bbe -654 3-. 13.5 f84f9 4f-3 ++ 966a,c.b48b 22 -0 7,34 .f 9.8f ++be226 -56e2a3 ++9b ++6 ++ 23 b 1 ++26-5 ab3ee ++9b. 2b4e72dc8a.67c6b59b,7 5-b-9667e fcfc522,a 8486 ++ 1-e8-e. -30e125 0f..2 818,4.1 ++7, 62-021 bf87-5,fd0eaa-d3a1 ++fb d09c9.- 13e7,d2 , 2da ++8988 ++64 ++51b ++-e9 1 bae379dbde5-8c8 c ++, 52,4 3dfa6935db3f 815-6961 dfd 765-.fe28. afd5a 0 17d ++822a93-1. 7c1 391bc1b367 3428a954 7bf,9d9 ++2 d4d ++4.06 4f6,5.7d d2ff ++e ++e0 62c4. 5 a.8-c3.50 ++b ++ 88 ++ e5f5ad9,a9c4f-2 ++0f342436df47b 13ffab0891b3-04d1941bc5.bb 1d b9bc1ae7e b83 0 82a 2b9 .0aa2f- 5054,cd2500f1e ++8 ++d8ea55-c36.1 f ++-193bc84 ++a e,c-e08a2bf,8fe5ea -a ++f7b0566,77 98 ++-e0 0a52299,0e ++9a -0a4 3f c5 ++-83e7489dc 551296.a.8,9- .0e1,6ec,0 -, 66.81f.1d1d..b06baca ++-e ++-86320.9,3e ++105f4eed 2 ++-a0d-007 b,7ac2 1 ++-4cbc7 -f9,a 89 ++f65feaa1a,3ff 318b ++ ++7 fbcb3566422 a.11 ++.582 ++ 6,b05-,202 c73-33767d.7, 3 a2201,bd ++ 418.8,442a ++ .235.d73-24ccd 1 -295.7-83 c0ed14 59ff ++c4a,8af4. 1.9 3,2a1 .1c556a01970-d ++- f10af88 1f9a18,05 ++73e ++0b 6c963.0d8bc746. bd-0-8.,b-7aa db9c-ef,0 1 9 ++ d4e992441-eea7-,2 f4-e0e15,82 108af13ad0c 5,1cf71a63d720033-8-3 0.d3 -660b - 75-0b. .781c ++ ++ac7c8 ++,39bdb ++3f.,2 ++-c7, c ++1bdf2 . 9354f58 e08-1 de ++-1c 1f8d7c3585832ec a8,.a 6.d ++a8-50-d f809 a d86c-9e,2,1 091e,85e 94874.9 2a20-ed -8ac42 ++8570b,0,0c.5- .2 286 47c--,a6 ++ d63 e7f310 b3.056b. ++ ae. 9e,2..,.5ea6 ++ea1 74 39 499-5755344 .24a39bbd ++0 ++5d9 ++,c1ad26.be ++5- 55a 29aba- ++ ++cfdbf ++8-d-7 7fa8 ++bf2,6..c-d0.f 5 ++ efe4 fefc03a6 c.8 ++d5e,44ddf, ++9,c6f97f514dc4c753 ++ 6,4.-a033cf87-d. ++-c ++567-260ad3de2ce36e51c,387c ++ad8c1.,dbd5 5608570d-e.4bb6.b1-5450 ++144e-283c0.467fc8 . cb1.,bcd f693670 ++2 ++52,367d738 5ef ++-94 6b8bd 10d2907b 1b9a9e,e3 6d-183f4 a2d02 85 23a3 55c 1bb 34 ++afefde-cd,12 ++fa701b ,5 ++.4-97 fc0 c1, ++-171 1 6 ++ ,.5a 9edfb1 ++-53.a4972ed 9-1c15 ++ a68. 276 90c0. ++,f ,-5-,88 -0e f94ba0-41,3 13dbbda 6-dfe0ebdf15. ,4c7a -7 ++eff63 ,451fdbe , 33 ++9 ++.87a.-9ffaed4b34-c9 d38808baf ,057 ++48b8-85f50dc54 4,-5c3472,.,455e,598e10bd3 ++7d0.e7 4478, 6- ,e. ++55-2a99662d8.5 ++b73 b - ++a42241-0369ef8984f-.96b6e008 0 18,d4c83e-7 ec5bd, ++ ++ 3- 757 ++,138b d02 84 5570. 3ec48d86.- ++4-,ff6b 1bae05 ++da32d .d1aa2092ba--f1f69b7f39-835,4, ++ ++06606f335.2b5 ++1 14 176ab1ad5 ,9bffbf415 7e4c 1 085 ++-4ff ++ 3.0.1 c 2 bc2821f 328,dd3a.,d867 27 ++7, ,80 .c,..11 5d-b6ab60a19506030-f e ,522c2. 39cd6,8b-c.c 888.885f0b,4d1ce9f98,84434460,4 1c79e373,a.1361bc-,fb4 4 6 f6fc5 7e519- ++6cc a6 ++,2d,7 8.17b,358 f add7.,3 ++ ++5e- f- 7 2f3af,,4eba-ac 63 3 4 ,e2 74 ++c39,af2 ++3ee6. ++ -8db6 0eede7b.,e..4 11c a8 24-05e578 8 fcb-b01,.b862b a 7 -cf3 ++-.d8 e1 1. b2d5d-916,2 ++5 ++.71.c12 ++ 9d ++8 ++ 0f4 8eeb0e3b, fede35ffc-2-2.d5 421eb22- c-93f,e.0 e 042 d,5407 2 f2b,19 2- 8acc aa3fc-3,d2 ffab. 2 375f2 , ,,e214-66e7 5-3 ++a26.8e1-4 372019cfb7ea99ae576e0 8c935 d91b3f20aa75 .80 4a-a 7 dda8a5.62f3.e ++ff62ec b.ac4e,7ab c7 ++ff4 c8ab eb35 e- f,-0.ff16d7 -3e98.538 ++ff,2 ++.1-e5 8772e ++9-,e6-d20-33d20 2-d4aac54 09e ++ ++ .03cf.7c ++ ++ .0..-8 ++2 ++ ++10182f2d0,8a .6 586 ,54b3556,422789 . a1ff ++-b ++6e9 e39e7a6 ++ ++, - -f 67d af 7f0a969.a35 ++,9 21 bea2 206ee0d c,0d --5. 7--7125 b46, ++ 0fd - a7 ++ ee10,a518,941 388a 8,c7f0 .28b,bc 8 ++ 5881 df 0-1872ef -22f 749 1b20c20,7 ++4ca 8,d-5d ++ac-fbf5--f5b1a4e, ++ ++6,0f408 ++ff9f38bc8c30.c88406a ++b 5 b45-1b2fd1 ++10c 0 e..e04 ++,-a 417.29ec ,eb6eb79a38 6e62-d8 ++262069 2d04 2bb42 f91e2 2 ++8, c97ff80- 1-ad9d63e240e.4346 -b 1c00 a-8f58, 4d f9cb b24 ++dd65c36 f.b6c 33ad,5 ++af68430-0216 c ++2f6 2f61f00eca5a- f7b17 ++ 04 58 ++7 e--1.0 4a88-90ff-c.c20 e e ++.,bd8e ab6034.,2ff1,3 41-729ca.,c3821a fe ++8e1. 35 7-7669 b3e ++32fb8-8 8.-a1 9,8d70 ++ 7a. 9c17.f ,3c 88987-546e. 2 ++ ++07, 02 -3aa3.0ee,eb486ca1 d0f42 ++5 3e9299.1f,d1-6db1932af6 5- abe.2b6b,4 ++6f48 0.dcab6-01 96.14,ecf08fd3d9e8b e3eeaf71bde247765f-f5 77ecc, ++--2 8 e 05,df,9d 53b 18498fe,85 6178f2. 0600,4 ,5153b58b9b6514 e8b1020.32 ++06c1c 09d6 0bf6e 8b0b,7 17933cf69214d729 b2 ++ ++e e460 ++ 7386-9 .9 27412a76, ++4-b870-7 64--9.2 ac e3199,b0..,5 6 dc,5 -.e ++e8728e ++8e2 7c.- .9- 0b5. ++32-. ++111013ed23,40 af5d 16 .264a85 .b0.4cbf 88.1 1a390e.c8ff e5a-2 ++ 6d7a 69a7 c2 0 ++d,., 819f,b4 ++5 ++62,.51.e0382 7 b9cb12 ++90ec 0e1-ef547c880 ++9.4,0 ++41256-a5 ++,66a b,aa42a009ba9 d5aab-6f9 ++2, 124c25 29- ,..23d8a795 ++4c89 c04a80 ++316- e6 15,cfca5f26.1,b6 -.be81418fa7-0 -edea,, a7f,f7a-c 14 ++5e -,3.903761 ++6577ef1.a84d, - .7 ++ c120eda8a03b6b ++fe3.5 ++8.f2c ++.1e28 9 b4,d,94 ++2e 017 1.-10c 97 851a ++d ++8 ++,c64fbfa15 16a5b.b6233b,c059ad8b39e43--0b9 09 2 da9df23f26 2aac3e,.8 .01e33ea7a58d60 cb2 ++8ef 68 ad21.d552.f787d328 c705ff7 25 d.838 ++4e52165- .13c0 ++c,e ++cdc 109d .. f88dfd4 .afb00--9b935.. ++ 5d81e15db72 ++8 28a70b 457.0 ++afd4.. ++3c ,ba7e340406a5 ++361c., 9f.3e.beb02 f2485.8380fc16ca41 ,8e7 5091e10,d ++ ++a 6d4ffb -.. ++cb 3f. ++ eed7f44 ,4 75538b3.811 4df7 2f0a1bfe305-ca.357, ++ 898d0 ++0 ++,bcf2 1.4d1,04bef9e4f08da5e3f4a 0,e15 ++b3434 -9--8-c61c- 37 4b dd6 a5 ,09778d60063-f -d cec,a8a6b6,1a0 ++c2c,a 52fe33f6 54 47 ++9c 0b9 ++67 75 ++,625d--1 3c98b8 , 0d46b3 2 200-260918a1398 ++b ++4,ce f,1db6e54e7b1d 2d2 d508 7- 294a 73-0 ++fa5 0345c a.c6. 4e074.1.602e52c b31c8 7-1.9d, ++4-fcd50373-54-ad47c10,92566 84 f ++-6b.182 ++b1 ,696d046d817f-e,0 d4- 5-a4ce5,e19 30 ++babf25.d85-0 -e 1bf2ee3 89.8..e42 ,-b a.c10051.3814 6-76 468-, ++15 9854,d,fa 8f-2 5d5 19 1393.1a19f48 d426036.e3f8 ,2e8d13a2c03 7b 10 -d b.c,-725,0. ++ cd248 ++a177e--d 451. 55 8-42-a 12d 1f27-da..58-8f57b- ++b4 ++ ee928336 8ae10008a b61.d1 ++60,.171 b -0f429eac, 511cb,5e. ad 7eb3e722f -a1f 18bee- ++, 6 ++32 d3b--c484 ++.d3c ++2 7.0 7 50b3 3 ++f 8- ++a, . 4349e4ad02d5c3da9-3. ff35 a 4 -1ffd 8 22604 ++-e.d6 ++e-,8e9,5. 0e869e6286- 904 ++d731e7 dd 4 16 f319b fafea3ef4b f1.9 0b 5, ++35a7,3c77c68 8616a 69659963420-fe9362 2-39b0 af81-222a -5 18 ebbc0afd81d6c.8f43 ++-5d.ebf8ec fb,d ++99c2d-3f,3 .92-ce.5 73c4,.7a-4.50f0c5 -392d7 ++3f606d99274 ++f0.b-,29 8,- 9986-ee ++fcf ++db.-b9f30b-a72.5344ac53 9 c77c ++ 0,d66fbba-f8f104.a b3,9 4c 3 f ++e6130944 ++.e4ee8d706b383c-b6d16-7 d76e 2 ..3 b937,0,. ++2dc1e 6813-88dcc3,9cbd- b71 ++95d73b,f1 ++, ++0 ++1-50a- - e-7c-2069439a9a29 05.516b4 d82ee1 9564 -74 d7ab ++f,fe,7-c 5a85.d. b03e-.5b- ++c,06,c2712 8 ++50ddf ++ ++ -b7eac5b-c1 2,.5e6 8011 25 .3 49-7d4 81 d8 b, ,9,. 253 fb3 93.c 4-6 5d9b958 bb6222,.,.,.525 d 07f1d .42.19 ++7fa5 ++,-f2a 703a3b3c ++ c1dcfa5b2ecec.14 ++ 8526e 82 ++d ++344f71a72b.8c0e0 1f2-5e52f704e55b,d ++dfd5,e644743c96a7 ++19b,c5166,081b71.7e5f9d, . 49 6,-9-91 1832e0d78e879e.a1e -d1 a9,,6b 6 bad20922-4e,e a,,f ++edec7,.68dca- 3f09,,.3c,3,., 8 ,8 ++6-cb9596,3bd ++ 919 ++6- c8 ++d4d 68890a1 74b8-73,219 19.0 ++c5f .54a2b9f .1 258. 13b058. 1 ++ ++b 843f2a2-411,515,- ++7.9-1e40,4a9e14948 ,ab7608 ,e. ba2-1-f 6 99,9 1d..74a,902bf.-787 b3a79e1,e .,33. ++afa-f ++d8c4a26 abe024,0c ,,c-a f-b 6,cd22b5.99fc 1adc a09577c 654, ++ 0 ++ .-b52f- 0 cbc563 .8 55 ++1bee ++f941ab1b ++7e9f ee ++b7338fbde a7be .9 93eb30d69 -2d9.fa -.23a6.92f0d-,fb2dd d8 ++9-b ++59a4 ++c 7 -e ++-bc3 2b9566-dd- af82ae 12d111a9b7cde .5c4-f5be3de e8a33,666bc2fd8 522 5.26 df,.d-1d da-2f,2b ++7d7d4 3 7fbe 38ad2 ++2,1c262 -3192eea2 5a55 69 ++9569c0,9f1- fbc 1f7b6b1 ++b4 ++5 12f.9 a96,e 709 a0d 7c5dbef20 b004 17d cacd-c2353 27 ++c 784c4-237f 46, e9 93 6d96631a b7 f-eb,87-1355f3 e554 097d57 ++ ++d0f756709c7 c1 ++573c e,.274 6-5c4945f 8 ++ ++2e34067d2d00,2 ++32 f 1 c041a ++,. 56.237 94 2,422107 0 3 958932ae-cee0 baa0b98ca.-126d67853cd8180e905fb8b3d085f26b6 ++3f c5 834 44.6dc7003140dc5 - 9 ++0a2e- 5e.f ..cc .8cf4, ++333493f-8542 c1381 - 9- ,ec 274f. 7f83a3 d5f54bf6845 d643b22 c8,3-1,4 968 c.92d264.78b8.7cba 7680. ++ a31da1 ++22c39 e-2,a6330 52d5 19c ++fc, cd49421b49375 3 de c ++,f2a1d3 .de .c ed3 7. 1acd 5273d6, c33 185dc0 d e4 ++65.dad 5 ++1303-0-7 0.e7aa9a ,.-.d a3772.2825 a 65644e7 ++ ++a2313 0 ++26 5e9ab3313.d -d9f38 54,91f.7, 7d8,,,a.1 eee3f1d67.db .5ff bcf3 32b ,0993.08d1 b 390f02db 9-d1- ,14a ++39514,5- 5cd1,b- ++.347-e185b,69a-b, 1,64 4-b ++53 ++e e1 fa1a8- b33 ++c5. ++-78c,3f0,6b ,cd-77fff69ed ++893.,01 ++e7e ++3.c- ++0e07.77f 3 ++.a96a48, ++ .c16 d, 02a685b f8 d8054ae3.691.92 ++e ++ e3a56-70. ++ ++2a-c3,,d.2 ++,47 ++7 84.7 13 e 5c94 ++3e4- ++a.e . ++ 96 ++34 ++10.1344c ,e 899 14 ++860ef-.a.5a d-2331bbb-b2454 ++8c1781 0b8 ++6b64aa ++.04f 560628391,2-1-22f 7a,,b.3aa0a9c7 1.96c5, 6 0e.1.00 19-e3 897 af3 ++53 ++0f 7e7a2 ++9 --,492 ++f e.10d1e2 7,5a 9fc7b601-8ff-c8b59f.67..d267,172588b . 4cb84 9, ++ 84a-d523f 4aae301,b.76a. 8c1.0 7b,5.,9 ++e235-9-58d- 3d47af ++.391-249b265. f d e,- ++96687 -5 ++d75. 031ba b23 3b ++ff ++87a 18b a-b4f 225f769f2-a .8- 65, c73b.f,b.67dc7 5 4b20, 506f d8 173-0 78 f. ++2b422.95b0eb4b,5fab2d5 50488a8 7bbe92243 37b,20bfdd d22b97d,c ++ .c8.,6 2718 ++9 ++ bff969fb ++7 ++1 4b 85af65 9a0 , c46 4 ++a9 b210 9-,f.5 f84db.ff 720f9755505095e 046 5bc ,099f,748,a33 5c8bb28-7,c 841 ++bed,d6-7d5 ++b4,1-, ++603e0258af ++ 8 ++63722f.b,b7-b 9 ,b91b,0 4-e 2 ++. .e, 97066 ++fad8e,bae ++e b5 c6c 3 4834 -7 ++b ++f91 d791 af 0385fc55953,0e,.79a3e8bc--ae, 5-1 a ++6c78, . db,-ac294bede689 74f28f-4f4e4 18 .,a,f- fc3e a ++7b 8ca3. . 28 -a1 ceb 6ee16-27-9 4a3ab3 ++ 4 892 6e80a 9c8e0,42.eb,8 9d2ecd ++0 e- 0...c4 a , --d391 3 ++8-13 ++5d,9eea ec 3e-4,06e-b7e910f832 373,eb1dd 012 c7.cb3.9.dc b986 5,,19a5-7.3.81ff37 ++55d3b.0 ++8 ++bdf, f8de 23 ,e28,3e- 67f34-1,1,6 a0282 0dd17.a 18b8b64b8a 9e 402 9e2b 4 b98 90 361 02,8194882d9 ++0-e39.. 47 ++. 8f,3d6bef787c,-15ba ++8-f- 334.750 ++1. d,0b.e42 ++ 9352d ,5a-33ef99e 3f57ffc , 91.,9 db82877 ++3b3,b ++,758 -b ,b6 10ef047.af.a3d ++-c- ++293253 7a, ++3c-1 55 6e42f7 ++da1 6,8e92e23eb2 .7,3 54d2e22a4f8c7c0601c7 .1aa88a5e ,f55.c ++d ++c b3 55a39f419712 .ae- c7f3-44e 7 a5 . ++ ++ea8 fd-32b8af88ec .bc8b0fcefe.acd9 aa- 72 071d8 8279- -4 db1f0 1e51a ea72,f43, ++a7db7 7a, 206579 ++d528 ++ 440c165-d54 ,.682, ++f91-47, 28 4c 26 2ac6bc 72b- 21b 903b,f,e59,c0 ++2c9 d9 ++2f45f2, d46-7468 ++,787e2b7fc ++5f90aa-cc0d3a8f0 b. cbbe-0 cf1b9. c-0 ++de27,c7ac6d6,063 f1 ++1- ++2c-c-1-0 ,.587e 1398bbd b. d 83f.5bd 81-fc -,b-3 7 7,8be5fa- 5eae,69d5, .fd c, 1d72bd0db1294 953f..b14 ++ d.95. f ++.,.a93 ++d7c.6 17 ++23bf 76b 5,6.5875a116cd019 9c2 3 277 2f50 18 ++ ++.c0c16c82bc8 7 13812 41b740 ++ ++9d,a40 a, 79adf..f567 ++f2 6f3363 ++08cf4 ++6e 2 5c2732-04cacf3c5 ++43.7e,f 29 b 8de152-eb,f36 44e2805 43a, a,96 .,ef8.dc 65a2 ++-b7d 9 0 ++a9f-.5a00a 9b 2aed ++73fb.b-a6b d6ac-2aaa3b89.0- 6 ++a cb1a ++3 ++b0c 1 0e3 6f1d-9.,c2bdd- .a.a 06360-4 1.f4b935ab04c,e5 ++ ,aa30c 9,6181.d,3ba ++d3 3eceb 9,87fd ad3 ++b1e-.26.-ee.13 ,, ..d0695.5c4e115.,20f90- 45bcb-, ff-21,f ++a 42 dc c-4.6bc2f fc8d5 ++c704, -41 ++ .c358 ++ ++a08c.a7,2,f,f - c42,- ++-23d04,5b ++cebc4205807c,- bf ++6023bc8a4,0b1 b15b15 ++c 8e581cfe54 8dc122b.14709..f5,7 9-5f-c,0a1c5e62a7 2 361e7f07 7dcc,fe6eb31 8215 512817244a6a,88-28e158227.0 e, ++a b, ++9a c 42 ++ ++ -aa01b87,1 ++66-5,47, a3 3e0a61 ++62021c8.73 5-85ef ++a08-cc.4a73 ++9c b ffb dd- ++eb08.ce865 399d ++74782 ,2934db5 c 93, c8 9 ++60a,d .1 b 9da,4.c0-5fb 52b 4..5 ++25-540ad 116 f1d540,2.-fae60 ++, ++1 ++ ++,66b53.e4917 ++8cbc1c ++68 ++ 61-c,2b,91fd-55 3ff78ca-1-,c2df3c,2764 bdc7cff4-, ++b4.441 b354-cd e835.03fcf 46,e31cd08 , dc ++5 5 -164 43 4470469 09-3 8261.- ee6bcef. 618b 732109d70 ++,93fd2391369756- 1,f2c 8,119 7a7cc4fd67b0-a-f ++5,2 -57.,d ++9,f.0123- ++e78,933f- 29e40740-9f4c1-49 ++00c,-d. ,f9ca 90- 0 8-7a ++ ++,3 e, 24bd96b34.b.29.,9- b ++f27c ++ ++ a .3c449 ++bf e41dc8f -b3, ++43 01 6-2 ,e.9a-f1 ++0db-a-773-f7b 027a2.e9-e4a5 80,a74 ++9 ++9361 ++9fa0 c 555, 82471290 ++ ++2d0c0 0c562baac030ddb3c1d5-- 86dc3d4f 6 .5d-7a ++ ++28,-d4,2338fed ++b ++. ed2- ++a6c 4a ++ , ++4 a4f1835.6c0a56.-- 972 ++ef3 03 , .1-c49,df,926 ++e8.580 ++5fa, ++5 ++.4 -. ++4c d. 71 ++42c71-b, ++bfbb50 bb4318 24- a ++efff ++5.a.a3,ae58.64f015f,e,9c83 9d3.4 771e0 ++ , .,d 7 cd- 0c6,8 6119 4a5c165,-1 ++2c4 ++78f5.57-,e. d 7af6a7c454eb,b- 0c b1 -0524b99d6-f. 9d707f899052, ++e -065- 6.e3d41d7508 c0-8c- fd2 7d 1 ++-414 f 7363ac02e7.30 1 ++ ++ d4c,a.1e ++, ++ 3 -a9- 8eb1.f76 4 e ++-65 f4ea342,6-39a-444ed ++43 bb6c00e64ff.a3. ++b-def69 9, b8 -7 ++b1d ++6-06f0 ++ cb6225d6, ++a., . ++-,ed2 d546.3d -08 ++ ++, ++6-e4 ,-095 fd5f ++ ++2351e844d12f0ddf4c,d 03 7 cc-ec-a75 ++a4db6f. 93cb.-b 41 1 ++a417f3 6.d-04 ++9b 034-f 7c53-c ++2,17f ++ ++18e2-e1.e-a4, 7-,496b296b7ca.,ca614bc4-, ,286a,7b 1490 bcb69188,6f0. d-a5- 3f041a2813,f2. -cc1 ,-6.0-c0 -ec b4d ++,caf5 2c eb. b30d1132c f 3 2d66f2 d803659ac45faf3a1236 ++d14b.471345 84c2 ++21f ++ 59bdf b0f06d 6201 c,2 23 5 9f7 59a77 ec-2 6c1 1 137f35593d3f533df.81,db 148 d ++24, 466c4afd b1d361352c,744d,1b2-d ++5a15542c b,c50 -,5437eb1 ++6.fc,7e8b786. ++ c e 6b83 6f39 ..a 0 b10e3f8333 1b- d ++2b6-c53 f2aa3.6-2b,8f124b.7 ee-fa ++1.-830df8 e9 ++49,-8c09,.4acca 8e ++53 ++.56 4 ++b70-b fbab84 bdce6-bc,c56e 701- ++2358 -63d8 ++965b 9 7 361f-f3ebcb1, d1.727024,3 . 7fec 3-05,e 59 b9f ac7fa4991 614bc--ec-a ++ ++d ++96 dd6 ++911bf. ++3 3c7-,209719a ++ ++867e9601- 12e7aaee 3 f5b ,..1a545b ++ 7,,a68,.ad2 1,0.c67f4f49-f9d44-95-5 .,efeb8 ++f110c771fae, ++5fb61.0,6-78a36d.264887b.444322458 94 a8-8ab ++658 ef.6d305603f4 ++ 5 992 . d215 ++aba206.ad80 ++0,df7fa6e43 .359-305bc68434a9 d-b d 77d ++9.43.5 d-2db ++-9d1a03 ,5 ++.e,cf.7a 30a4a748 ++2 71c93ba 64.-eb, e -6 4f5 56c ab4e2.6d1--37d,.3 ++c487a3c,.3a bcdf3708ef5 1dd7e9-1 91,-18, ++,b1 ++ 4 ++ ++ 39fc6 fd e3-ed , d 845bdc ++460-4 ++8d9 ++ 9356a1ccf- ++7a14 ++4a94- 758,6 efc 2-6 ++c ++ b74b 0 ++-7 d,ff ++,6-d, 6cd -4 3606c48 ++974bf51019 ++f3e9-1f5141d4, ++ ++317.e7 beffea,d54 ddc-0c- ++, ce7 ++45 ++ab2d .ba41. 4 c 9b- , ++3 93 ++c - 5 4 ++-f ++0 25 fe7 ++0,0,0c ++ 5 2b7047 ++ea d60c1 ++0,4c16.39262e912-7d- ++-ee876c2 5 8 ++a.2384.5df-4e-7 ++5d8, .fef2.d 09 fb9fe8c ++ 08-9f ++d 8-e e 2. f860b.e3ff-5,8,60 ++,,.52 22 f ++9 .b 8d.8.c934655 ++9e9 ,4.764b8fc-d9c -4 , ++. ++94a22 2-64d ++ 3 ++ ++4b081 ad,,2 d-4 e9,8ad f6.1,8, -,0.4 53e4-9ebb,5.b53-0.ea6e2aa7 ++-1 7532adc05 c7b01 c, -d035f89954a5 ++6b0 ++ee 4fbc,9 e-6e7 bf45b .062e7.a474494-.ca ++a72 ++3ce9b4a4-5bbbe3e377250 ++ad6,ddea0,e ,bf ++9 ++41 c1097dd6882-02bbcc8d10d-a672. d65ea.10786afe94ce7,71a415a8e5,2 308c53cfade,bf8f,e84 1c454,9-fada6-d 94eb6a ++.0f39,-96d97 a ++,ef24 ++4, b1 ++a167 ++6c 5e9b70.7176fda4 ++396f8f6,3f,d01eee 14 147dd.39f ae96 3 ++c..8828e..53f3 7,., 6 6aec495c47 ++ 42 d3495-dc98cd8ec4 8abbbc841-52.5 c9de6e84. 4e28de97 b ++e ++72e dc,8d9585,5424f0aab6724df3 7a .43b,. 80 ++6.3-c 17d22 5,de ++ , a5b6 ++49 f-70e ++ 4d 41 -4d d0.1,bd,be3b9 -- 86 416c ++2a04c08fb.305 1f-57 ,,3 1d d0..1,16,d5ff ++ .,1fe57b.73032.ec ++a4d6,4353-02, 5.8261ad fe1-2ce f.6bac1 fe bc.5c7c.7 c ab ++ 3.-,08-b587d4 ++f3846. 594361fa.4a71378c8 b8a3e55099 6f 4e +\ No newline at end of file +diff --git a/comm/third_party/rnp/src/tests/data/test_messages/message.text-sig-crcr b/comm/third_party/rnp/src/tests/data/test_messages/message.text-sig-crcr +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_messages/message.text-sig-crcr +@@ -0,0 +1,703 @@ ++ ++T5WXT j4gSOE9isAVG Nm-wfc8wB aaH0KHRjy2pU,eoiSk8X,1snr3dh6OvKifdv7OgL mEOxHbw1phWrxn2xGKj06T rn4QNalIM,c4hpoU8RyzBkCB1IT2QJ,lynPy7SndNOG4.Vw VxbXbqklpBnltk-g-5Ec9cE4Af.WZ ++gY.U8Xa6 Kag5y4sHQ 3tNv6OyQsWh NpmZZwA3J87-fHffx9tz1UkWtdE 86- ++-BhlA ++dLiv-WXD,-ygD 52W4fGqxjqM L2RYm6xYZT7 ++ciC9zG RuNeVec4.DcPYweNLW1ONuAUMz, CTPGtI6Q8zWrzBRGeMhVsQgQmb0TACc t.4bWf.PeNnG2CHAKa0U8M-mXeq89KAbYH97E2Uc3L Me-5KTw28DEZ-l7HbT ++v G ++Gr9BkCsNr5VXncGLQaAO7N9Vl6ptl3vU4f8iFZ3N53r0Z21Jo7 ++ybUX-Us7sJwNwfNXtCMO4bZ8lj fjwB3Gx1JD63dya kN-4HycLr3K-YQt6IPP r1fH95TfQUgu0ktaVhhagMOFMy6 mY XxiuzrMsA8,aVutV7AE3J3KFveI4pF SdjNGlXW-VGSwq52onbyY mqp jo6g3LwwCs1tnnSsfRGj BYlc pKniT.U,92i,paLgecyYX750niLl4JLdWKLmQwmw6 2roU4jZOZLhgENaxDZZiou78 baT64yam,P89MTlu2rTUFotVLN8Cj5GYqzMQFvozaqf,63oRbI ++T1k9xW k MwdoRvi8Ju0DpYz19FYcCTH9On5B9BhSb1Q9Zynlj.TwnrM2U,73mqwHd -PJMexnK1c 2dbwa bBOYgdrjbxKxxlkNI C5KN0Iym9BE .IOkI S,NxKNIM ++mLaXrtoYcBbfZRwueASaOhorcPhfLFhVya.MN,3F59ri1sPkj0SkBWcEEKD439sfGe1RKlAuGy.IpxzLlRbCWJBCwZt3h ++C ++IHsZu2 Za1Jh8 t,C Mvvc2.hnD ++YZf.fGHP s9SQIs8Aa8.iGi-CYX-DEoNUC4aS0o,pQP0ezLKu p3 Snhq9LZLxdTdF1yFhOos5X QF zMkgUehW213XAjll-KBIZgv3bKnddY2leqJb4QRuPRCj7LUPCdQ40g8MOSbvviyHp ++9.5oZLx9RCFk.i4PIYO qahZznTbmyKcT9odVsE PWWOGEc3Wgu loO3DRNQ.pZ2m ZD-PXCG6LQkGDoj-4gonCD4uhGOzPZAf9UYTszSREmdL YF6XaN2gbfZkhcO8dCIGWkBW7fZoHn0ebd 7hFRI2Q8tgzq r ++WDVXTEU-KitMWcQU5YaUt ++O8Sy4uCLom8nAqboVaUIQOGAK-jT66o rOXAU41 l3fS3 iepWgNYoznpq 8jkavAQZFM.H3n-bQmi7WnZHsiu.j-9yq8uFOg965EDCeq GU85KPncYc6oY0zhgZ9lOOfdMApk1RPDfuI7Kr-,Sc ++a.X02SumxqSLRNgYSe3Tq0EYSKXexqxvMNa-tXvsG3 en,U-MeS4f8,2G.AUVxU4UeMgRri4sy-hf05UMt0Sz9bPoiVb0uBQLTYvv3ACxT5O0rZ Z2RSgmDErzLN9 ++SxI-XBk6nkH4e6.xx5oL9EN S41Ir20-unX4-ll5 ++3YK5t90oI5fPNCc Lt8m57v37N Hy-958gGc,o7II ifi-BTJDCPIuM.yMHPtJUF XlR0NnWxTR 5ZaFA 8L22 ++4FtWC0 ++ yFgmT9BY-p BGrFe3r.yk 6hZ- ++1m3yhiKi wPNTe7zfvCLtSJARiCP09mV1widXAWruJtiH SqvZ75upSW9ztgBcJY76fjt JCnbVJlw8ZR0 zEEyTeDQqqHx3Zoqz3Vc2 ++EVUU9 P -SKH5 pVRRlimJteo4MEdS34.KfuuxMLXvj1V7eR,JOBT6zN9K3FOszOMNVO97Jm9rC6MgCP7CzLHfJk1AsoB-lpxf3Kn5DVE B 7t0rNZ4k7yHhoa,.kQqB3wkggKNK7RkbEA8QF.2eohUYIOkG8wc4n82 ++TGVEwN0H2ObpZ2krROycRWftG.6xFNojcUJIVdFsbu3a 2 s8s,a8 ++8B8i-iPI8KUibpJgCOu9ms8BPfxYgGqg SenRXSBez4-QcogoeSv.GzaCVd .dhjSO4VZJqjJzEAPL37 qopWhDAaZ09S7c43IVZ6uOTfH2nZpSOX5wcAR7 ++z,mOJpQDBH3IvU, Wt H6qMlnp2- S2uTQtSJbPXqPul8xTdO837Ewgu1wGRFF qBvqcdN4dsz3Vo8R0FM2QxLiT9PShGEJ zh Y32,8,R-T6looK58qQNDDHD3YQShk1UwkgpGXsJS1 RC1IeQRujf0ipNQ-SW6R6eztkZQHmD CKWDDazT-7bTL1h QXGg0CiwacK3O5Dkr608 ++ GWlyg8fxtgA9,MU H7xb0I-2OmOw6WYl,05fWuDzAMy2vWtPqQ 2zqPq zINTCwsSDdzGen72 ++yVa6nySl.jS- YW-HoNuT12SF ++1SuG eL.9NVptH6MKYi-o6z ++Q,XIG0KrzilZ,btZTQaMlHL ++nvZGw,5emVDb ++aXjfmOX DpKB.U8MD,Vyr-4crPfyA Aa9 ++f,,wR0Td4J4ik1l ++zW cYc2XJljTewnUV3sKletG,j9x7VG K,q3nko.altSuhuPY6pFrh,fykxRk4Hkc.4dGIrpsO8tF10mRu8kqfjrnt4h5G-28DOwpm5,Nl8oFRXAC ZO7udK7E4rb4aKs8wVSS52 ++imm34.8nRdRqX7 ++HZTrryuKHXX aSyiVNBquvUotBUS13 42oUKYFN4HwSBFIdsRjr5pnz5lBstzVNjY75Gkumm7G5tE H2DMI7T4PlkzSjzmdFET2eMd2TEPfs KwGKmrG R6rJFevUx-H83KZ1DmAbA1vQSlXIB8zY2MeXuxFFI6 ++9dM.U359 ku4J1sJHx ++2Evm7bIOF Y4y,3YN6QOGD3IZtcdv6QafL RV7K2J-.H EZsOjJ5XcEU.6yKLMxxmXv,I upwaXdcEGlw86,MFv9 ++s.Quj tiDuN 8WpGMNF9XAdD7DDvbAYaTb.g-6oVo9HaSf8f5kRULvJ.ac1zrwV6pm ot LV0fUnbTS2.wHqqWu,EuONBG e6Jezp ++i8 ++3eT,GrurwWKnPanCaxN1SgjTj7XSe gAf4KjMeb8cjd-1GBPhjoJN 7CCr4Y3zXBrHhgOFohUhGqGynZ38Br nemoJWtcl529tccaLGB--8,hberFZoQ55RXnmTaJ0VmqWkOrMOHjMWzT9e8spgqirl-t38Kz0xn2TCLS cB-7. Ve9228yS2ofR4nt.E5VGF,yPnhW .ER1ygUsa T oNBzhYqGD65ArnX,TDEc 8Exp fJerXURiqm3XOOo1 hZYyivHaC rLFEsCA IRiOkR,ZNSMDVPOAyEU Nmc ++77 zV1ZkzPlwytvy6tphekEfeD Y1Lh5UB4 1Mo9mH4joq0W,Ddmv0.OX-45L8D2RaoXclm4t4 wCgmXumHkONDVg7lU6bn3f7iGII-UhvNolxcvJHFIIJnAF4 3JPV,7Cl23-FcpGpAK7-qH7 q6gAyPYaGkjXoJ1HKVFc0q2h UGu8AjOAlWJZNm Q SMVlTukUz ++pu5e3NkSri,PYMY3T vrz ++ JE5kC ++0RZmrUDFaluuf,Z ++18 jDpQSbwuq TAi1 7msi ++k3Or6jwdLH8aX2VwMYmI jS5f 712u.cWf6.BGpn yS ++HP9phNu2gD6cMyeZNYU1jVu pZRk9h d3Uq3D,,c 4tJFOkhL-5bep-VXl0u8TB.kBmI26v1vnrCDGIqLcFLNFM5 j k.G4Pzt ++kLjT90wM13UDtT-L.8dPSTvdyjzhxoQr-UpZ.SJ2hm-jC, ++q7UPQ,RltplO98GxBX4fc9EHUEHsvr4oa8,Ok9ii34CtrNI -T,,7wAefx8,DkXIKr9E.NJxXynexf RDkYuA1TEn0hic MCEve.IPNTYzGSS30wZkns jL,h sK6p.-5ba7uULPwDhjmu KwsuB.84xT46 lDdiKyE2Ts0fa5RZGa4, ++6wFsvNzWix7,ihCFVjrKGdOW 5d1Aryr4rGNNjjPOAXcR Xz31YExlMSHNak du5Eob3xbJ-Z ++B aOHx7FQM9 T 3PEpUKVQPwt 26KdSC86ja1J1PSCoFPetq-fG8B,dkkAmD21F9cbKZiS8nF1AlQdxfcCFHo ++s00rTC8j9dL6aIW,bDXMo UYPpYtbuPj7dyi- ++or48 hsuw.urGbpJra9QMU,EyCTnL2b4f3cgT1WUm oLNhzDQkpPOA DI6KbL ++w,FBW.D5XBTXHj ++ixX35f66u8bVN.m.KW ++UVP8u1N00LhG6WUCTV-z 8SxqeKf4cLNZasPHnegfr gQYQdWBXi5olQDmyFEoJc5AdEKqURQk7z8PI-Fcge-rJYlWZ0 8pJlyC7j9eCbmCuBGZJi5lQ5qyGHBgClrXn WI9LE3Mxdc ++YUEtp7sWlot ++QpngZWUi9DEsK6VhLjHI.4u 1hza4nE.X3,QrJc9hdOXbC biZGxOwSiFP4v vpoh asl3b3eQ vOXDV C.UMKep-pZqASXBqxYl,KEV zlNYk0kydiu 27B5n26DeOmtoS JmgKZu,AxvhGbQfUFDRMNhPlCNsz2 RZdXOfR4F3Fqa.iOwvYjCDiEOfIAq6poy.1mYq15OvBP1F EJwYWKA3Cu ixHCw qLF4zw9FswA8 6 ++ueJ1rmoDGbKLc ++sgJ i54an-Y4ldxe x0,yRtyUvIMLVbKMJnjvrWLhL aLPdx7zqSUuyPKm4xCY9JRh 9C1zkFuYplZmxyx.rcXUUpBLWMm MAUOTuq.SranlW8V-Gwz9KhL AkZ4ZBjqRXxIlDEHj1nr KMhdXqqRf QVlGRq5PCBJkZYdBYrz,7nq6XWoyAonGlC0c,F a0XzT71LmrNtY9 CW7rse32hjTmkcmPb4fHH9yY,ROfo 4WIvcF jC5EU Lrwy16oJK4 W6zmLdlar3mDz7AY1V GoOb6Q ++rM T ++zr26LzBd vWZPCRdSUt .fbsisjI ++KMx b3vkh.qIahBwokI ++Y9S455BIiQDTyq4 ++k6uE2ojreCpZuVfFqGX ++nRTKeDTn8KMmd1dPH GwS8v6w2fA, HnoFqlP ZLbXUKVzLqdH-gPNqkpwAPNZ3ZyOpH2RcZX38JV2brwWn oN0IB6 ++Z ,98KmjZKW e2c3-IGB.C6,C5yEs7m9 7brjehM9 KivMpuhqPFl.189tApA6R8L9l qv K9wRhM8ArOV y ++P5 ++F3ufOUAIu0klkjBgILj6k06Kn4 ++u,HjmN.yerft ++2Qk2PGZDH ++paMqyJhCUJbNI fPwiPl8BtJtT- N-1-T4MFrUdxbmBTz 8PGZ 6hvgbr7mw pKuLQtI.7RpDmJpUQl147K7ExUdE ++10djCLeYc0o8KsAfh5o6n3JXtx-B,Cd XS.BsMJE0iUGIYMMa8hUAI8VYP X1EZI6II2tPUaZr3MieJp69 OQYBT ++0S-Vk7ngLmOpFQ,PxiaJn0 Z-kysptvNzzhBRP b8ZG qU.oA ++uEoorWCxkBwQ,4t2mXr ++orM,rJ4KDjcC MvY4sd-wANSG HMs n4kjR2EiuzsFtUc.yGqhF3t6kskGCeTY9Zvb8hV4F0 ++8nKVl9v Ho0-fd9zaCG2v ++q9LPC4UuWBjEXCSkyIK2FH5CvzAxid4zaxECb38IJ 7Z9BDC ++ Mj-6-MsMSr8vbseyMrszj.GW, y20M.RxWQWGgC,8jTecMDn gmH.7X 5BPM89PYgvij Dpcit QDQy0yRcp5B6D2dguce2FrnoKC6 ++X8PhFgv6V,lAqq,LNAUl1xBUvT5bs,S ++sCPm6N5,fbr hauAvXc9 Dj8mnxlDKOe8VgXdsF ++oLVKn ++Fqy.D2 ++jOrdPhm ++B u8PMFeUqtoX.XAkYUqggl.KNR488 HR7kzVN4FtTS9FOxFBb8CQ5m 7 po0y5tJ j,K.VQeEYxLz-AZvLs6xmtImr cq0LwoiBedTQ,hrcexZJ,FQXx4QGE ++K NvfE1P Urglme1YwaR-rz..1i2htZsQdQLpugBDY6N5dzXBA94T 7v,lPiBp9w ++An,VH5EXmZhge7TG0zP1vWsQ1Kn8Y ++gQ8S87QV48Yu6ouWFNxoDl1kG ++NtceRhufbD4xx3Ohvat1aO1uBMrIClrkp ++aqFvIHJmFFhu1IjORH.kPkZvDj3puF7PBk ++x3pdwvHoznZl747UQ1-cas ++FE ab7p.0W5KtDriMXT ++E2eFcpHUpIYnow6Xm2De-pVI,rDfm3VZjqRZDSe nW EeJ ++WMtBcpcr9T1 Dt9-v.w34 TrkI2 -2MIpCzpPFdb V9n5nSo27p ++5HPkETjdU2cVqSyEvzkAI hDGoj7gUmlf74iXhSzQN6.,jZ6X7jasLnW7jOYMpSSKbvk gXG2vcntTA0jS-wXpll7d-dAFl8qj oOQDXI3MBIGisgLB1XDWhHy zQq5dxKoqlUUtzkHo7ZBeJPKLAtpRm0I d3.S2K wGYeJTM ++B5uq56hretX 9svxKX,FdL-OD9vFj8 ++q7aOU ZYz QFgFecm3FglSYA5n riZons CJKDs65rcDwzCYai3dCt-henVKemg-InTL4S,LnS lRXn2F67 ++CU ++1HZDj.O5HW65OaRBOq3 B-1 ++m0wF PW GK ++hg6sZ1n PSi5.6CM.HJkyurIWL8O653UQbp3xp,d-0K0vicEuZKC52d84S5Ut3qa-q-GaSpOw7I ++KxJKt tR1FSM ++c4-L0QCsU68,eop ++QZ ++fyic4CkaZinDP6bwrmKhr2 NpYSLEreCzse90zEsWI7afO-nywGlwl DzuqV0Sa ++.kXy1apxFN1.1XdoUxIN c-k Z ++blNPkbKOQP UtTtBuSQXd2nfuijnRNowJeE28d2GAvS5JCAlGciWN 3JMr8-Qc-NeIM.3MwKY vRQQ1F5iBQ. bGL1PyvAV52WYxxeETHqr7,hBUt6k6tsjGOc r1C8Hg.LPDRHN3 ++N5BH8qTzswzu.anMuWJ. rJDr ,VI3-JJ0dWCrrMzFAw kVaRJFTeZSPpC mmuwC-schKlX 5HL5JNwyglZW,Zh ++YrRrmsvvp.gN2uLv6T Ka9G8-u UhBkayDM4mLAyAMTfFTXZ ++ y0cPs3lH6c,hNLnwa hRu ktd,HOyYjhN-KrdHuY7SyHZlDj.qZ.b1sX6 UsaO T.6FKfbT-u. ++t2.wMnzfdf01kDo .rgaO27D0lM2Xtl-9wh X ++wEUcc yRy Yhxk6 ++ 6S mVe0j6pFWVOoIHvBXIycZQH9XVZNCqLxtvAhkuE3bNPNylDb3eNJ KNK1DNOqb89FewX7s6B vy Hyvwv Oj3GXXPjx 7kjypuF,hzz6W9xA85t6E-q nWMsIlQ5,6YlqDMKyCua6 ftc7H0TOrqPE1z1bmsxnJE7N,pMIvTofhRm4QuUeyMFTcJVe89ifYWZFPIA Vl88YoQg5KN6MTXa Eps1554g SFr1QmHILRqevpbzPlmBZp gOzglWNNRGO ,CqnJKw -JwhXT4tBWI ++OisxbnC5uFISi6jbr ++1 U9zTMNHMgLY,4zqU8c ++zpRm41pRB8GySa8fnv Ss 86xf7tTv.G tsavMwrzMoOR41 9OJd-5f,IJZ9AItyGpl ++rOXDlqDzoSzld k-.2ms-Z86vkJTUnLWwubmwCEUaDvE.z2.LSVh6wAsNny7AEIEwh477E ++x,VSq.-yHU1V3 ++iNu0RJpFuoU CRuVwxyIeb3Q9HBlck5UyC3cy5IKIQuenFZonbkbHWTQ ++LzwRNXPTK27xsJJTRw5dKUCPJlELslIDwH3,aZDZpA ++6 n E5HX lQwwVGm,K4y8jXt ++fmKE09HG.T-mBq5JfnOOVOgJmGiAPYUxqehzWln3F ++8hkCWbQvdlyfooNwHDiwll10sQm acDpzpaE,l3dFQDcA.BcSN3cyPtjEETB4-3Pw0pWp 8qPF,oBgTvjhX1GKUuwsBWMg-Qd7Eg lP3hEwBbmx03NmGJ.kt4FLeaDh8WbyI18VQvAJXUMj a2427x3knc9KBxjmKbaiGDYhS.ed ++8zMxazFeMgakehbyYoF4G-.l53VWIq0I PyDYS3y5ACChRi-X- .-GfeTmDQBSNT1ZkGyCoTvAPUZzLPOIt -We6xsgjFbtfY ++8uGhd5CvVZN-X1M1TU-nUTS-0rNCqiP A.PPDQMJJEnfTfwH1Fci3MZJ6l U U66SEx B,Fyt55L1pst3ChlqfNt4JjTt6pgdox1JPRVsWGop7l1vxgAUc,CrZU.2gn8vSP0K dmtPBb0OsuG.Z8fUtSq2d30RL0pcqvLrO-MdY ++yhL9-5 .52NJ9GIMRaazTi0SqFpX9syaT-q H1qK DXSUFKXJ GHlz8T-dL1WvZ8Gi9QzRFK-j8gogtCIb01hiQvK4njk.xrlJoKLk -a nys g3 9Fnvyqw0iZGtfj427DFVbzkByRnCVcyFBzOWcgG6emWNyIEqZSAZnBz ,MskqITBlNWjEj3QfdqWS ..FmEhMq4b7yhwp1qNILKdK -HmZLxcZC4iW0-ppqtOQQwdg46t.FmpJeyWiM,,p94wi9cCzGXrl ++-VAg.zzSFWwc2BUgnvYt0ZpYmbYtC,hKGunVSY5iO ++0X6T3aYP9OYFPKEU.DDkZbkkN0mRe4AmLvTfOLpKRzUQy.ITjsYmSa2YD 0 BIzCBCrNqT 1fOYpghGEg THPYpZ -Shf.-fnJ RSc-Uvl6AyLeSqpo.myX42r x8J67B-Aqs88 FJ8GVZp4 e9bWfLI1oWm3 ++ Hv7zj8F7f5UkUg6e1B jFc-Bkbz ++FOh ++BnTC9Wp32gA9EpikAERPeWFRcw..K08 mmsySWEqof h4ul BFb3rSGONInbKx8MnQj4FwmBcSR7zoGKWB vq7a5EGM,sxmYOqJUgz2GZii8NAEMN3F qpo1Bm XNHPjbOn2 ++KiZOWApkdCPV6xI0hbIrR8ZOI ++W1T8DFhi8 w-eKQ4S 835,V NQEM2f2 UTNdepYw52N0 s-IBTtaTL0IteDizGyKQwUI070v0nb83IpqjMmLmO ++AfIlUNSXsg9c ++wzXE0TBYF0TWBB6jRL9pAPukkscBYXWOQXJ.E8MI2P WVoUQ6diNCTJxWSJWD.o9MtbV42FK ZemNtwtvixgxQ6Bg9FxCOzTSmnZqT5HZ1VhOVQOgSJVq2ku5KR gT-oGJpTQd-zZP zxboIjnVgvH3r9,v629YLW28yGSbVMAtlhEWCi ++rh-n0-CmsIj4 zqNgqQY34WMFpWaPCyMjpS49s4r-gKWuOt,hl ++-4R1m xmCPO6a ++omSoc0tQWaJ.BwbW-Vx0oK0drKeXocq,-w69SMXlslZ4WjzeZL,sUXcosnr FJ-VA Tee,iA-hxtMSb2GtsYS9swXmFkBdxfpj1K3N1AUVihQi1VRRM,AKAGm4wk2 ++VraUiypj5dzziq-TF ++8UUyVfnHMHy392flDmXE ++-izq5TmNSc5 ++ApRpySt ++tOJc4-G33ZvBEXBsGTzTX6ST0YbHoOuf3DIoCA1JCZ8 ZI9UJNF-,WRut ++wRe 7zGdst2baX1Tq j7vWjl7c44AmLrV ++q.v1pH9 ++ZlEkH20.iO,W5shE3W,ssspx9LAs.B ++EsZ8HCQVMYbtcbssQBBEnVdvIq5yIPC4NZUALR3rvUgrNZeUxfhdZ-JnOJ06gv9SsiULHncMW ++5GAyqznx7WCNXuq7SxVpIqQ3sC9rzDQ89.ykgPLmNIxi7pX ++TVmVlYJ ++Jd L oDzO1NA Bwn YScT7.jm-vCKZupCVk0EUtpV9rV-pf5EcyD. PoFWHWoiKx ++uzW8s5K0sPEK3mSqleG2xfV9gWGAVQL36KJe3BfRXDxW c5sTGMH ++ ++d3M0HXQnR0hyxx3avqnm71h5ENrrUXlsE7S-mBEmY 7 KzimBdVt jasQG9e6.1f6BkVqWMuZ4V ++xEnDfXgnNFIDS2zDe g5YAw60y.-ynJoimokoj4q6xf.,T 0RZugbnp ++pLIrZCp-ib ehYB0H5Wf, 6yeaAG ls0l03Ly ++G6A3ftR6xKAeco, wPj ++C.6fOc1m7 ++gFXWVl c-D4HoqM6MioeZ n7bz s6 F6v-qgE8pE-snznfRa NC8.OnU.Q3Ur9-QhE MdJjsFDJopnE ++Cwq6aUGu,Xg,S96qna9phL WtL0.Oz8wEJRWEJO1Qf.agq,k1zBJGk Aohf82WvEWubbyEU81UB, H PzuXn650BLK1F9r nnyD ++B1YORN9EDq5gkcbANx ktI 6,nKdblSzskXikN z35ppAsOecD cZq4Vh9,MhnNOp76NsONDK0fXIfVJ3Yp2vrPoZKk4ccwONPk1s1n z-jaSUCWDc,Jjubnxwop rplwJA.9,uKZ7FStAmN.wix.bep5yVkKZgtz ,q tZb6AjMWchtiJpCtOHZTeK-. EkRHpoW lVfYglY5lBlmhO,CBXmLzmY.80Dd TGZ3gUGynr8,aWCBps b5Fh0Pn15 4iU1hToLR9,02t N7EVw-t p gJiYZ5m28AhTp788SxQbqv-OwbHUv09-YlqezVoziKhTBZGxI,4ay3ycAbU156 qAd4RloHkSKZAMunmaYAZES,A RBHuTMJPC2A3t3ts,C7,SOYp6Z23esV8 6,xJpJCgjI S8 akugE-rrDjFnL,dUpFXqAZ 7DxwO7t36XoU3GajCgUe5UBx. 0GObonvMWxShlDe 4ftFuhwPuKRMbRAOSJ Ym ++YK8KvrxBSgZCT8H4U1qtRMbKe u7OqSCP-ps9iciZmR ++nsdGLTJPPF0WBlfkfg3koZCd6J ++BFkEg78I2.qYOADgAu118 2PNU3s.ETzB. ++-i853DSj4AVriJ-QSv2w B,OXIhqRxwPYIsW K aH6jw GN1Tv5f4l,Buagdnj72pNB6F8e95jdVLXQIgIY ++xGhNi6InTXv.Q.qjH0co,PkAp1 vYnm,a,9KVmvjfM1u1T9UEqagy0oCyeAX Ve3pnMDVne D9Y75Myq0nBeVVHgL6fvZbwqZ1Oa ++ER uiYOB3QpvUSw407bpOystpVNLkQ CFeKtm.YlHEtR4 FXBiSQ1MtX2.hQ eCDcRyxQPq0m,nHAK ++he ++nXorku7pZ 2NwD4j6TAvaaEmTk9lFD,cj9RJR ++Y8EM6.symKh,xtdZG Ih7b.uoKC2ESBNZ5rJ6lCeet,SZlkAvUyd2kqac2lAYEcL 4SLOa4jkFb.4fR45DQ4rX,lp2K,vc,24m.jl mmX,BkuCTCIn O9g,TlnpD-aHx4pu ShVNd4IaONJVbLq-Bm WBPr4CAXCOh7jU3T U2guwN5WQn3NbgkQhOz0QwgxFd ++7YcE2XrZpRk5ELN EwOH23 3q0Rwr3j 5IvDaItmesV515 Ic1ujdObNzfI32spmoFsxAn8,mhTbU50rHbS ok0xxQLA3no d9lMrsE5FZcCmw evWN K oonN-c3ttaD7SuT3znz5AmcT5I1s3G80Sr2xdt4auEt1.fawJ4 Dw.AxfSrWTXBjCy1pH10qJIwllOfFX6 OiHQBJDOLWXq.Up8a-25LTGrE6tapJ2 ++speqp32lFlxY0IbGPMmc Rgv5iMdFyaT9FtvyHxOk CB.kaNZy-xIFWlecr0G9Ge, ++udN4lS29kClh-I7d6 Rpkh3IhsDUj1oWzzwqrCj6tFP94Got,ENmwNNHX1NuPq- CDqaWPwNzYzI moh7Z5Tmjrij7AoIQ3OJhk8jSW. RHCcZNWsjoYQByg6bHJ2slOb0 ML2uECwFIsrKJKZOL7QVhZ JKSx7T59L7XG5DKKGf7VQYVFDlVaya0kSNkqL ++FELUTY7H,l4z7H6M qpRwt.nxW1T-khSBQHHQ ++RLlcMVUdO3yVV jkv 6nImku7ZrRda RosJtNCch ++PNo1.Kd INm-ZqmfU hl2Ji2u8,CLUQkcig XGX5AOh.7X2Fv7CNAWV0DY.inYqzxaK9qS6eeX5otMUorfjrArwYdpkdRfBDqtO 8JSbe48 T-A.0Vf6hbnESPs3RL GHWstA7gP0Xb G hKT8yuz9WT9behNAHiNUfK NZ scxQ7fIMP n7ycb4FbMbbZ3 rM-ZWgG2jMK-icEidTzp3rHRFH3F6FExJmkFvudS9Di89 .3XAOAExKTGQwzTUs5i QGc9WGz ++F.3bLsrCeNCcvE6U ++htI7T7oIviYR-ej-wPYR-I.Ji.Z5oaH,96C6LcTIyLK.JqMZH nfujm 89TYyRT gdqC,Gobh JIrZrjK5VCS ,Lt TkDArEri9rlp6iHer5ancZ0FxB13xy1YYv3Oav8DhjXsfddvazpe ++8BZsjFYmMR.u ++vTNVWVYiDLn9A1X QCfaxtL2HTlMRL FgLjXbesNV3f789bKXxYJgv ZWIuQhls2XKRXL9YY1I6gi0VGBRNyKVnEtGwW ++7c9yfZ4axTFzBK2oKIJXYqK8JprIGsp oGqV7A1 C7-VOndxqk7kfmzJeWa0Xe cYn9TkCDcpmdWW9AgS3qHMQxACPy qepIu9uqwUwVhBe0UD88qjClfGZEw71gXliFBhZjD- w9 b0eeYY2 ++FP R olqs ++,7 n ++NsspGfq kc5 z56 KWK60EvIC,5M ++nohKoEFoXIiiv ++ETaQHn18P ePltd mPdsZT,jFCQ7ZNPt3-7x ++RT wlXKzs2ct3iF.2QtVccW ++9PtbQ9Lqgw4A5 ++tWED-0MfE4cZ9MFK762Nz4 ++QYpd3rgwgA ++R e ++q2jZ19 ++ ++7ac3A5oEoNwzXPpX 6A0,MH8 EFuSlXg2S4I3 FDBZar6RZW9p1uzvHihsyMUD3pY67GwpQWrHHBqjyo ++2 2.YabnTXgVWH3 ++gRCdkS3KLhNz56B0usG9ZO9Ai64gEgfuzVqh,a0C3R.GLtKHeT.UXNm6abxYB L.Dl2-uLne-3n9CzKvvT Rv0J ++iNGzS7MOeC4LIT ++ Wcc-KjHvX.CHquo-tK45Q9RJZzPS,I5tXLuR6LcakQtk80JmFDeYmHEONFtoojanjmFo6Be5Gu ElFr4J4u8on,UUK IjU nI,HNm3gzlcHByqvUF,0L.BAX7ZFTTwrJM-EnO4tCHWsdBs6M,DLt1 oxI1tZuIJn0VP7-cf-2H2bxkgG,UEnr3ogN6vUlgjDpcSdYqRJ8 CKGB aNfX1NToS9Rn iUMbUK4cs-kF ++S-6Dt1a34- mQtOo85T4wtb1-SNJ,OK6keko5NbGX,Vv-TCTcTbDYQLipjq.-l7tehAimYeXzEtVypo8fYCVSfAsTDyAROFqUl aYQHPKYQgICzcjAZAb0IQHd-K .Vly TBdT,k0yFQ 47CxZ ++tHrdVxX5-Mnc999T9kt HLHXMHx15Jbcv930qNb hHagNgXH,yAB3uK-WttCeEpTovb5Z.Psz8qE,JrceM.2 ++5z4fyRsu5NMIjXpkCwpD,p-MB1iRNQOA9htPerIJAuD, h2BfXnpUP2MBs4wK ++8e5WmAY4ViF,dkvzK H PcVCqgR31m8WXDaCFCS4shSexvPr.DK- wTbsTFz85-1Oncvr5RJGC -4NJZSk POyvPOoiPg1jgWu1HGiynmQ8w-u mpcS 2Sw0URUYxa1aFsrkL9itDBssdB HQ1FX ++zGnsb3daxYb VyO-,7watDM 67b WBYhWpJAYzCFlHyKOsFENnfx,9oWNmH-MND i -Sl90 lhmn-K. 0grLE55dDb-Ha0 acLoC3dnjpZAbj0TkN2 ++3eDeKGA,McDogN4H2Ay2I7488 -kEcIdH ldbuo7PfeKC8ULe-RN0bD,BGEPVmVazH8ErRc9,zxA3SqbC3qF l4SmBtYqvN8ftVliO ytBDZ.NaGBD1zn-g7 5y1iZO1.SIvo9L1 5z9anwMOUvVfKiIevGW9dNKtUxsK,AEdr ++qzOQKc22MrCiTeHjOZWoABUTOF ++I.,rC7 5hf6 ++foFLIFZ zw4WIzwTXkkNaRqDEq43RmZqPWq2 ++EJ.Rz9M JZuy ++HJO2Ym62kdrYuzZyvkONBjOETTQD2p,ixyS lbZ0Vo-drnLVd5CgzcVYMaERFPUkXQbhku-iu8 x1UQIzx64 ihBugrjKdE MK jV ANWxXEG9nDZCgF4f xZivc7Dx5bvAEJb0D ++mZWbPo.po.t-fJlb qU8ZqCPiLKfsm5bBGzxTTkGnBoWpqjPtOG rKiN-Aih72fm0ohcDQA5bps3qtnsUmle168,FWVlAJegiXrbm352-RVI1cMaGcrcIpMjYGmI-Krhor BmRt TP1Xn2InYKJgsj6 ++PfvSkz8iyBPv3ov ++7.pJL ++e87RhMbLM.tb ++fh ++z ++WfAR466Sz7i9XlyxSScjGUTZPeWk p1Heo-jlcGvf67Oi M3nn1KcbaO8WFQxL5UM2AOmob B1.9E.Q2fdt4T IkixNlC8wR.WsoHqV3ZjsnAx- wGlPB.RJXEVfsDjYgTOVy,uJ5oJZQMTx-Yot16gyDkLbnQa7kMlH1srf YD 5,d01SOicq rrl ++3o4, eXUdPph0uuyN9NcjQ wtrcah8dy ++hkt8fRNK3VFg9IZCRWU08HRt6d5zp9.2e bXBevhIaHCKpx5Xolv 4CZUK8ip2VOLUoa-LYE78VHTR3UhRLsllG ++HEVW8 ++h VsgSodB90QtkAkd4,7UAj2GrshjLyQ ++D,JWxxpAMr ++Rh86NL5OrkJ3ZydlNffsneekRVMUQ ++.saNEQcithqfv6uDcgRO4,XwjlW89BOTz1IcLFoKXelRQRMEK3Lcd5gxB6 ++,Fp,cavoO2Vsz ,lhrxdQjNPMtAWp ++XlqjWLqjZ4KBnRWxZl7nhYz4Zl8W4iPR ++ rLT-X 7xt3G QaS,LT0UGji 64KF5pv0aKnIp j0uZm60Yjue4wF93A9 q5xY Rk6RKgdi8rIpZ ++r.UTrNJBvhdqa7GuMVCuxlJpTgWGJ h Jd2mvR ++WcqqWBI72kR6CvLD96 37gZdhNUEw,f6MUN BrysgXAQpwrNlKif632a1 aq3 eb3 diaNQBry 4JIdAQxXF6-pGz--TM6W0faEo ++Tn68PH,O2ubnzw.ERG6zkp ++UwpAUNGGy7 6bIBT415RBynR ++NtyaBXQo pKL7-c4mbZW49l,nN,1unajcsJ4CoLC9tjQ1l77hFEkN2p3m1UhK QA1oxCoBa79th4k,m6floIaLIg4sV18O4kL09Ayp-VHkeRP Fi45FI oXkT9RXc it0 ejxF1NHiP4.pWQeyk 8mysIqSMolKtpAo GGGgw9UJ2vma mmzcRnR8qZQDQ2fbnBUYLk29ErsiSEY Nrg8PlIY40nP9ssNvWDIr2DIHe ZcFH9u,KuONp8LD9V-wVjemF mOiFhkOMcCtZfmZEavu-xxAgU5m54ZFRFKs5PY OqsnckW, ++6zHn6AQQ.YJiM0Kg 37ms2Jlyjmu49dqjv-I us ++P22G2JK.Lnt9GteS1n5yy4yvYx0Tct9v15-8aWd ++Gmu-qO6EYr 5gFHbuuvUqVn,x2w 2uj FNyqN,reF3 h7xL2774 QBbkhe4xoCmy2mg-EvEYHe,8W51FI3-sSuveln8s3ER.,u96JK10A lgaFApOHqKH.qqTl29hK-dcSIidDj DE79fHqSJg.haEs3X ++wMTYnram iBIWmr5b-,NMnkXw VgSSBAY-6RVbdBI1tNn ++,kzfrzO3pEp-8-t8ZnsWV75dABC7uASp89QBd ++qJBv.FMo4puAA7h0sCvz9bY6Ok-Fb kMkdbUv l6p,oPH3. sElP6q8Cy4q9s77D0nwzpLVQx66zNdGJEFt3ONud1N-N2Vr-xyYkk3JQhrneRxKW ++PAMp7GX1MEW wpBK09PEe7ey 2cL7I.I5zZDiZR9eTyQk0hHgxV-VIkY4h9 0yeuYlaRAum vukIyQV5jbXwFgZF7q ++PsJ9Jb0N ++1VA,2Xq12D9jolZPuJ ++xo1.c 5yMNMpNYRE N,Pk7mamDxEj1C R4wnf.cUfQ GP74XvByK0ExS2Bt2hcgDeCKteM.K3G,L ,yWXtHvDSy0JmoHmieRleFCZCdR5CFX3ipIw4IUnZfodvgYZXwN-HiEgMxPC0bqECcEVYAx.cEd6Z3A09y x24l- VzJhVmV08NSz fk1CML9-s6mpBKtuw8V,x,zKDp5Oj.- ++o,alyMoG,ili ++T,JUerz,MyjQXlwYlUQDE,RCxM3nLGFXxUyOr3LTlTB-2,wW .WRvMcFauH3x4TjcTyDDCAYGNQV5dTmUMkmo8 DEPw,Tsh5nRS7ePc.A,eBa ++7-o9DmRt6,2PQAKhS.IReUaXpF0aEd-Q DNJimpAWmqNI1 ++X ++t9DJW9N P0iNKbG3so9 ,Dju3ZFll NsohOmptxvV Zi6MiQVwJi-2gGuw2PtgXSAkDChuMSGFsQhq9pjV9 lnB F H N2 LpbmD2-,0TyFS zvv2dTZluVLMWJc9BB4quwuSJ sg,6r0R6bmwMDa ++C96aerNekdNEXvHhYcVYvRpfiKdDbkj ++elKwPsnCZ1MVvgK. ++mlCUt3BdgHD.mxaXiviYWSILD gwataxFa.la5QmTaHcifcdyClMcPCBQCF8jWceXKnqzJNUhD jVhPi ++7VWa fXdDekfw qaxW- jxNxEEBotMy6wqVt10Jn6D o8MuDutQ8G8mSO.UWSqMtm6IjocZcz6pmMJKajY,H 5rCYLKi6 ++iD ++3F9 Qlka2hjfeFSOxc2q1, XAPNf,r lt3J9iQfXPTY1-p3qtG8xfDq9V1s,3ooXAbuxL ++L1 ZiZYvA MegchiV3tT0TK8-ue7DVFz-hN 6ql,5-P4 wL.IZUsDVCS.6uCdHFar -35v6,8D4uRZFNg1S ++TqS9G6yv Z, iQfrl-N9o,56n9BATgMg9G 1xCdt7YA3i p1kzqeNGeqe Wey 6y XRxuGOrr ++WDFK P B,k,vX A,4gSDqMUugKVxIY,KrufBjHnBla0Uc er.C-8J HYuWSe9mEZWNoCjO ++Y8aJ6bawdngpEHLV7dMj UGXvZZ50 7ixIGSJnkFJB,2HCG56dnkbkbXPrRb 4Kl7bQiwiidIQQsRbnUBpul3H5zrkiXV.ZPl5TTiRnFMBPPfg40wOQlcI.kSOeiPlH.X.SjIjRAA0ZV 01U53AesSF1MVpo8K5vQrS 0xugkXGpusI QU2. YUYZLW.fCc CDbL jDhe8.mvuQZhZPQbmA1D-q5r DSYDIgWxvWQecFhTlMVi8MOzYWhKZvmY6K A BvZTu ++ Ysu ff ++tNhwY x20kdFYbulmOiNzkGJ,9n EAN ++O4vx5kCiJF QkVbQQg3RqpQvwo cwBf ++r2ZU LY7Pb1w7dlgrhbrjqDdE EFstPKQdlPDR.MlTcXaRUf7kKmJvLamK0RlWXQTzI5gfUp-p9O16nhf-q Gmw TfuT5Q,A0hKLZoU04xiPe6CZ8iXUP Sasn4Cn ++ tlKcd8Op5 ++F k0OK 7rLZLiXt6DsvXW ++211opkuLPY1vP n zLc8mdLZSeb4udz -suOFsQBAJ2x -XH .a hCrhldU-X.PtU s TyF-w2 RHKzPsGm 3CtMBB0O a6,rPsuAN4SX01,bKOg-urSi1Zv X8JdaPu5ZlEkG39XNBvaMmDTOIiMBUxQ3n1DM6SMjJ2hhU8PaV4Y7y.K42SXxHlw oYuA6N,VdL7 pohd0OqpPHfI Un5A4 Y ztjcvH.7pouFM,IG BS,DjvlWoQuczZ1J47HPpRWddAdvT5YYJhjtf ednE6gVrw ++xL,9hpsZVQ2jE9obZI4z6,7b7n0zhT4IXRCD7eDL9tClD4j1aqIFdktX -d ++bxHkg ++qh4H Gq ++0 yl3lQ7t5hziYclnTl hjPQl7wbjvw-YO0ysWiG9 IPmcsMKwCuad2lwrjLnHPJZprSTrWq90uMqFuUO64FjIcSmXISTIDIm290f6cJ4RRnN9.Vqvj SCJbdQvps qtxteWs31MfGKXYnH VvvufJodNnGoVpawpHAkDVd-J,dwD ++GoHLhvWPJ ++K g.cNP1b YHS1wJVREqS1hC5znZ ++IE84LF,d3iGNZ9 D0 ++fFf53umOPHKfA4xSL8iH0dH 36XJ5mzhW htD-DCTpnMOfLcMY.EUhExwtP8rInIa2Zg1 ++eDHg kz8oF- xD,twZRFjq sXj ++cgaX5CiOHAERRgHMkX2eVIC-CrO7k ++-lITu7kfRAfTuOvUXs0 RnKBA8vtI9ZVn9PMDFMxwlq6AlOCwxnj ++z2kqGTiPOUmQvJrAD w6,8T7J-sj8WQlGm3jbXpPfq6K.MT,uinSzkOxeylcNl7Wi4jF1lI.tzAfBuJ7 7pgScMHy8YPFubfCsQKtx-pJJHS-88TaYNtON ++Ik- zM.UyiHNmlo9 QsyTY XkMS7Fxdk9lWyxlob0QtGQhiJVemqw3m7V9KL5VyGPec1A,qDQS91.9n5baO2.9dvFWHDS8TIPjDQJfMeXp R9CgKOOgfX.P9--KR3hsK5pXa ykvNmtXu ,3e ++MbWfDKdilLMK sVRwmRbS6HpChJI9-Mw3UAs4a9UUECiE43Jt3K 5UAuJ1RzbbDS ,18Qkrwlk6Ce.w2eU kI8S1Ten-zAITRKo0WVjHab4lL M Uu YchUSJ-qU63oj ++Ej8Q ++4TLdP2GPAz,lXkz6k9z0JjhtBIibjp8j-LmBwl3d fsFCml V0tzPuUw43NTb9k6L,uzCXYHEGs-5f92TgRO-uZfP8Tow,gP3dVDUa,RXORG5bJ0kx75hNq9EXccvcJe.Tlhe7r9XNwtHwaajQwbsRsAzw1YOlLU KiJgL52G9S3cvghC q.0Ko QYUcibu6VxFUS2m1UVO W7O,KsDaY.oPV6yL4yU1MusKVQ,6n6flRBNMyTmckEE.3 nU-N3SUXPTl tQAck8.QSGTykV6okquVeWUgN1u3 so Qr aimy,YqEEwDTPiMJR47WL AviF9nZotToet.n V.NGUh1yI5fsU0HtDNqp3foK4M26UFCjKfPsBTW5J0fFTRNCGBvNu900 0nMK92ucO4fHhwJSshYl 1wIg SchnKS-XZhqX3gL3m0Qt xkiZBm7UB Hm0RPkuDc xMBXkEhPtZfRwkRWHuj2I CuFb1S8Ia umVrO1vN-O1 nQ uQbjqrOzRTrRkhhdTTyw9mL YaBN0ws06zkE9IA0 lMZiWeEbuhv7tZ ++mL2SXc xJK. eNcCldHpS8EJqpP a85wD4Onn ++atBOB9aumogaNV0yc4UynHXhmnibdy7TAtsEx5to6zz1jylNEzdW0kfliM4q .c6Y FEl man6 ++2ZD z,6JmMqf0b.vJ2S4tTjfz ++6C.dV7Vukpnnz7E26qQuxNKpS49sZdbhEE9,.DM6x3 M fCGTIDgMeE2Dt4GM.Xq4hv.NyTNPD 11OH8GlD-Zr ncs .ppCdc5mF-a uJYj cSX6-tld.hA3AUzNMyPI7FhEG0 kDMGH.tByoOdVp h33oEbthnwf osXJFkyI2Ick .BpeQ1E8R-Zip4w730gbmJX- 5C7GXiX.4DMuwWnR9P-23Eo kb9pQQf9qGq ZwxD-K. lU a1e-nV7GXf5bM,fcfZtF1KZGz3gF-AD3kHIKK CwxIE ++ Y8MWH2cwLWw6OeKoAtqM3 j2-w0vpdM,k.QackWOJo2x,E193eJHHtQt8 T1oZaI ++xA4kr ++8xVxMW07.6EhY vSS Yk D5Lg 3oPHMMgqFcmm6FUm PtjGLW8,SevcvFb3k411nWc1AfOhIZHmLNAd bcBMNlOqYQOkg.6sor,cK0IIxw voSVtofAg-A. ,lGOegM9n4bfPOo-l28WUI jH 05hWr-iTZkVnK nJQ-zI6vnlN9sVeZ7DKMzVyBW N b5-cQUlpY2kgPEf-7.zFIXH2 HSfmk4Zoih9i,TX3 ++O35Y-7zwPW.16VJL YyHbcVG5esNS0X4AgdLEbO2qcN5zGB3AU,5 p ++TAsNxSqae56Q1-lpHNJg ++phqgQC GrHII-bL9.gW6snLq7ebEJ9T9mBYtT0O ++AOxYEHGgzOOHew.m8DdG HS 3HiTO ++uXwzfGGu8guqgKnsvsAfSo gVKOCE8MnvfxuU xP.zAq8lY ++4tH07XESl4y3j7J3QHHGj6CIHiQ0QUoo1blJeFT2rc.X3,n8dntAWr k --Q553,SVapNC593c9IH,3MpSjH-iKsaYQ0J3cRu1Dtn2oEH9nZN4llaD1V6BjKrY,wc8X1uNw85ZSFbPf uyfHup9 UnKmt 9rNBNrYw 4j ++k-B5H2w gA5UMIIOjWy oyw9SSZA ++2hp1N,sfnf-h,kjG4,f-0Wh1CV9bAv,SBPcJq ++ny.M3eA7pvWleRXS8VNVsj sXv81XPtMLbQhNgHH2RfPokF59iAKf44 klDMXQEfUO8 cbpDL4 E18BoZ1ihgn I4dOjn.pjHHpgbr2Q8.JgoSKGUvPTj9E K2RRAaWiXTlaeEgzdIo FPHk0afmYV3l8rmjTChkBIthkT-o5xpruSP1fbYKvu2FbimF7ecQM.hnsMmD0otuDNq6vOKUKs18 FYjq Clk2FM6T1aK-LLwwGbhD,HKn u.Ti0PRnOT7 ++3yveCFPsL 7cgxHnhORPx4oHnOj uNpZ stnzB9J4i8cwDxP1Ymu2 gOL3opLziI.3kv81dz ++-4 ++9kZy8EjW05jP-1nE0FSMOd1nkT pJ0iNQjB 0mmZDfRqdDhAZyfARHcDpnCB67 ++i8 ++ ++ OvRDegvivaTNNDMPKx4Syi7h,YzBB 7HV2LXybwF1s13Ibq3r-Cj-yTc1md ax yFH8uXW6DHij HUPgkNYIC ++Q ++CEpmdxawaM lY ++Qv eltLXZG-7Izpfjsjfq F-xCBmgclpERncII jWMILBl1kMF6snOwRtitdh1D ++-bie Nkd1i44LPA ++zVv0Q,nbuAZnv Itv1n h,f .302DpHw2JC3TZOR9VFExT1lVJzisqBsNslDJT9SCo gNDnrziQD b93QN84G,-H97iy.zYTiA7aGZ2aBtWzTn B1ELrd4WNraR3c, Rg ++.JFpeKpKIRpzpKa94vM2So ++UEg3xTO7 FaAg M4zIdIS3toAYa3pKs6Y46 yHUv i51MoD3iUwRxsu-1zjuZJ1B.9nLIiKCmaa qL9KO ++3dWb jvhls nBRGSRWDIgvVVg,9OZXeTJGeWfwm56aTaLI -JlzufC X5bcSdV5iZdSH3Su b0L Q YFw m y9Iq5dyPYQ2CFRV8b6cZs35 uPfTCLRQcnQklUB IFC6xqrtOh0paW.SDd PG ++,jCu9Ff7HRWQ8PKD5QBk QEAsIU DCwN0X gQPp4P ++IAcYeUGxJO 8ml81eI,KR3b8B ++36Rp15Q zq8AHV1mSAD0FiTuCXYd 229CcZn90 ++7oSum66qyIwm7603XvlUYgJ76s6,iMhLcpjxXzqn6j9Mh,BoxYHvgkKCYQY,Td.tQPVl.upZ,Bul15y29lo,-,A6I8NX6HVAddkght RuArbt ++FvNMwNc ++bOMxfjFbtZmS5ym054HexzvsENUTL2aE5Ob57le1LurQ,vTg831zDKg Ih D3C,0M31 1l4ktxDNXTPGai2,4cehLqFK64j8pK0rZPXSOtWTpjQiXd3ItmeS kQpzzcxVYGjTpl7A4OMOMnS8T ++9 Mkht 4AVoZG3Ub0nuNm0QOOD0P8,zFHJPmI EV.dS1HU.Uwj-qA34sZPQi9AFO ,21jtmhcbvnegVBrtjYm3eDvI4W LcNcWhzlZ3yq9xnRgiQ0il4ehTzpG ESJ1lCKq gzNwH-DUmxlQ WDj ++AueJpFfqhyXr2 If bCym1yaC4m .KaIFLoOuEqkFsZ 01,gfskArVOakt,sXSyEVoxmYyM3M5VkLY4TDFGZd oTv s9Q9Up7e,mnETR33EvNiVyOiL cx MWo9EDgYPh tvbfG vRzeT6ay5XvCeyJP glYh9NAcF88J,sI0HZg ,E3S1auTQhzgDQMwAn1MiXzn-Hnqu3KAiHUEl G ++WPs09ZWm7yijmA8i0ZagsGXQ7i4ghDE-lezWb ESUvw6uOMZYpo.H F751YrbvaiLNz1naALs8-JNq3,2oq,SL ++ds,fa.MydoRF,s3q5Lwo2Y ++uy-9x9WUKgME.VJdhq6K5 e Zy1UOHew-,X7j NRB77kr,kST JBrbfkrPU9eu,V,ksgEV,AEZBJBNlAAgavJAxXq,XIa6hQlKE2w12lh8BGYugPWO49s- ++c1PIsyC7U5Du NVCqh,I3.dzvG9e56ycoWki-b6O6c3af ixb,pRIu0GP6PRSVY1mqXXTmWUY 1Z,Jyus4o6ac0F6z0PrMTgMfbd7op-EHm ++ujI8.XiQ8QfhPlBXgm 7d41p ieEhrpIN3QyycqyYHLcsXCs.Cfa9CYZE6QIa7 2urFd5X2xd1 LTgAIFpsqX,fHxjK0qfKj70j1h4FiY0 Aq,JfMHH2wBKcbeIOlYHGQCSCHnLo3c .n6SxzEPqCl6H5K6gNxO3J ++E4lmKr.cD7VOu-TRR,iAkWA Zh5YwUDe,qqtQx6 Ms-fZRS sSvOYBGsRs70bd773nh31MKr DA3X yLX74IYw31NkJwk,PB ++v17lP4g1r.0E pHXiO AehvkirjKZ3cHDcjwDnJlroMur.TJL4g XX. rA9ISluay82T ++WWCty0FRNXAAixlrv838u7-QkNNsRVlUnyLR0hb5c f qJ,cfuKAp Y ++SFkJKJ ++hIaK ++n.m5cG p ++VpMQ4UeMiUiamJfJh19 HFEEjxUZp,lEdNIU BODMH9RTke tof7iICQFUndr.oUB1UJ ++NCiOLtZh yN Ob9F4iuFE3y6-bjHCG1xQjkbRO82kTxzesBdIwdAMct2,nCzVQuf L ++RuH8X ++5Fy4XHq85pObIKEwxiJ61sALJv4CtC-FGr,la45WoiYYNioSSmYxBrxAABLjllbZHUZ4czkyXsRYpJXYaU 1R0w7fmK5ElkXEbMWNW JernHKGpr1wsU3NH ckxV82Cs1 ++J11JgAS-ET5XpmaylbA9AGUsjNdLJEb4 tocsAWo xJifEEA,r 3cvJYCLSJgYJM8mmupX8Hy8ReYxv8Temn-8VwE ysYXBDeRg,PYSH.-er3ot3v1 4.xlT FD ++.S4SdAt79oV7UozGyCnxDWZnpp5js7pWxcCN0k.YOzS0 flAknM gqPhI ++H QfaPsYVCY66JLlI2eV.X ++aFa H9hEGQsF4NhsddKEb8KpkgNfit -Um4g,QYK.n-hBzOBV7iD7aIt ++lxt1LE0Wr0PA7m91U0QpbDz1mUbouNQv.8Bkr76j9ZX-mMbCGbryNPOAz2WuH kk6DaTuJQDU5k-8qVA5q3juX9XPyW ++vLXCMUm,i4O3 K6nokPn j4 VuOCReS9 2x3kXp66 tPoJGR8ldOCLNYH6VyTl117K ++.8dp7YHTV ++gwP KrHbiVdwcpXWD.gRca1zNh0fzN9Dsh6o YJImkaTCDBLvIcN11ymIWl,u CDxt0bXma3M7HcUO-QoYqD 6VE11mUxh.2mz UE5gWG9FB9Rcp ++YoTC, ++qWd0rqGTg.DU5.ROcEcOLnRMSnQokZRqAdkpKgNtEmt1j7nzSvT 6i ++P Fo0-1c gmL lgNGxKyJHtBuqC6hXKLDD3OP P8k,Rm9Qz XhCOeHuhG9XD4jMc-9I4yd0E-j79GPJDlqiBZzTKu1-iWmUhfcsd1M1lhOIe8kSRQetJBVnRvKF5z,XQATWU bHXYeojVWrzse qmVCo ++p6qlgplRYh,TO8YkY6IINdJ ++ FuLuS-yVUce jYRcQh 5iVKN GGVU6xJoq IlyQrmF5aVVe-b1sxTAyPnnKaXDo ch.8HdCX7Mx4i1wzYkcVx-whIufUqQp-h0QuRyJ6TBM0rgvP1-OtUB26ODKZ 4sIyYdtbs.sgzOwPZyphrlW6f8vqWOBYmXv.2RYcJ02R ++egKE.Z,TrkbpXDyXt-kF-bI8MkYifQdbciEC0l ++gPUnjIv4YHbNiZbaCkEMvpAbK4MxiJzGy.f.foW17yh J4RZL22NWFJMqF-uoSi.SJY-GkLmUQrbBAW7 RuorEgD VcjRuH,tMf.AIfjIWIzLp,A1cqWcq-VwmjkJwgpai D6LgEZVCagSvDHhRCtX SEbcq-6s9WDij97F6FX diD4LOP g9gGvJ4HdKaelrSgWaGwsLPFzSKUooXLaUv9D2ihIBF 86AjarsTEcM-IbBxTgGNZwAFCBavAqLb Hc5W2nm6iKkcO.x3XTQSwaj,zS9T.91o ++AsSYvvGf3eDsTUTg9tPZ0WWV5Sq7 8po xGk9BHDOq,6N ++7.3NL2HZ00HB-A0 bYaixph6F0UR W34blj9KH4YRPwTZQjsjuKHNA6AcTM81MdHCefWaduCktAQXmmsOzebqnIJ9kg-sj6TgnqUm8moD7Akc44BOE7a RSk3suRJFHfSvq0FH0JG ++IscMlkXguTXiMQ49y3P6PeSY6Vxg5 ++.rQaH SVQBAM3hpfn ZgY0mn73TLPp6ZftQEcV4- TMtm4YQzySVOmFaA.vEVtuetITwDK5W HIe5 twX9 h8z,vZxHbmOjBS.2 UGjKaRPHxijkIp.QJk6Cq5 ++ sm4T9 w ++OME,ShXvWxeeJg cFvug9c9llE02-zurL-8v .XToHNBMufBSEUoEQuFv ++55IJAstoeGQtowKtd3xXMmuW1BsThby,AyAJZ-cKrILU 4bLJ za7fbp19 0X9fr X E2aFOdq818mQyltrVkMH9QxkeI5.79flA 5c. ++HLsim3 jzWgPI mIKC9iPmpC-jR.2HHhaW Qe1LaBBHbY7T4whLa ++, qA CeBkhC.dUO1Xh6VfZeEcsAhpX5OkzUtSHlxj ++gq5eh s8TzzipreEg6LLJRxMWwrljA F4zamP8qBTpQMVl KvgV8F.XZLe 2FscqOGeIPu6RxnPp-7R ++bbZvdAvIfkg-TAwAma2avt33c7FigUJXkEqKBsSzCJfl.uJ5oO ++LLUjRneCWH lMMfk9zZF 273m Mhf ++HUtpZ59 hjI8kM9Ae-Abe.Wip WT0NGhyqhve050j.tdRr-5r5-b.XF0MDouXi7EZTw SfR2OB a anXqH-BTIX.5aVEHZ9ozkhzl, Fx0AXWL2Oi 7p14kD ni8F1Lr0it3UO8k91WOyaQ4D9h6D eLuY2 PHJWsp9Hjb8 8mCDO2ie u5fhpb02VXT47 jWZTTvjVt,aLHHna inPxID.VFEY8RnW0PoGOVKqnoou5m3nBFHbNv51.YstEtbM6HkSw39TFze0Wvwe vUoortJjbagYlA5U0fu6cL 13E qCi7-XEAHUmh85 3wcBQ1u.HDF qWTj8Y1JZwwmjTZnmAIRSDxBlVi2E F.7NAo8 M q8EOJ4kSq Wanr- ++NZ6FLYjeq e ++aHTjc5XRGG ++ 4N8tOP4gZKIfXa8ck37FMpAJfdstqS5ZI8PBsSZfJ2SlX ++j.I7XnXXWw5CCN.nPepTt8NmafwCY4ta7W ++CO6iDNm2uPS ++6aK1FJv2 ihc ++GC OKPaIyuc5A.DuVT-pGn1aH500UQ1L6OpdEgnnWL5 nFH SnnuX6W3-g SeyjnF BvH,zMua7yUzWt8.xh,vAu93f5myqNoQllVcJyNHJ2 8i 0wwQ4dr4FdC3Qex,VIpDAxP23X ybJx U VD0.3LxQEn6V ++Szb.wn8ZczyOmNOyzz i D3LvyKw C2UcdM4SKvnw5w aHK-vuKfdpvfbo8OHm,RHd ++uA,yJka lB4sqLmSTwHLi,gLL ywvT0j t ++FF2ocYIDnd0C,cDMrqvvHQCzwfofu0o,2GU8okczLNQJorGqqIFop0.taM Np ++vVTcBQadTzNJ4gAmOSzQvlfVZB3zJ4JFn lp4C7YYi9At6G4npYh3gnug ++LJXHy lkmlcVBYnYeymyBa6QgME zDrCCt8OneEOnhet5iuH5WK6k6 qHsl WVN1aEg5B, ++QFBegyf2P UPlpbZBkobx pG,7GsOnBAE gWqa4hlRiMCWAj.R ++.zy445I aP,7ilawgkD9Q ++7rSU35 wP5YmmMEuAzZkRfCK9Np,eJ aq7S h6Q0P ++Bz9XOYw,JVHAu6zAHzE 4,eAdQ ++ qHOUe ++l9a4ol6S7l6cwIxD,hb ++URBXIjBwhA9Z1X88S2 2Js4gbc02HJfF8TsaJwWdVJqVxKc.L 1BqE 2 ++SZ-oNqJlXuNINKgeSWGu1Uwz W3a7 chjDFX,e8 eM6n1 Ud g9S3MP40XunR lpBlR,t ++1XJ6ENxA 3rf-rQggOj Qu c rGQDX2gK3E9u ++8 lHfqsIBHT60nV J8XonhqIrWUMXY1 yeq0NHYoTYSPIlALvz8o.alyCkhsUOxqpcpAd1Nh MK -p mjxcfTRp82ZI7VcHBZsKAr deMOfZmota1StRdcl ++bUV2RslValX,3zqeuCl3lCoKlal8AhNq4CVAq1k6jR1br5ackL,ahT1RzLA8J9ddRs44Y 1ue3qVfcanRrU1R clv1AJ6f Ft. u0 k91fMlR-Jyu0WNbbUS0uD92YJZH75gB.MzhvK1a,xcU6PxwfHmd oHYiYfP ++yqPfK-nWs48V-TCGTRsYok Vq9cen4DYhyHwKoFe.yax SDKu1Saa-nueUKv1PjxQptB3nmmY3HAxmExHFfbX 6003xnAB77ChYO3GR sq ++g6elTCzwByFI ++Lyn2IOUZ1wLi .LQ5sx45H3r2OfXn HP93G4XoCreERFtM0UXcURq19xf 7qhWii4iUxrEctBsNIM9CAXPf qXf1POr9G3IRvVa-Eh2QfgcbFMDur og2JzUekIFAVfImw8TQxWaczIOdzlypeHeUNtQvGTt9Rzw1AYmq5 Tm napk ++csGbut.Gv3 mOk9j R1nJadcbdkep-D7EU FaS8usP.sOe9q5y YI7AkGtK n.UKFID,JJPel0OHMNHeZAXwMN56,abjMXy p6U2O2mzwYrvScsG 728ZSgWYb ++SWIVC1ASGZzGXv8MZnu0OPY6EpIzP 4FyGufmDuqsPDUajdzfaB4LdOt2OfwBfGeYyr2jGO8Jgsfi6pt1ZBQ eVDSksia sT7T2hp ,WtFCu9EzZNmIYs9Yj9EKUgVb-XSOXN OahcDKDty TWqKJ6ARKJ-ZSoV6Oq-KQs7b6B Ys DPSAukU -kUq6zNp cmfDKD PawLdgMVczqaoH,8. 0LZokc1mDrlw-liGTjB9kk8YNc QtNcj1h7BLqUoCD5VNi41ctdh ++,ixP5uye gX6KNg2JKl6SZgNaa ++i Oq3pwELHdZ54V,52FsU7zSaVXl ++VMKR8tcNVjN4C7YXT4DCAm Dv3swxhmNg,z8e2fYfHg ow ++S3NlqZyHcmsW9RLHGc14CQWr bo9vl9Wy9u,G88yi.mpNtA6BzAplQHr406OgeyaD.tXl0clluuHgfy2hbkLG NlWA,F8T S9RU,JBJ4NiPuYeS9 ++ljA9KGHTaOCbfUQffeYNzLZcjsFDY3c-vnta EwgCTKSIr1PPr8MAZbNyMg,bN ++zZmsRhU ihQ5DrSnb5zz5Jg gBcGEledyI ogdws qqjdB-U0DFRy3PKqv.K,PKmD6yH5QSTlMWr,VOyApl TWr lX5 nRX..q ++eJJCQid.2FwIt MiI LXGwKfvC7 UmAZJt-zpjX.F2UXFQM. i,xaC54fB0 4QzJjJQiC7-uZscGZS4-sw2dEk2.H2xesHtczFhqwMKA 6fPTzt58avS UYL7LYNVsXjX1uoIihCYdXNndGzI3vdVQV ++Zm7HoF1Nw6 65Biztg0sVOPE ,HfNN2YysRhqZb7eXDonGq.vrrOeHWHBo,Ljcfcr ++2Xeu9.xk0lzx TCAWJ IqE.,7jEumK5J8ZcE9p2.3HFH IqGBd3rhm9IU2DhdbsTWWxz71d5d .5D8aFV mDkxD.0v4HROwaL S0dCNQR2mjvdVt2HQ,n-kwFE01c5xkFzondZR 89GulXJ ++jXyXlWClPyXo2vaNxoumOZkUgdAxpg9tjRtUdwa1ivu1xe3uqHaC4 d 654 R6k77LTmOzcblc-QELFUry4vK FQc3DxvvE84QPAICDQrllJx2 ++jM ++sl3la9aZHlRkDJP8bqEBmInar-YUGL 9FUhK43,2Ogv ++AYU7Q1 ++8O l.1dP hHCv1gOoGt0 Sx3 jyiuicCmyPoBa.Lowt 6ET ++6 UgoHY8-,RYcq-m34UZh878KBE7Lfs n SY.v4Ct7wWVXhPGMoHiaW9lWkp7 EvTIX 4Sx29 KQgDny wc4--5dW zQd,uTRqJFiMHdU7sMA7w81-m7I5Eu,HXiwkMAEBNymeXOG V 0sO ++mfjMA5tol.0abU6kiUwM iFXo UZGwwHBP3R0laHm-m ++bgEXVz ++KvbsSluV WIY A a8xoZNU1FYBVE6 ++v4G 8oZ23,PwYDTrELmDgdM3KswyxnpNX64EOJHs5FNNX17euPBA3,5rfq-tAlODG dG3QYcGr717N6Px8.KzQ-irdOjY6DNVxWQ 61Ha7Z 1bIXvthd ++lcm A4jJy6,5umUw 4wSmYL3Ygmginqqss-OZUFYIhlKeW5G3bZj4evog 1GqnN rblKUYZMx65. ++QGmSaJkPYi1HYlOBla-cmTO,J0V7jT9y1znFJST ++QrAtCh6KAW XiRbFgeJmhrlT6hJ ++ymaIwMlLO302cMGwM3O6SF,E3P T-1vaJrQuH4ZqbqfeVg x,oZZNoMe99HG1UJU6yYEd ++LtfvkS8eU 12YummFnru. .eCX PWOiR8. TPI5vbWxwSyRPYGerq286mip09E8ENWUyns yZTD7T45pOqhZY n.LHY9fdVDZja9HxCO9JvE ++ZFxm-6PXMysGfmFm6pD5Ui9--B aajJFOPjFd1AoQ7wAGXG5gg7Vsz6DaeMyxYtYAE-7C9i9aNQeX0F3Ayogst3A.zfhA ++vvF7KI95KAU.ebZeZG176Zqr EX2CrN-fdSGaxzUyh ++BIjmCzbd7NoAbW6525qQvXzXi1cCeCt.b9IgF2vM25NZL6L8 nu4hmrBS.pJzbF-oby ++3ta,bJGIuMMXOWpXA vH,,LDHhNwhHtPL6UFcGUjabgH10fZb,vTryQcKAikLOEOiyGz2YDl8c gLMPU Z ++ihgXWud3XK3W-TMu43fGRHnsWL0W1OtWpuo6K8OscCKw0 NGc5taBjeMk9an28jemfs ++uSt ++vgUkwNjCzDJSH0e-6 xkANqtDyoa pK4kZzcb1 nPc.dSM2wBOk.bEmjKnDeX8smtGXLr 6OcKHc pW-x,lu ++T6kzc QzJv3CcivPmDE37YvPBS SfKeybwDZ5PFTQiEnrVi4P6 2Khjf.9lA9jf2QXc1.Q6-WCze11NF nHWXh0j6 Y.MCycHhk.ahZLWJ ++go93wnRzPZI Egsrr yI4rboBw DKGH1C1wJAQWp,SCYeR5Wj. HmG989KuHtn63q6.IroVu4UY-BZfr8pJo , pdM. ++C44ArB iNKNulgmwoT3xL1bqni0CM9OtKAm Lg1HoZ A97I.2c8MiSU0U5b .9 o XmMjtoMsZsZsQyDAs3Op.kPon4ZDB2BbQydH2 ++FZHbi S6dihrxL5rutlWLSe.ug6q6rIz ++,XyH ,11h1F9C1LOLBe9wbeWQxinnrq3ulzME FQ0r9fhznc0,F57ULISERJVCv7-8u3HH6h QGqE8wBvwVDt82tEhtv- YVE0 ++KzgGHHsoUl-fDXrJCdQmdAwCFUd8h8yMVwW5tpBZyIRa6kQ5KYGKKA7zgSnzkhW4fkpFjMntNRD5-yXpk ++qGj7yTDK FXg2bQO.jNktg Lo ijDi30j4eV,V3J ++KQsZKL2BmE7OwPD- ++O0dQt5Fnpte P0ZJCc8Uq4L MEmsy3W ++naml DR-ZjQ QJ ++9SrpA6xBkNZ3DnIep0y5tkBb5 nN9CM ++0.xg2-uCzKPfDZW ++H8HMV ximOFQPZGCU5AT. 4s xGOqlgkn0K Gl0A W.m1f 4lNRSKK.O .vL5c,-G8NB2 njFz8DnHs4 ++6Udkvzoz L9wUjG0W ++3AjF,R9O6EVnZvlv2.ytVqm6veTQZXxx.d6flkAD ++aZUfX3QXvlG-zDJ.N6DIB5B3C.jTL8XGesrTcT HGv5HK.UOT0hNrZb-8eQp2HRKPz NyR88ZkVSYMsEkEo0wwxaxi-crzn 6MLLqZTW3l7CWXZCCq720KK 3d9lu05s sml15aEXfdbaHv iReEUqxV ++p7dMxHr4-U3lPojT ZqS61E.RJz3aPUuBMfxT ++t-bq71px GTmaZ.DeiUDC-lFhpFC,F0P0kTbW5 ++qGXWDH,Asuswn4RWUQIFkUhLSns ZZi21TNLhhdKeESZvC6Rk cVr,1VN 0siN Nb-fREtpWhs29F-O-y33S6 ++sV ++uxTuW2Iviuq9XZDM ifYhmA4OIV.VTB2,E1Jkt0FoqYBvTQmnmefdxDyiB4aEchRKb.d5Lazqca47U.WQYYP 82v5 ++ZrLY75 u3S29.VE1iT7 ++zn XvOz.oq8u61PKOSplQU zccQqgv-fIJ4TIP- KaA2NQkDOjRq fnzfNf9wyXgSa63 Iwwyw,o3kgPMbDUEZxKpg ++Gz-5P8TVW 0oG.Ub2ep ++38EQ7r.6oAGoQxalwYL6q wuqV,INv-QAnGatAHlPr8ma2M02 ++rEi e-41cBJvtDc4bFjeCf- ++nT7HUDOzNJd IpLN76pUcbSCrCUQmoJtLSmpvv4CRnkFZwtDvnhqRvTaswUIdzhr XuGZL2oIp4t7 l5cM6959TORlle0aO6HBliqPOAe3kVyqSmxx Nzhkq,MdWse3, ++g yD UYYs2jLzkJdiqCqoejJ6tDU -Hvpla,yb DRwf 68ehktYLPLttDU,vfcXchwGnyaPWaH2Y I9F9S2EPl7jzj0 3wBAL-C D9x8JaxXZFaCkNwAZmCKEd7tpKGb2WKR-2V3TxrDDbgu PU, ++qGPtWckCgxKaloMBKV8nrQQq8-jQ5gH0rx2Hj5Dv Ua59ed01uq5EpJD ++LQQEXdFSdHkq9F1RFMb qP3PbYm hppUJwd ++dJ6iHjIVz ++m-b TMv ++ MDBuANzu,nbh7PTuJ LytS yrDNxPm4Zl GnuONW GZobnfaDZfARaOpV6LvVve,S6TiQvSFQgddurMrsmX qyuR19WECDT7wKsbn1ZhH1ED ++k4zd37aS8SmV.KXNzdY3iUbIdgP2auy XwURxZQMQFvhQXF7EgQcOc2iKWwnlO7oOCX DxZEMW4kZG-MboOPiX8h BRQNncd1f9Txj aK- nxifOMkrZQgylaBqKksSv ++0K gaiuQgX6B6gCXMd5eagz3Sz5vim31YdLclWVKq ++7RZm.,Io67 HBMc12f2-.9zv9DdmaCTrqgT mqirmPQAe ++TARGZy.U.e-.l ++MxC.02AVbZbzXCw W6N6rzSQ4 ++ 1mFWCK3 JJXBgu65CVW3MxD2M8 Av3g5uCzNxWI0puDk BoYm5 4IG8-.eg ++rkl3 6Wwdx1wB8 ++HZZ,kb,6 iXmMIpMY7TVxtIpHeXLEz37mFVEta8xnG0wKdrwaA ++.EcSP2NJdSLsGpRkkedRzBtbm7623RlfqR.kZUE5yrZQ aGfQ,2i1k1F GQVojXumSJ POD7YMprnaSkDWzlHS rMzvcBsOUWKP ++xjHXlQk erRvcDfXcUhMd1cqxTDaQgoOz8n7zm.GHi ++3pPKMo-i 0s.mQPvexr-lBAy4VaaZ3BY7mkhi0VPIMH5Hh423.W ++RiDRh7JIdIrT3bakj0PZOO86BRf-0tk1C3ht7v4fux62vJkaONIDHoaSYPaX0MEqrbgvYhhd7RwCut3.tkpAPTyZ4b1B.VMzYlXaVoTsEZKGALlU2idKDhDTYOMTku,AturDpt k131Grom8HQROdh7v NToLXFniC,K5TLAGJh8Vc1CdvKYAgnpe0B.6TjvycoUUBnLV eC9-dqFkx6suc1,lRNbRh ++Ndxe cn,5SWCAEVynaIY3grGhN-TltQYzNXLUzlDp Nr32fAnpU.qsDhJICud7.ZbqprNoAgoGup94McnmHNRd .6G,9 ++AN4B8TakTWwvZhpu6.9ZCUYpMis-FPXtH0aOmpak2ixEz9nbwJgdbcBP oXOQ3Kej6rimmgZzvaqNYrXLnAGplU3hcFN8XsB9g6xQ0f8noq-KA0BfxYjCIiaskC3c gTORV1jqzPzDFyS5 LjnSdg-QCYewwTH.K 0kEJsNCd4hyNB3vZAt S4h4LbsZIPAkaK-ln,sRu ROyO5H ++bUj,-2fUg.X-Sht.foyK80ndv-yMvs YOB6MQkQ.XlX Cf4c ++rs,t9bthE8IIKub63ZTQ.ngPl 84ZTEROslIzCym6C fIy-cRRcyZJwDLxlz LN 46 psePYcdDV1OZ6vpIrcas8UP8ID--D95PlagZp- ++93P8e85NrHCAvDYgKyjZ oR0wujFcmlJGe,f.LZkPPBOjyIiOfosfZ-ZwuSLR8cm0h1dIswlxW eK.QrmGEJuMvWi.4,SroOwdexosjIS Mj6c ++QOlWoQ6X2D9T5N Es.Zf ++h3oCL.2lTv4tDcT R8,npBZTkFy .gZMtCbOlBDSgW S56XIMEGONDmnhNPpYgEvo3wmY.w.Wbl.Q aGk5haym8fvkFCjY4o3gMdVLtB-w5VmPPJS6xJQrch AAo IddU LpYEYvj0KHciI3YhLPiu ix1qHe tGIdZo4DO.5IUQb JQf6sV NuyrBowt0iJJr ., v3ANWmX1R-ThEzB3W ,DwIhhcHPAXYrCfkuLOXkPTF,RqS o BrlQKiEZ,wqgCF00h ++evOvyANLzQ i9i8O-6ABvar-ap83zQsdQrOwMmTr7cQ,5uSUy09oNi0t8dt4iq38bspJou1Dk2qqDgBH4.GrAgxQO7yHkFGHHW.D5,Gx5dNt3Pi8plhv-mjn4hmdajXe3aFhVi2k6sM05Xmd4jbI0Kw-2seY7OIRmZnj453Gh81ekLqLq6PVrGwrP76GminZaob J5Uy Lo2Av709t4 RxJGPUwy2M2PUkqOeqM5TGvVnoUo,l,,Y0fjjUOF21VrnSIb-kt,ozhyMqdXQSPcmMJbM9Ckw7skGHmZy l egorvy7hkBCn.g6nxx,880E,GgGCswClR. jR c06mr7iLLBFV-5AFxf p7L ItpIPat up6CzhqXNx1n0LHCb51 9YvbuBpbZmKKps hR8DBYkg0eyHQiI0E ILDWT8Jl.8-, QBy7W osOlwYXE 3KhhOlRVA03Xn-pirIU2QTsHJkmHVIEkhRSWrPias9s4 2ufUcD3wlC7e5DS,UhHx ++uN4hIG -bEs ++vmRemKAuEUPud ++Tus--9hNsk5iQuXvEHuX,LguQ Q LFus26mepgUY Qi64v1paAFtv5I3DR2I9E ++Tf ++PoO0baS AWKS46r ++tq,N9Z ++rb98CfOzj ++9VejcJXAe8P8H 6HcBwrnvY2B0yfcmrJY9Js4R8-zjK40vOc7FN3 sWL6zRq.fb5s-BWb --m e27zP672 KMu88nqVfx1SoQ1lZG4OlQ6TfgY2GtDXMd5yaBT7Z-W.r-e.nyPtPw,UR2gfxEpswu.JqUi9MQI7tnmvCKV4qrlQm k,X2pIMV9H.0PXyMmc7OE9e3ZBJ3JI9Pi3tZuaq7JvRQ5Xpgc ePZZeZjT 4UAeLBKMOI4W2 ++gZ1HntxBxnbxhWPheXVtM7Ytdz7CM1BrqFbzYB1TxC 8WZJL66bPvAriKXYp4K19skQA83o92sjikATWZ TUs4kPgfpxbs0bHorv.wtX. KLLuD3hrM2cy2Ara,xD, cz7PRBmJ6nmi3ImRAbmYU6ejNP9U. 0aoMMBAfb0.24Np9RPy1XveXW2L3Tq fW6ZlvJTYOwkuL9zs GclJ tzNPn9LvYrNW.ckz6UGaEQL ka2BpGhx.nWXdN35cGqvm57G1SrtfLe49sOlMk80KD8Y3UyrcENwmeog8Txp9bUL7AlwEB6vjvrZjbtzGLvCMVi0 ++JQ9lu.L0s5usL.9i xbH ++jU.qN4TeR ++mbaBZxkcb1-sn3 y9 SRdljo5Ait NWkZ1LCjaay0MjwYq QaCCLndr,ige-JgXLkgTAZ Q ++XWJRKf,6PLTnQ0 2PkQJbDqUvpAJhJR-v,u0gEi3V5i1RCfzDCQ ++Bz2RpSN8PIbvJvmJiaRG4 vYO7VbURVKcuto.WKFdXCEIHG6 4XLMCqYI6gYwbXuSMczPsox ++18BPYV1KpfUZWIq ++s2ophBiyn JhTrs8uKu.apHmlKGTl 0V6eKbp3QoDEI-YF4jfJZ.wrdItT6oG6ag ++t2P. SgvlHkD0z1Rwz7IsApSQ JN-KtgxIEoi74gUNlMI jXHbwaKujYjTbroSKUJGm Y p3y3sJSpBEYPU3jWsytMwdlnLUWEmUXsGR-RYBCZygAL ++YSih-to6VTutV .1N4Ig6U-6nydujC1anMBmEwt.PWQClqVty0NSK0O62UnC8vkU6-dz ++Iv2PVTp1sA2fUMtE2BW -ab8d iE NRQQ6 vlY9uNx5Ogu.,V9 ++hWaYuorNg8qEDRBmhAxnBrGxdgZUTgcf7w nb2 ih gveRA7O3J2Db8iY2kYQj ++9 b NaeWDE9HseE2mxHhY.XXu,642U ZcFaHABvT0GF12RZA6uJ4ORCbdGxC513 qNrmkiXNJ-d.9CXJ3b3F ++A8ECf9LaWOBwN7PX2 7gdaB ++YceMY.vnhgxBgUbRZDyxNO7XFwgGrITgWfJTKcLf eh jy3cva4VGuq8jIO--S ++nrn o7E76TTuiK75c lqc.FluE3 nPXp56YLHIypAyyr 3a QR J K ++QhYfeVCbZK5Z ,Bs WSb -w0rTLrwe Zw9W43ObS1o3Gkl KLUl0 4XwzmuxzkrjBak,RCABVK8AVHwfyCoD7-z1,Kb,6NEG I91meK4bLN,c KTr gXATxmqipLNT GFaTq HEz2qYd4H2lk9e0Omng4F1BSrA9fnZy6Lt6cK6f8UnTovVzIYq TFh2sH3jV9SlyIRyMUt1Tslx42g5yFp YMU e3g9rgMXSXD. ++.mjs7RTIFj 3D ++a1L2P ++X PRyj K3NAD.EV4qnT39yOEJyR S.cr IXc3j0BXn8E7BzBW ++Fn ++1HB ++joI noZ6ln,gX ++ESlFMZYGxUBheCcIBQ- b-5J ++Yu6WO.M rR5 ++dnyDjgb9U JSK ++ ++ADTfQU8.wqeyF0ZvLpg4yNM ++TPixNq4l.vc,TrNrAus8dNFIYSfs8,McnRbZfH9PB.fm kTs6DZlhmRT.gMU w5 MnCW7,f-C 68DVAIAM.P3JIzdBhMQh6Uu8 ++ ++Uf9dWqvxqV1z8v6DlDUS M k32zBLQBtuOKTnp9Mj N ++GZMSh zMXrXUDnhzjq9Eno.Dreh2b8Cxx1-w40KAiM7Ud5XVIgofSKMv,TPuFEbfcGEwx9Uk8q MUli-s gkz, ++h j ++1i.,sYCJcrowcwK4dw BI sFWOH230HBF7P5jp,p9sRHhY cuZVPOdQq8UWnhdvL0BMwkZOlhNBDzqi hpjJ.Tx2f0D1xyCTWZjw2f2TYwWzqFL38mTf9jmxZJ3MKaRt4LB, 7B.Y3APmGF WH6B4rxYD2Lxb2HNE9w601Qzf9iuln8po3f4ZxZM ++TWfrE7wDwNYfnCTobt4KduzZUs72V.qOsC1V5cnSh20-mGWPr0 D2B6qq 0lrG6 40tfbPB1EFacN26kHt79Bk Aa8PnulKlhJ5WxdRbsI nmH1vsILx.DlmOXNP9psR-x4AOepP9erZs57m9eHo Jci wagn ++-UouWCsaG zhbtX0WCp0WQvT3rKJaA2ANwq8wTdWbScn0SkTkGGhRWRN1seU5Ui WSWL4s8I 8gnylznyfYoAHPScy4pXVuRD-vgwlMa1LJwD2 ea 2Yz ++7aTpuAlJPVEhP6,MGkwf pS IvjL0eGBxpZOvyd7fnq3v 5UkjBbL4 MfVUtgk k5uHI G5krV4kX vtd WHX 6cDtNbHJ 2A,P 8gbrwR8aDT0GmbiIDC KPrWL6crSerwHbCjdSQkRTq4R4Wj ++13VKhBn8VoQyQ9oUQvZ.b-AFuGJ,uJqxzI ++umSPK rxHWNTjNIq hSP1oBhkClZ ++gDD ++w6R2zafWy5ryoX9 S 9u cFf5 O0LI ++zqsYCK7M,5bmH3F8zHbhdVKmUa..,5esRLMJE ++j VHF2SKZtehxPWyGfamcHATsiPquW92yEeSoBCdWnOD uObL ++, ++E0f7tMEqIgc2QGI76CHqqjImXFmr Op8 Z-k1pSoZ0MaJ-IolHjVxPk3e,t,Haa TpRLH HeMiliP8Cya0GW4lrEnnrzJLp G9dn tvbvdgX5uxylOGUbSzxqfq ++d n1hQmSLuRcbQ0voDCeDVC,BmWNiblgGZGjdoIQrhNDu-m3-2Cwv.ikXe2bfs290MuK9UA SwVcg 7sNgyN,n NA9haVThpNBp4sCX.SrrLdk1hIqwVq B 3T ++ 7fUGqwMFfeR9oBF Y7TR,3sZgrQ7z9X1YL4uGYzZ5AQhDH,N wR5tjXZDx5GWdR5jhK42B8UZA 6VISCgqVLAsLYwOQ48iFLXB tsrxLSDPm6j03 ++ zYlgxNFkD1SIrLIx opu6Xbhw8VWr--fhU ihJJR 8CzpgrNkmmjQBzhy4SAO,4TrlBBq8iKw2E7FVpI9.DEAC,ntPD4UBG5 J3Fh4cFoKZ ++j NS5cyXzE. 5LEy Xml9 e6-3EDuPPNTbpe iBGpqh80 0IaT6YCo4 7voKby7o2GJZne5r3L, h1cQsUuPMN3PxQygAAVudNtdL,.4WUrM,zLkeG,yfzt bDH csPMl Rlq gUobL g87-VRq pJ,nocSJBNPz-6IW9JXdgkfESygS HkNYQvEmvd,YT 8ZY0Qse8vs-4 0pahZUVMcMkYZFuGgg4L8ZHbg1Qs.qNfglaLm A2f8taxhIhN1lUfNW2MTx5PvO6lY4kx2xX91JWYDZx4lF8AITm4wgg1.BslEC-z-uGLe 4EBbi7Nz ++o,YE30Ddd0HIL847CZC9cTpMkZVRESngEZhTE4kA79e1.rQeIxqxUy4lUWmJd .3 ++N0hTm2e6khMp8-Ig1fPc.e mRba59uBgH9sUNmyI ++Sur,t3ycNfONpaASA.Rxayr6H04iLLe9 KyLuFI.Ljk5TO,jWwFAxAuH 0oBBLdPOgItxfvF Zt4Aixywn8877oiSrn.k apif60avBe 6AjkQreigNBS9xDbZqYpYFwhBvK 9co7vg1IA2oCQIFe6vJFTrHXmABCp 1 v64WQ53CP rgs.N8IU2w9XGxf ++Y ++A7gPbqywJ7hB.ZlovVkR O,sU0EfhvjW ++ev9vgStgrI kPcUnLdJfBGpG5fX7C iakPxJwooRcxOm7 ObdtyqeLSHzQ1qujSSp6QCCY4TNRj0O-1jRtmeDNRWM53VDNiWxPjMowD2EuP4Aq0H mfRDaQeLhPtb 93WQA6ZQjCWfT12 luxhsF ++IOC ++Ayyc22brw1hePHjD1UKC,Wy-VGLpBW-OyCOv5yvOO,PYyz5kEfHnzen8q4Nb ++mMu5JB6E0oN pmBFIpksSu8eutCg7Bt.ktur12 ZBp6RR,WLOE,.22o0Man-,aO0bCRmBOKk9jP3fAVewiZRCnh4E69sLIRsS2g5A ukuhHbqoA90dTEnakjF,YzCw c,42btRMPuPxzHZhkwKstFngMzByFmFN2Zu0t,qJ287ZS7LxHHbhNq3dAXiP-wmDD0XO I AiT2yh5nWl11kBagP9zmfs2Xl CrF -Ju2V-SQGE3jB94HK956K5, YgAB6S kaAybA ++FLdPmiajZ7EnaTWOM4afusOYUoOB ++yfAnOH6XSQCO95mWd h Pr50iP,ORrjbRIWU5iCcoeVtmn ++mX yhSn.Yvg3OSj yVc4dJTEKxfwEJPsi5DF206aHjTzAS3 y.K35QUKqbI1Jgb ++,0wj ++xCNgKvZU55pH7E8yk2u,77fse5qHANrSf3RBenS 6dJzs2ygEgT4zbT KmU6bGwmzKL1GZR4CUVge xJch6nHa ,RzQEElM ix fAT tRibu96VPqD.MUk56Eg3QysPsSBellvToJvxQpbqaRZojbpPDrCYKBar7E-MSAcVMBuaoKP Hns68hmU4w OXcuVm.X,uJIa53ze yQOjBWWRM0j2E5OP2Lpv ++xg YPM 6KdWegs6q1gGWW-, kvGt7xf0 YU4-g0A,8 Klnbu ++.qGBke8dWzOkF4iYbGM2KLKsm gjl5T Y-JxjhrqnYK8zM ++obJKjnt ++q0cJ zxSVksbXFIMM1l.W5ST4oFLlLLp7lHZNVnB9LTNEMYT8d vCpGw1DJLp,I63Pqd-AO.2BXlaFCFgx88nRMC2-sk.q VZz,gFqsCL6yIQ7rkU-lY 1jYG3.6OOgFnsO R7s0M2,JNYS lR7XOEW lZzCIj9rnl HM ZFfA0gtwNIjibAXL.R,IoO8KSBt0GbuVBIeLiYwKS7hb-k0HG unYpQsUtq6gyjG zW.c7QPgoQ.KN71XvuVh2WyF0nxN0yhrbxPhaU X ++IauM9 r.J.2b6oc vU.eHnvzm C62K3GXPNYZCGf6UOfAbi ++ltEBGKKB1dI l.06x4.Ur7Vd9jzdo6j2b.GpxB-hWpITsa 8.TqIvC6reoAD1XxNKyPk5Ua3pCXSjGHl0 ioE.cBTIC0WRkfosSt1CDi6dqORm9HxXFvGgScLWgb ++zqlvtS Pqdfk0OEPyUd a1rf ++cVO5EZHa2 ++12kHCmPJoxiBMn5ggUkFjZST 9huUO3bKzJYzUONrY4acTVAiU-Nb tEl11Z xZgXU3rOGML sO7YzupKYhFnqSCl,Yz9Wsh-bR0,IJPz71zpQNVWdv7gWlHkcUb,2KbR5IFBUxJYAevA4X8mGnOTapDUChLBGDToSLOOItKnMlbl IR-59DQVrmyFbCQG8PrveMKjz3W6VE4c ++CO4b0NiEsDN.1uTy3g ++ pyj6 cvNx9nyNb FyoYpmKOygte2Byo4 qZ CNhgnRO uWauOt9 ++42Nkd4AXJDKEmJZr2OlI5AyY8YIfShVtIC4srtsF 3BHzyia,J16LwdA, 24Zx3teR1AoizkAeb-s.uJjsaqHONk-tTV3dpb rrTAIha,l ++5I.-70jJsWmiQyQr1 ++0Exg2-CRulW2zMGJ,Z6B nfD WBXU1-yWKoC9nhUXpBfsJY8 wCxn5sdX- J.OKkIP4RZ1Lqp4OQ02W0-Y DJBu6RbsDL9kHSZ3xxuCp,5pwKsgcJ8k-AkQFV5 rs.tY0gUqE1z.EYKeLnlA1X9dnl9e-LwVP3dkkmix-DDKKjO,NU O7eA dzdL9mu gmAe8fwj0upaEq EjrH ++vPu7Mob-lsnSqzn3spCaQwDt6AJuJcAE0RTGQfG kyaBd73nLN29 mTI3piC4Xh rvo 5BjR6e1jScg2NbUznR6yr9 c4 Ukb,WABR6myjVWje-44ttQDd88aj4qjKD7kC2 ++Jp-M5-kN-yS.zQXw-0o ++rAeXAAnJH HdC1BOVu ++QxTstVc K1FhXcEBsh3fuKpW 8P.qREEG3J02 ++2moP56 VEMmMTbf.QRYT,SZtYJcBapAUpW3A1-.x5h8cC 8,4ov1Ym6V5nZu2oVkvMq5Z4i qgmU4NTIsBKBYSQAw-HxWspTqxwOF8owjlM8uL4wBr9np ++ljxf8aN8i6BMAv9TfjIazolVgy ++TQzz7l ++n efpuo l tCPDJE0d5819p3ziamx6 93fmgmyB1H767SdpL1qPd29jkilr.i8y,yU0lqMb0OcTsgc,CHcmmJf, FzP7q i0EtI8 yKC.6xFaB0cl5uO9Fm M ++iLzk-ZACI ++rnJ,SD0Gp23CLx3lxBBz gy b,gpRLTsd g55gDMMV6DHy1 ++ArLt,c4J1yEJ7tEJOAV,88qSq3Xq-Sfiac64FMRsatU1P9 SAA6BBaKPfQz9SRzDBcSXsO8H1rSIkQE9OljIJj2eU6MVDL70. ++YyZK4HTaV9XeIQbMP 3 vBPTIwlhes fJvzU12uOzbJ2DymN3uvOFZGMJE2vH M1bwUOvyCx jt2pO4L,xB0mvgvv2JvD0tA7V0sarrt5y8hNgDePLU-D6 ayyt.nxO1erKA5wLKbHK50NCMve,EbZXQziRFodixV gFyziHgcXzwmyHHIuf9D7 2J1rWCK, ++yIXLpAw6XIlWvuSAyeBLuknHUl1ESPIFY4MlZOADM-2fDrmI0VGJAT.9SLAqwbiLU7zr7TvL5G0 Bgekc.qG7w X.qy CIXPXvHz0p-vB9 ++, sK,SkE Q61hW ++BVAi10.5phYD7m1cY ,61mG07T3Cw,77ttrm2S lrk ++Q EL1 KI YBu.JaztPiik6 ++AhqRcKt6qMsurjwPAXlg-soW pitH 63XC3lQibKWo 0PWkQ Pbf.aRxw9o-q,zRy QTa ++-,OWqw8b9gPNdEfEDM Qbrxrs, P3k9cvDMP ++7Z ++2mZlt7. mFA8ZXPFzT1AneAH28iAJP4vIEf4570U1bl42BKO 2GgoiY1SLp9r.DsY k1M6 aZpG ++4T6p4voCSlMzv6Zuvq jT2qjezireY,V4AycovQYgULIqxNH.XPEySCVCdzd8-gpJprcprJFI7Op xHJt-zCGGb-.npUsWzdgc A6fSb-.rQI92 ++G3aJq1AcLIETZep8S 70a jsqjEPetYjxCug.zOP0GhajUN6zmv9Q ++6.Id71HfNVIb ++jLHejMvHxA sXq6KuTOTO 3hPUDlG0,e5GSiYTZ3s0NgpEIjPBi8HXeh3i jhEqoZalHeTSj81 ++YAO5AngCBBWtDZFdrWw8u.DEC7FOwAawv HGaVl puI,Yh0 f b1Wo zs.FiKL dgkAR7wmhye6JcoQhMhGxPhVlW3EezhLUkt4P6mC7ZA4JliQ7mRT dByak ++ b.FaSArLmJt7HFxQ6.pJYQqPVQDXtmgsC9 ++1B-4Sq IPI,4K70Ta bEqY.gO4wfeyv2oOSIfDgz rKhgW jcbpCC49wCUoi5uMdFRSPexEpyL 3tGagOPLbxPhemFkyk4WvaHm.I6FHJ ++YyQtOJsDW ++nBQmdUaya v7vuDX hJnQ Zw MwNwED3pAR6oAAmEINH1zXZIpM4 ++Y8Q8RPvMv9nSORP2MNuKkx ++XSszIxkrmaizlJhr0zOjtOReIXl lzed1SpMdZledzb beVKqbvONzEA4.iemEgBKBohlTLHeXFyc7q,IvczwV2jnTF27HYMCacRfk-N ix-E0Y ++bSPZUAi zrYQg,LYS9.xHQ-c5RgpycC UAU 8u3MGP3-IJb0MncMIu0n kGO8QnJ0j3gU9Rwf4WKJwy0D2x G3 7Im4ZUZ0p5Z IbysJVf16y.0MUq5aAuj2TvLGXME0z FM0Jigs,9UcmJq5hwp7ia NbonEq,fb33ZhtzwvU92ijkjkmzAnFtSsmV-qKW9pElSfNT zBjorW hBufzd5VKz ++2w Bh E6tqIKpp ooZ ++9 XLAx9.OWFFcT8 ++GL6McVU 72uICz.u5ZhkRvjL37OaJrO9jJIgCMxYKoi ryz8eA,XJtxNWy ++8l2W29tSeoKfvu6j UkQ7u5sqUE6rLoIbin2Ft1vp IuJFE1DYn ++zAeG2XwF7ouC Rx5k1M9LFPEAV..dQ75JcuD b eHNK ++qxeONETvW 20q2o 9l-QHcaCuYRMD-fI74Kx2AiDrO7P38A6bqL9xYh194wrOHGTM7o K9iFOz.YO bdsyaR ylexPe4h-Nq.4UlLbfBkI5TQ5Y9tlHQKDcJr8khI1eVM3exXdL ++ ++ 6M3 ++bnf,rOBO2 MQcN505SUMJG1JOa.H9KmLqMFZPVeK02zB gHf 8MkfOvMTNtVtlNz ++PPSUfv,a ++KtPO7zFfoHOFzikUznrm60CgkBeSreQy0,QcEQ9LHTb55jLi D8vCT4iSjKEt4cgfmJwv XsWcml ++0KxltR 6 Qnbh9ldg h1cEL8,dKS ZrbmLCKxn K2GWhdSD D A2K8h,T ipqteIJFqHbNjHgnYbSl ++ ++X 5XrjaR4lK-yb3FMSEvRwTHxRIv8DTmhreGWYmEMI9i g ++xmwX Da.lAJJqu2sIepS g kP-w.9DzE7hjTa2p26K ttEoBdwQaMd1eV cx ++gJF3nfUn-okboaBR8gmte7lvHMzbs p ++S ++lcm a9ABa p5nwXnt HATgPsaAlMPwDPO6T469 sd6oukDJkIaP0laxoGzlt90lBEIJB uo5 0A0g-7vXmqutKhztV0LE2-BZ-zROAzv ++7En1c52zI6x-mYzYl1V ++teE LhXwWBnpOtcR32wDapi6OmWT,zUMLXr13UHja6 oQOJhT ymrFPlKusZueFMlZ6qFGYMbuA5mAH0cP qHuybyuE3kWj ++Ihok pA5axqO1lH2UDDfc-XUtyChw ++7jW-qa01 Jcu4WKQJ40E01msTLXVkjslOrM6dFr.XIT,c4LmHtQazx YMVhcIm5sZwNL,14Q ++HYkOGk-qmgQXbvYd7EXY73S2Zp gk0rzHmuIU ,42SIgjuYQ1RWT0pc-YIa-9.XObE TJ2ZR3ySLzKp1rmV Bp2u,0.aUa vZKsGtr9RjPAxfE0HlX83srGr2WsYgQV-uuV FWfE cfzJ-baDECE Q 7gFGG9x2773X-0 BdMR6gsrywh8H.QMEg5IQ7Vow 3,euIfOjSH GeFxUMT2,Wa-a zrDIQT3Ky3AWP QUJMfIjpTpOl-FiO4nnE bzQ,Bat 0povffp3pqlj2-9CCy5HgtE9qiJNGh mc,TR-, s. ++7kBaixHimGa2bleKbWspEMAa tUG vu ++TGxI0 y1D-kd.crNnhzAeCVqe6NRzQknICmDwSse8PcaC8iYx8W4-pHfnTjf7,ZsnlwlT1E ,MTL8tjpmTmkH9DooBmS4qU4 xoZ ++LwM.tHQo.Iq dIV ++d ++.B, RI6Ex0xTTxFvitcfYX rMSd06GR5bv0-A8 2cOaSrgseMwzfkI,evWZEmichsS5gKWbt,sI 99dhNwhEtFKg UCGPMJrZebILe.riH2BQbVuK,1HfUK YfZYZbX L IlLNG4pW ++7vvRGCgxp23o7WdK4Wia MipnVkHnXWl8 9I.CMTx45 ++l pQbIxx.J ++Z2bO4U ++BPuebut fqDuXo2y9hc33R3KXkEGo1potKFMfJAa28R05BRd18,,WScIvKAIPuioKdjcQeH1HAD aVdWRnMCKwuGMUaCfSUHtUridAOceONqm4jlYx 8plUMxhTWGSHHprBsBl V3hANjCFZ0ou,rbjZ-HBplXB8c4l-5EKl7v-rVXWWY-GTsuHG1OBy Pt HyuMSTDXtpFfO0-U2HOUXaOs9,FLPbhzosAbq9G em0OnrcBkVtZg.FDfXRSyvYhli8 WdRexgEFTz 5g7wAxSAtrKtFG3Pc.Cc26C1xDjhS3csHf f2lX7ZeD5BooTVlNE.AaqnV8OiL,4UQRavIOhD etd0mNQvmFdiKex084nO5cIL9aTVkZajCQu jHNcYl9Tz2NOirm1waAMWarXKe,F.SJPLeM7lQifh-wxz6ygP nZldUvAEtu.OcszUhO M, YRK2G6h5MLJgSsV58rTxE90nkKG55Lwdl 1mjRdZRcINjWh7CU.BQveXGrcwrFQtthBW,TSQKF.PqBv 76jTMg7S1CPdmBBTHBsIpTkERSM4Z5HaB,jU QjS9yuf4Od4eTUxBpS ++aPeMhuS8rpm-2LkTWMN7n87bPi2xLUa44qbU,GerBU ++YjBzlZeLbu ywzftCPWNcOVbYAAxwg6MNikvMGOj1WQeMc ++vfWSGGZ.041ogs ++s1e3VVtBafOR6u t57HSkaYrhIZu.bv1hju0MZ25Crt3k,csVPCH.3glzdh8NE2huieB,yrOPJW 3r ++lxR- zTedDERTj8JGbdYD2TFT.4PQm2GLUVgKb ++prhYf200si3HUMfBfZs XfM12 HpOsjiAtzLOPGJMGOxPHrhwdplSK5cZMhP35naswBEW2J1 ++4mTm,19bo ppS zqb3lW ++NPm3c.V,G20L ++NJq8 tlI Cp9Y2GhTS3MLc0,OOzX 7 OajV8As0nBzYdRoY l8oLN9BnyB ++kkzV0BZtkxQh.zejIN6qr3wbpbl oXcyEtsUPsa.BOUUMASj,XUk8CWk5waUjbFwd0y3usQly4iqqy0 ++3FeSor3O,5J60Jv n70RRC3Zisi ++Zfl39wR.vY6IiPaZuHgJ4K9 1XreYyPO7mcIZa1 ++WY.78Yc354K9C.cmre58-ftmVbxUR3BCRC ++hxbIBZ DY.MPAMA ++ qk497-NCy,Cen- ++a5ZBl,J76xh52iMHWZ3aenTAeDfSJN-1RGxgDrsmAe2YdwI8xHlCg pw8o6LwBzVEZqG ++wNWb UtIC6r7miyZb7h 3-bty5un9Y3dlUwNG5mfVPrmwQulJt9ktJV, l0hN9Uxh7BfuTD,K7TZTnTmjm jssEYBKK. ++xyzs4W JpkOg Q7lA87fTr2JWvjAi ++zEEi33qgsSNrMHS2iqr5wqr2 Tx mjTsHEPOVBoCfrGAX9ofokfjdzE.th6p,wQTbf5c 8z2 aTcLfPpFlCJbl. Osw5xgoRH3 GnkQP lUVyf4El -vsoiJ9T.hg2 QQNi,gKkaOyyqS qU1J,kZe15 8ieicP20CjpfrCVrYPvvLO8EQideVF86uaYHjsHYY.wCctbr.Y5FL ++MFlYEdf5ILn,NMSRDeMKM1jqIzadP kADa-ClHjaEtZ,XBdafWyDRWy,BgZRFMo8TFsRLuikz ++m6D1e23H8ldUeLu2MzQjjV C-svnh ++uh8JW29kWNm19iX jMOpERy4Q ,.OLlZc,wg -vs7d-1w-trBD1uRKELl- ++,xWdY9Rkj8Kpt uqGU2PjB.r hIZKhtnEZN6XhFuuMb wJd.Na wyOVHf lh8 ++ R3PRV2iVSoL.JVGl- HNbAe0CPFoo525TFmc883k0M7zzY Wxx6fVKrxvtrbRVeM 8CYQMGSgF j5B 4fLxHczHOaot4-EsiKEG64ZBC6UKjukj87y,nx45cpGIOUmjXWgW4izBScmXETFI7j6XzAagsS3Nv-,L4byBaQ2TWds OhA 3AESFIr0oEsU NU15h,wsDBg2p42OTplIf,SI5H93nkZ0GxmL4yNyrm-bDB3 U-Sgp,AnrK.A9BrGze aNNss7yI ++qN,tPvun9A.yCtzlx -,g w h ++.yjwfuMUOMcIQrJsgqu2-Hfiz-BjrH h 88QoKZo7KHWU r ++sA4OlNhNph ++0CA9xH-d81P ZEhsDZm9iQoxTf6Z7eYW1cjuSBVZIx pRB ++oSTXYAllNqpDI vyuH,eIcyB9ZXW-MbfU8P7nzs3bmJkPyp1U jKqBC2we4mgTvhzGC8te 3eWSnA8g ewTNZ.wE gLosymT5rzYp6xWmEn2eXMD66199ePdX ++buMWZXgLEVrgopbDy-FPV5PTK7XNBqbtuH,aomcwD29pBjVxbxx ++sny bvnp. Xq udCyGXvI ++SPtd6HAYBDrUN Hdr.BgnbeNEJliTj1oFjvakIoFfoF.qmJ0wWWUQRg99fVi9CJhMgjjGGw 19 fUnTURAm9m.,bxsJ,bazomKZFCY5AkULVvwYZs 7iSVGeqAiD z dMa2yV4XbgqdJIXudbihfXBjdvYapvR-qsH,3QlgORmU VFvgCV1vh.s3Iooq1Lsz1xul8 ++X3zXIVmaOIztKHEs9Q99s-,.u8LnxH6a 73Axy9C8nIhmdWH c7wJz0SbAX3 yJpp DUVdLcWpMhb6P5pRJogN2SUDO8yarWrYuXYUazFOaq0krth,P0lK1UdOJldPCedO4 ++ysqoCqYbuSjiBKA3H1JDCuNFLf-enooow7 A9yz4GY 9PvpkDXeTtwBp5UB rCUx x-3hwytGh IHXe61uCcURhd7DhUMoEc3RWVz ++mKEd-ke,cbEcXwdvp 5qOjZ,V7 SPA,qQGBYNiM lEw0jJg4TK7CaYXhjM ++I2TUm ++uPFHn1A-0J.d Ha0KCRF,9RKYB ++Nj8s4MUg8d kkyUtsUnU6za lbG dPpVdktecC ++DeI9Pz mxFnIsVCDALgecea7ZkXi4FI5Yho gIzwfVT,.fliQelNVIbOxA6 .weM-,WeDBn.i8,5PTnSWJge-AoYp-OL vRSDt9BXs4ntRBqum.JdcHgNqeKCsC,NSotu3FIEoXBl,K8HV53e08hT 8i8VNYkgd,tVKr,2 wu.B K0QQSo G5 ++Q3MGCAuib ++eV ++o ++pD ++x4E.J1 ++1D4sZMGbgQV2MAUDK,WlTk x swHTOXvkm 9,tVK,XK9wzjfS57kRUKASpLXQjxRC8 km0c qw ++CMnt4CmfbyjBmX GIC4a TUjDE1NT-wZcL, MSw,sp7Qh3YUPG83VQd9vwR2sLUO8t..-NIt.1MR4JHIq.q TbbQcb,ydT,0Yubu ++QYcaZz8hM.yl rVTr ++zJHTP21h8o4zab1kFRB.GUSF7yUP9 Y2sTQW pd056SH k .HfcjC1 ++7kfNV7aw22WXE4KM0wmMNsLXrv06RfJz4UV4xRfTIOXdy2c5sohEsUf9K4qHxTzNh94nuR5jzrZ5-J5EEpSw2xLlASixh,K5j6T.DRea,-RnV6Cm8P0vqG32VX1cJht V1oJH7S6Yi2dJfs IUW02rWnZf5nYx5UURm.d4ssYRJBhp,UvQu Vxj4oPIG1VagMfA X5h.Jd4u qXd.P.WJm,srJFn3dL18NQQs j48Pd 6dHc5EIa0Etl4eoMYeUER5jD oGIWRfyn V8- pB04L99qGnORL2lYespc9EOKfAe-1HgtPDv6L7lTIhdljBn2in2oyLtcal06sMR ,uR8y,yRx8Wx00H0luyv2D4bSCedIYLancEkbr,eq.VQsnsTJT93TcYip0wDwW2mQiblD7t9qUsumIXc,QbOcdEXrkHCqFpwA7cu0l0gxL r.cYjsRZdqJG.n12XjrE1rZ2dTt9DGno073DsDyNhmoOEsUT9zwEw53BWpH9WEx83zpakc 7u8uaFBtqS9SLyBWYP9P1ST.nIqqfRtT3Lwrtzw4OfVJ5 6R6b,XjjV.BYEefUoycUmUm-SyEfJCX9oM,O-txf ++9epu-PS8iA0dXkXXwILBM.0mjH3cuhhIss8IEykhDP.mq3P0go8tt tZzA09ahBN33A.eTq22VTRzi2rv.IwrW1Trhr lVh. qZ43Vpd,QvuuiqOV ++LnMcVcdeSCe7BTeqXAkw6iOCTyZcUdm i420WugblrVqbvB9s9b ,U1e.IoeyekdEFcdfTVqgPOZqEW me ++Cc8kIlH-L.zbv5-ynE8XwX fZ9 5DMLOxK K9WPwN9Lk5dmkmNRy,zkUpDroClqBR8KWMJbyIA ccxywk3 emln10wTKw,J184gc 35GLynOG7 EyhgGLpHzO NXtC nySlxev4IxY25 XPn6h7gFiHPdS fBZBfdeRUtzY5AuGclBI09ulB2hvOA4FQulBOh5L mzYX8h ++mtChbgmgsh81x,v FU.wQcRP5VW051HYKndE0 ++gm,r5h,YnMKZlzm6I,He9 ++iYk.Ih8M9utQDgbCrX5 ++U8O Ep.2jVt4RNBdhFeY 0EHvmpbB0yZhcF1GHpCH7LVN rZr-43SENn3c.bwXRMqlzfVv5c ++8A2AcG-v4fkX-S q0BktnHgUvX X39S4ztZCK .oNV6r2utLWt g-QOlP,-Ra0f zE6 3wqTx2iltz ++au5Z0q6tfir3hQa2bm97iS9fueaEHg.xbauhO9XK1MC KYG6gew6bgTFf zXgNOfa2iCDlXu ++X8DIys,eVavqDyPj1tjIvBV6yO ++Tjl4c1Ga7Fn,shvPk8gpKfhu7Ht5OrG-BgX9IN9oP-wF5Q21c4H8kVaEpD ++b ++uUtumjbot JJz-K vwDFRYwcG58,.0b82QpA vm9,co2 8 ++l jQxUlI43eR4Boh QZsqGq-qMVN34I6ZMKo 4B66mfoU ,f.O.y.-WNTUz r5p8FFnOPo 4lum T DjyUYaW4c3MdecagfWDgwVnt0Uwb.FKyj1awD69H8qjuYzPdrxbuch0yo7ef DcSdYxrxZXMjH3Rtttb tx IU WKV GutbQWmJHUAyttd,oYwQA5 ++MRmMfBNr.NR CebobfK-xDiltGoqPxqZfm,lSBnY,z,xN3SCvWZC,7W WqHUdryFZF RSA5iit9sdpbEEEoQoP5MryYF4DHj3-JfjHD9D7NeMl0UIboXyqktemfvqe9Rk ++UeAKSY4YtPPNskA7tAV4ZzBpA hCjyR9m0O23eAY QpBkmpOI IWSh3z26M-nTCiAEnWcZ5QlLxsomvUS8hvV JuThEJodoRV7O.Nl5c 765jKK ++Sh5Pv AGYC-FzsjqUtBbBi cN5DAM4W,GFliy6Hy HEp4ETW1rFVup0jpH UvpyXOQ.YCsc8OjLY4dEMEcm A ++g, ++Bk280zf5.XSvBhhby Fw QN1MqWiivJkBehud42yRr L0G7 ++RDTO1vkCZ N ueqdj.yDIXFflZUEM5n ++oj7d5pbpN9K YrdJn5ahh5sFiVWbQAYxsv8SPBv3f Aa 1q-MVcpq3 b4dMwjua ++,yw ZK4Ho6XWlxmEV1Ic3EhZS- ++eveHPP6oWGRz3F,aQW2zaIoJMUWbbTWTpe59hv ++5j2xVi8Fppa5v-y1RXEZ-,IDoTo-xW.b8E,7mTmxej 1qvkkdxS RUuX 1TLWGFOW aH4eO-Q rsl 1f N3vs0As0-. VarM8ZwjK3quwVx2XyB7I1qeTMH59e0.ZPDrpDk,RDVe ++w,N0SyJzR5fuB14jk5vafXpOp-bein tc3opYK8G Ik7CEb0M695GQ31W. MJwIpenP9S1iSB4 bpk,jeL1nZ0m q43amvA4HDLrAL5w2CjSCJ ++kHscqgsJjK7G1,Gfg0q,ZB6R ++dEcy-3 ++4Zq5wQ9bDlZknhv 7fJ58lZe1FsJ7sGabW6O7qYwcaJs6C,h0j24XaO ++XuwYjwAeHkB0fMkZNyjubR69RzTrhRGW94NCYt8qooDD3t ah3wvKbT8UT U20 ++D1UvbJo84M-Q0F8Qz-V.ZnuLK6i03L3AET v 8GK86W77Wv6fqMv-3jWrcusLQ7mnqFa aTYngyQrDCDkt wcpwOsb2 ,mrNt,0gQ.ISP7c5i ++fTju5 ++LJbdmV,kk e +\ No newline at end of file +diff --git a/comm/third_party/rnp/src/tests/data/test_messages/message.txt.asc b/comm/third_party/rnp/src/tests/data/test_messages/message.txt.asc +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_messages/message.txt.asc +@@ -0,0 +1,8 @@ ++-----BEGIN PGP SIGNATURE----- ++ ++wrkEAAEIACMWIQTpWjy/WDqoCizMU6p7xnCbFcI6SgUCXbKyewUDAAAAAAAKCRB7xnCbFcI6SpI5 ++A/9odriDt8cSLDEla9ZQ1GtvbAC919TXWNrqpfpwLoNv+IVAsCfNlBYF5b2GqvuNO+QzEApOvKRv ++18J3rYXFv5FdXe558hEJTAvKKzaFwMylHLYiRdDUf51Iw8OppOYtXvZEM+/8sR2nbwG2Rc+3KQNK ++dVMqxmx2tC+WU8LVTjV42Q== ++=Kzng ++-----END PGP SIGNATURE----- +diff --git a/comm/third_party/rnp/src/tests/data/test_messages/message.txt.clear-2-sigs b/comm/third_party/rnp/src/tests/data/test_messages/message.txt.clear-2-sigs +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_messages/message.txt.clear-2-sigs +@@ -0,0 +1,15 @@ ++-----BEGIN PGP SIGNED MESSAGE----- ++Hash: SHA256 ++ ++This is test message to be signed, and/or encrypted, cleartext signed and detached signed. ++It will use keys from keyrings/1. ++End of message. ++-----BEGIN PGP SIGNATURE----- ++ ++wnsKARYIACMWIQRz7cyRGa/I4tu9zeUEUUCWaf/ePAUCYmvVewUDAAAAAAAKCRAEUUCWaf/ePMWq ++AQD3tml7WhJ/2e7xvyRfc6i+KoJzuXSG/dKrX3WtlKs8xgEApYctVe4t0tUJFL0wKo2YQYsdH99P ++xx/tlqW7V52M1QrCewQBFggAIxYhBHPtzJEZr8ji273N5QRRQJZp/948BQJia9V7BQMAAAAAAAoJ ++EARRQJZp/948xaoBAPe2aXtaEn/Z7vG/JF9zqL4qgnO5dIb90qtfda2UqzzGAQClhy1V7i3S1QkU ++vTAqjZhBix0f30/HH+2WpbtXnYzVCg== ++=GmE+ ++-----END PGP SIGNATURE----- +diff --git a/comm/third_party/rnp/src/tests/data/test_messages/message.txt.clear-2-sigs-2 b/comm/third_party/rnp/src/tests/data/test_messages/message.txt.clear-2-sigs-2 +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_messages/message.txt.clear-2-sigs-2 +@@ -0,0 +1,15 @@ ++-----BEGIN PGP SIGNED MESSAGE----- ++Hash: SHA256 ++ ++This is test message to be signed, and/or encrypted, cleartext signed and detached signed. ++It will use keys from keyrings/1. ++End of message. ++-----BEGIN PGP SIGNATURE----- ++ ++wnsEARYIACMWIQRz7cyRGa/I4tu9zeUEUUCWaf/ePAUCYmvVewUDAAAAAAAKCRAEUUCWaf/ePMWq ++AQD3tml7WhJ/2e7xvyRfc6i+KoJzuXSG/dKrX3WtlKs8xgEApYctVe4t0tUJFL0wKo2YQYsdH99P ++xx/tlqW7V52M1QrCewsBFggAIxYhBHPtzJEZr8ji273N5QRRQJZp/948BQJia9V7BQMAAAAAAAoJ ++EARRQJZp/948xaoBAPe2aXtaEn/Z7vG/JF9zqL4qgnO5dIb90qtfda2UqzzGAQClhy1V7i3S1QkU ++vTAqjZhBix0f30/HH+2WpbtXnYzVCg== ++=qa43 ++-----END PGP SIGNATURE----- +diff --git a/comm/third_party/rnp/src/tests/data/test_messages/message.txt.cleartext-signed-nonewline b/comm/third_party/rnp/src/tests/data/test_messages/message.txt.cleartext-signed-nonewline +--- a/comm/third_party/rnp/src/tests/data/test_messages/message.txt.cleartext-signed-nonewline ++++ b/comm/third_party/rnp/src/tests/data/test_messages/message.txt.cleartext-signed-nonewline +@@ -1,14 +1,14 @@ +------BEGIN PGP SIGNED MESSAGE----- +-Hash: SHA256 ++-----BEGIN PGP SIGNED MESSAGE----- ++Hash: SHA256 + This is test message to be signed, and/or encrypted, cleartext signed and detached signed. + It will use keys from keyrings/1. +-End of message. +------BEGIN PGP SIGNATURE----- +-Version: rnp 0.9.0+git20191024.471.5201802 ++End of message. ++-----BEGIN PGP SIGNATURE----- ++Version: rnp 0.9.0+git20191024.471.5201802 + +-wrkEAQEIACMWIQTpWjy/WDqoCizMU6p7xnCbFcI6SgUCXbK6xwUDAAAAAAAKCRB7xnCbFcI6SjvL +-A/9OOzkgc3JwM2X4uykxu4kvmRWuhKY4hZegu7Nt/eBQm24aNZbHt6z1ZOfMBJGzDzJjAtFdDZ5O +-9LqAvWEf1kqLT2u5v2TB5LHA2GWFNK3WxTapceeWOo+3Q2Ssky0tUBxazHBE14WOdM+MPQevTwtw +-C2Q+p06E1lE+SiIa+KP1Og== +-=AFtl +------END PGP SIGNATURE----- ++wrkEAQEIACMWIQTpWjy/WDqoCizMU6p7xnCbFcI6SgUCXbK6xwUDAAAAAAAKCRB7xnCbFcI6SjvL ++A/9OOzkgc3JwM2X4uykxu4kvmRWuhKY4hZegu7Nt/eBQm24aNZbHt6z1ZOfMBJGzDzJjAtFdDZ5O ++9LqAvWEf1kqLT2u5v2TB5LHA2GWFNK3WxTapceeWOo+3Q2Ssky0tUBxazHBE14WOdM+MPQevTwtw ++C2Q+p06E1lE+SiIa+KP1Og== ++=AFtl ++-----END PGP SIGNATURE----- +diff --git a/comm/third_party/rnp/src/tests/data/test_stream_armor/b64_trailer_extra_data.b64 b/third_party/rnp/src/tests/data/test_stream_armor/b64_trailer_extra_data.b/comm64 +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_stream_armor/b64_trailer_extra_data.b64 +@@ -0,0 +1,5 @@ ++mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo ++ugCpCfi0CWVjYy0yNTUxOYiUBBMWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID ++AQIeAwIXgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJEMx4YniYGwco ++lFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAcYMsXAY73iZpPsEcAQDmKbH21LkwoClU ++9RrUJSYZnMla/pQdgOxd7/PjRCpbCg== zz +diff --git a/comm/third_party/rnp/src/tests/data/test_stream_armor/ecc-25519-pub-bad-crc.asc b/third_party/rnp/src/tests/data/test_stream_armor/ecc-25519-pub-b/commad-crc.asc +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_stream_armor/ecc-25519-pub-bad-crc.asc +@@ -0,0 +1,9 @@ ++-----BEGIN PGP PUBLIC KEY BLOCK----- ++ ++mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo ++ugCpCfi0CWVjYy0yNTUxOYiUBBMWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID ++AQIeAwIXgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJEMx4YniYGwco ++lFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAcYMsXAY73iZpPsEcAQDmKbH21LkwoClU ++9RrUJSYZnMla/pQdgOxd7/PjRCpbCg== ++=m1Zp ++-----END PGP PUBLIC KEY BLOCK----- +diff --git a/comm/third_party/rnp/src/tests/data/test_stream_armor/long_b64_trailer.b64 b/third_party/rnp/src/tests/data/test_stream_armor/long_b64_trailer.b/comm64 +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_stream_armor/long_b64_trailer.b64 +@@ -0,0 +1,5 @@ ++mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo ++ugCpCfi0CWVjYy0yNTUxOYiUBBMWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID ++AQIeAwIXgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJEMx4YniYGwco ++lFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAcYMsXAY73iZpPsEcAQDmKbH21LkwoClU ++9RrUJSYZnMla/pQdgOxd7/PjRCpbCg=== +diff --git a/comm/third_party/rnp/src/tests/data/test_stream_armor/wrong_b64_trailer.asc b/third_party/rnp/src/tests/data/test_stream_armor/wrong_b/comm64_trailer.asc +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_stream_armor/wrong_b64_trailer.asc +@@ -0,0 +1,9 @@ ++-----BEGIN PGP PUBLIC KEY BLOCK----- ++ ++mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo ++ugCpCfi0CWVjYy0yNTUxOYiUBBMWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID ++AQIeAwIXgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJEMx4YniYGwco ++lFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAcYMsXAY73iZpPsEcAQDmKbH21LkwoClU ++9RrUJSYZnMla/pQdgOxd7/PjRCpbCg=z ++=miZp ++-----END PGP PUBLIC KEY BLOCK----- +diff --git a/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub-2.b64 b/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub-2.b/comm64 +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub-2.b64 +@@ -0,0 +1,7 @@ ++mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6 ++d+O201bdKhdowbielOougCpCfi0CWVjYy0yNTUxOYiUBB ++MWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwI ++XgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJ ++EMx4YniYGwcolFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAc ++YMsXAY73iZpPsEcAQDmKbH21LkwoClU9RrUJSYZnMla/p ++QdgOxd7/PjRCpbCg== +\ No newline at end of file +diff --git a/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub-3.b64 b/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub-3.b/comm64 +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub-3.b64 +@@ -0,0 +1,9 @@ ++mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6 ++d+O201bdKhdowbielOougCpCfi0CWVjYy0yNTUxOYiUBB ++MWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwI ++XgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJ ++EMx4YniYGwcolFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAc ++YMsXAY73iZpPsEcAQDmKbH21LkwoClU9RrUJSYZnMla/p ++QdgOxd7/PjRCpbCg== ++ ++ +diff --git a/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub-4.b64 b/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub-4.b/comm64 +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub-4.b64 +@@ -0,0 +1,7 @@ ++mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6 ++d+O201bdKhdowbielOougCpCfi0CWVjYy0yNTUxOYiUBB ++MWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwI ++XgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJ ++EMx4YniYGwcolFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAc ++YMsXAY73iZpPsEcAQDmKbH21LkwoClU9RrUJSYZnMla/p ++QdgOxd7/PjRCpbCg==zz +\ No newline at end of file +diff --git a/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub.b64 b/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub.b/comm64 +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-25519-pub.b64 +@@ -0,0 +1,7 @@ ++mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6 ++d+O201bdKhdowbielOougCpCfi0CWVjYy0yNTUxOYiUBB ++MWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwI ++XgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJ ++EMx4YniYGwcolFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAc ++YMsXAY73iZpPsEcAQDmKbH21LkwoClU9RrUJSYZnMla/p ++QdgOxd7/PjRCpbCg== +diff --git a/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-p256k1-pub-2.b64 b/third_party/rnp/src/tests/data/test_stream_key_load/ecc-p256k1-pub-2.b/comm64 +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-p256k1-pub-2.b64 +@@ -0,0 +1,13 @@ ++mE8EWsOE8xMFK4EEAAoCAwQVHqFZWqedXUIkNFs82PsQB3bs ++CDhrL/73xZca3+vokB4T7jHcACThuMZYuUqUo9NzNTJioluO ++vZG+UdYXPdfdtAplY2MtcDI1NmsxiJQEExMIADwCGwMFCwkI ++BwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEgfdytX1Ov+cA ++CmYjPqW7b5aSwaAFAlxVsQ0ACgkQPqW7b5aSwaD2tQD/R4d1 ++5NBuSJ6IB1brH0E9nEWkqo892PaAY5akdCO/i9EBAMsjE5NP ++xBnCs03c+VHFU200k27ixdrWpUa+HZEIA5wSuFMEWsOE8xIF ++K4EEAAoCAwSUWwe7CaaOYRANiKet2evLiOumefIHuvRpyOSK ++hyRdclIWpBUCAWEnmalkEL/8cEM5fjtILtCOKXqCOBsPv45H ++AwEIB4h4BBgTCAAgAhsMFiEEgfdytX1Ov+cACmYjPqW7b5aS ++waAFAlxVsRUACgkQPqW7b5aSwaCETgD/YXzCMYMbPGAU2oTi ++tjAno8hDWmgTeaFWeCmqf6l9mP8BAKvpewWeFGZfWGAQcWPi ++E+jv7vadvEt1yMA8rmT041F5 +diff --git a/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-p256k1-pub.b64 b/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-p256k1-pub.b64 +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/data/test_stream_key_load/ecc-p256k1-pub.b64 +@@ -0,0 +1,1 @@ ++mE8EWsOE8xMFK4EEAAoCAwQVHqFZWqedXUIkNFs82PsQB3bsCDhrL/73xZca3+vokB4T7jHcACThuMZYuUqUo9NzNTJioluOvZG+UdYXPdfdtAplY2MtcDI1NmsxiJQEExMIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEgfdytX1Ov+cACmYjPqW7b5aSwaAFAlxVsQ0ACgkQPqW7b5aSwaD2tQD/R4d15NBuSJ6IB1brH0E9nEWkqo892PaAY5akdCO/i9EBAMsjE5NPxBnCs03c+VHFU200k27ixdrWpUa+HZEIA5wSuFMEWsOE8xIFK4EEAAoCAwSUWwe7CaaOYRANiKet2evLiOumefIHuvRpyOSKhyRdclIWpBUCAWEnmalkEL/8cEM5fjtILtCOKXqCOBsPv45HAwEIB4h4BBgTCAAgAhsMFiEEgfdytX1Ov+cACmYjPqW7b5aSwaAFAlxVsRUACgkQPqW7b5aSwaCETgD/YXzCMYMbPGAU2oTitjAno8hDWmgTeaFWeCmqf6l9mP8BAKvpewWeFGZfWGAQcWPiE+jv7vadvEt1yMA8rmT041F5 +diff --git a/comm/third_party/rnp/src/tests/ffi-enc.cpp b/comm/third_party/rnp/src/tests/ffi-enc.cpp +--- a/comm/third_party/rnp/src/tests/ffi-enc.cpp ++++ b/comm/third_party/rnp/src/tests/ffi-enc.cpp +@@ -965,5 +965,68 @@ TEST_F(rnp_tests, test_ffi_decrypt_small + assert_rnp_failure(rnp_decrypt(ffi, input, output)); + assert_rnp_success(rnp_input_destroy(input)); + assert_rnp_success(rnp_output_destroy(output)); + rnp_ffi_destroy(ffi); + } ++ ++TEST_F(rnp_tests, test_ffi_encrypt_no_wrap) ++{ ++ rnp_ffi_t ffi = NULL; ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_true( ++ load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg")); ++ ++ rnp_input_t input = NULL; ++ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed")); ++ rnp_output_t output = NULL; ++ assert_rnp_success(rnp_output_to_path(&output, "encrypted")); ++ rnp_op_encrypt_t op = NULL; ++ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output)); ++ rnp_key_handle_t key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key)); ++ assert_rnp_success(rnp_op_encrypt_add_recipient(op, key)); ++ rnp_key_handle_destroy(key); ++ /* set nowrap flag */ ++ assert_rnp_failure(rnp_op_encrypt_set_flags(NULL, RNP_ENCRYPT_NOWRAP)); ++ assert_rnp_failure(rnp_op_encrypt_set_flags(op, 17)); ++ assert_rnp_success(rnp_op_encrypt_set_flags(op, RNP_ENCRYPT_NOWRAP)); ++ assert_rnp_success(rnp_op_encrypt_execute(op)); ++ ++ assert_rnp_success(rnp_input_destroy(input)); ++ assert_rnp_success(rnp_output_destroy(output)); ++ assert_rnp_success(rnp_op_encrypt_destroy(op)); ++ ++ /* decrypt via rnp_decrypt() */ ++ assert_rnp_success(rnp_input_from_path(&input, "encrypted")); ++ assert_rnp_success(rnp_output_to_path(&output, "decrypted")); ++ assert_rnp_success( ++ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password")); ++ assert_rnp_success(rnp_decrypt(ffi, input, output)); ++ rnp_input_destroy(input); ++ rnp_output_destroy(output); ++ assert_string_equal(file_to_str("decrypted").c_str(), ++ file_to_str("data/test_messages/message.txt").c_str()); ++ unlink("decrypted"); ++ ++ /* decrypt and verify signatures */ ++ rnp_op_verify_t verify; ++ assert_rnp_success(rnp_input_from_path(&input, "encrypted")); ++ assert_rnp_success(rnp_output_to_path(&output, "verified")); ++ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output)); ++ assert_rnp_success(rnp_op_verify_execute(verify)); ++ /* check signature */ ++ rnp_op_verify_signature_t sig; ++ size_t sig_count; ++ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count)); ++ assert_int_equal(sig_count, 1); ++ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig)); ++ assert_rnp_success(rnp_op_verify_signature_get_status(sig)); ++ assert_rnp_success(rnp_input_destroy(input)); ++ assert_rnp_success(rnp_output_destroy(output)); ++ rnp_op_verify_destroy(verify); ++ assert_string_equal(file_to_str("verified").c_str(), ++ file_to_str("data/test_messages/message.txt").c_str()); ++ unlink("verified"); ++ ++ // final cleanup ++ rnp_ffi_destroy(ffi); ++} +diff --git a/comm/third_party/rnp/src/tests/ffi-key-prop.cpp b/comm/third_party/rnp/src/tests/ffi-key-prop.cpp +--- a/comm/third_party/rnp/src/tests/ffi-key-prop.cpp ++++ b/comm/third_party/rnp/src/tests/ffi-key-prop.cpp +@@ -26,10 +26,13 @@ + + #include + #include + #include "rnp_tests.h" + #include "support.h" ++#include ++#include "pgp-key.h" ++#include "ffi-priv-types.h" + + TEST_F(rnp_tests, test_ffi_key_set_expiry_multiple_uids) + { + rnp_ffi_t ffi = NULL; + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); +@@ -44,20 +47,31 @@ TEST_F(rnp_tests, test_ffi_key_set_expir + assert_rnp_success(rnp_key_get_uid_count(key, &count)); + assert_int_equal(count, 3); + uint32_t expiry = 10; + assert_rnp_success(rnp_key_get_expiration(key, &expiry)); + assert_int_equal(expiry, 0); ++ bool expired = true; ++ assert_rnp_failure(rnp_key_is_expired(key, NULL)); ++ assert_rnp_failure(rnp_key_is_expired(NULL, &expired)); ++ rnp_key_handle_t bkey = bogus_key_handle(ffi); ++ assert_non_null(bkey); ++ assert_rnp_failure(rnp_key_is_expired(bkey, &expired)); ++ rnp_key_handle_destroy(bkey); ++ assert_rnp_success(rnp_key_is_expired(key, &expired)); ++ assert_false(expired); + assert_true(check_uid_valid(key, 0, true)); + assert_true(check_uid_valid(key, 1, true)); + assert_true(check_uid_valid(key, 2, true)); + assert_true(check_uid_primary(key, 0, false)); + assert_true(check_uid_primary(key, 1, false)); + assert_true(check_uid_primary(key, 2, false)); + /* set expiration time to minimum value so key is expired now, but uids are still valid */ + assert_rnp_success(rnp_key_set_expiration(key, 1)); + assert_rnp_success(rnp_key_get_expiration(key, &expiry)); + assert_int_equal(expiry, 1); ++ assert_rnp_success(rnp_key_is_expired(key, &expired)); ++ assert_true(expired); + bool valid = true; + assert_rnp_success(rnp_key_is_valid(key, &valid)); + assert_false(valid); + assert_true(check_uid_valid(key, 0, true)); + assert_true(check_uid_valid(key, 1, true)); +@@ -67,10 +81,12 @@ TEST_F(rnp_tests, test_ffi_key_set_expir + reload_keyrings(&ffi); + assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); + assert_non_null(key); + assert_rnp_success(rnp_key_get_expiration(key, &expiry)); + assert_int_equal(expiry, 1); ++ assert_rnp_success(rnp_key_is_expired(key, &expired)); ++ assert_true(expired); + assert_true(check_uid_valid(key, 0, true)); + assert_true(check_uid_valid(key, 1, true)); + assert_true(check_uid_valid(key, 2, true)); + /* set expiration to maximum value */ + assert_rnp_success( +@@ -79,10 +95,12 @@ TEST_F(rnp_tests, test_ffi_key_set_expir + assert_rnp_success(rnp_key_get_expiration(key, &expiry)); + assert_int_equal(expiry, 0xFFFFFFFF); + valid = false; + assert_rnp_success(rnp_key_is_valid(key, &valid)); + assert_true(valid); ++ assert_rnp_success(rnp_key_is_expired(key, &expired)); ++ assert_false(expired); + assert_true(check_uid_valid(key, 0, true)); + assert_true(check_uid_valid(key, 1, true)); + assert_true(check_uid_valid(key, 2, true)); + rnp_key_handle_destroy(key); + /* reload and make sure changes are saved */ +@@ -101,20 +119,24 @@ TEST_F(rnp_tests, test_ffi_key_set_expir + import_all_keys(ffi, "data/test_key_edge_cases/alice-3-uids-primary-expiring.pgp")); + assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); + expiry = 0; + assert_rnp_success(rnp_key_get_expiration(key, &expiry)); + assert_int_equal(expiry, 674700647); ++ assert_rnp_success(rnp_key_is_expired(key, &expired)); ++ assert_false(expired); + assert_true(check_uid_valid(key, 0, true)); + assert_true(check_uid_valid(key, 1, true)); + assert_true(check_uid_valid(key, 2, true)); + assert_true(check_uid_primary(key, 0, true)); + assert_true(check_uid_primary(key, 1, false)); + assert_true(check_uid_primary(key, 2, false)); + assert_rnp_success(rnp_key_unlock(key, "password")); + assert_rnp_success(rnp_key_set_expiration(key, 0)); + assert_rnp_success(rnp_key_get_expiration(key, &expiry)); + assert_int_equal(expiry, 0); ++ assert_rnp_success(rnp_key_is_expired(key, &expired)); ++ assert_false(expired); + valid = false; + assert_rnp_success(rnp_key_is_valid(key, &valid)); + assert_true(valid); + assert_true(check_uid_valid(key, 0, true)); + assert_true(check_uid_valid(key, 1, true)); +@@ -277,5 +299,1076 @@ TEST_F(rnp_tests, test_ffi_key_25519_twe + assert_rnp_success(rnp_key_25519_bits_tweaked(sub, &tweaked)); + assert_true(tweaked); + rnp_key_handle_destroy(sub); + rnp_ffi_destroy(ffi); + } ++ ++TEST_F(rnp_tests, test_ffi_key_revoke) ++{ ++ rnp_ffi_t ffi = NULL; ++ ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_true(import_pub_keys(ffi, "data/test_key_validity/alice-sub-pub.pgp")); ++ rnp_key_handle_t key_handle = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key_handle)); ++ /* check for failure with wrong parameters */ ++ assert_rnp_failure(rnp_key_revoke(NULL, 0, "SHA256", "superseded", "test key revocation")); ++ assert_rnp_failure(rnp_key_revoke(key_handle, 0, "SHA256", NULL, NULL)); ++ assert_rnp_failure(rnp_key_revoke(key_handle, 0x17, "SHA256", NULL, NULL)); ++ assert_rnp_failure(rnp_key_revoke(key_handle, 0, "Wrong hash", NULL, NULL)); ++ assert_rnp_failure(rnp_key_revoke(key_handle, 0, "SHA256", "Wrong reason code", NULL)); ++ /* attempt to revoke key without the secret */ ++ assert_rnp_failure(rnp_key_revoke(key_handle, 0, "SHA256", "retired", "Custom reason")); ++ assert_rnp_success(rnp_key_handle_destroy(key_handle)); ++ /* attempt to revoke subkey without the secret */ ++ rnp_key_handle_t sub_handle = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub_handle)); ++ assert_rnp_failure(rnp_key_revoke(sub_handle, 0, "SHA256", "retired", "Custom reason")); ++ assert_rnp_success(rnp_key_handle_destroy(sub_handle)); ++ /* load secret key */ ++ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sub-sec.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key_handle)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub_handle)); ++ /* wrong password - must fail */ ++ assert_rnp_success( ++ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "wrong")); ++ assert_rnp_failure(rnp_key_revoke(key_handle, 0, "SHA256", "superseded", NULL)); ++ assert_rnp_failure(rnp_key_revoke(sub_handle, 0, "SHA256", "superseded", NULL)); ++ /* unlocked key - must succeed */ ++ bool revoked = false; ++ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked)); ++ assert_false(revoked); ++ assert_rnp_success(rnp_key_unlock(key_handle, "password")); ++ assert_rnp_success(rnp_key_revoke(key_handle, 0, "SHA256", NULL, NULL)); ++ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked)); ++ assert_true(revoked); ++ /* subkey */ ++ assert_rnp_success(rnp_key_is_revoked(sub_handle, &revoked)); ++ assert_false(revoked); ++ bool locked = true; ++ assert_rnp_success(rnp_key_is_locked(key_handle, &locked)); ++ assert_false(locked); ++ assert_rnp_success(rnp_key_revoke(sub_handle, 0, "SHA256", NULL, "subkey revoked")); ++ assert_rnp_success(rnp_key_is_revoked(sub_handle, &revoked)); ++ assert_true(revoked); ++ assert_rnp_success(rnp_key_lock(key_handle)); ++ assert_rnp_success(rnp_key_handle_destroy(key_handle)); ++ assert_rnp_success(rnp_key_handle_destroy(sub_handle)); ++ /* correct password provider - must succeed */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET | RNP_KEY_UNLOAD_PUBLIC)); ++ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sub-sec.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key_handle)); ++ assert_rnp_success( ++ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password")); ++ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked)); ++ assert_false(revoked); ++ assert_rnp_success( ++ rnp_key_revoke(key_handle, 0, "SHA256", "superseded", "test key revocation")); ++ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked)); ++ assert_true(revoked); ++ /* make sure FFI locks key back */ ++ assert_rnp_success(rnp_key_is_locked(key_handle, &locked)); ++ assert_true(locked); ++ assert_rnp_success(rnp_key_handle_destroy(key_handle)); ++ /* repeat for subkey */ ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub_handle)); ++ assert_rnp_success(rnp_key_is_revoked(sub_handle, &revoked)); ++ assert_false(revoked); ++ assert_rnp_success(rnp_key_revoke(sub_handle, 0, "SHA256", "no", "test sub revocation")); ++ assert_rnp_success(rnp_key_is_revoked(sub_handle, &revoked)); ++ assert_true(revoked); ++ assert_rnp_success(rnp_key_handle_destroy(sub_handle)); ++ assert_rnp_success(rnp_ffi_destroy(ffi)); ++} ++ ++TEST_F(rnp_tests, test_ffi_key_set_expiry) ++{ ++ rnp_ffi_t ffi = NULL; ++ rnp_input_t input = NULL; ++ ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_true(import_pub_keys(ffi, "data/test_key_validity/alice-sub-pub.pgp")); ++ ++ /* check edge cases */ ++ assert_rnp_failure(rnp_key_set_expiration(NULL, 0)); ++ rnp_key_handle_t key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ /* cannot set key expiration with public key only */ ++ assert_rnp_failure(rnp_key_set_expiration(key, 1000)); ++ rnp_key_handle_t sub = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub)); ++ assert_rnp_failure(rnp_key_set_expiration(sub, 1000)); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ ++ /* load secret key */ ++ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sub-sec.pgp")); ++ uint32_t expiry = 0; ++ const uint32_t new_expiry = 10 * 365 * 24 * 60 * 60; ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ expiry = 255; ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, 0); ++ assert_rnp_success(rnp_key_set_expiration(key, 0)); ++ /* will fail on locked key */ ++ assert_rnp_failure(rnp_key_set_expiration(key, new_expiry)); ++ assert_rnp_success(rnp_key_unlock(key, "password")); ++ assert_rnp_success(rnp_key_set_expiration(key, new_expiry)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, new_expiry); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub)); ++ /* will succeed on locked subkey since it is not signing one */ ++ assert_rnp_success(rnp_key_set_expiration(sub, 0)); ++ assert_rnp_success(rnp_key_set_expiration(sub, new_expiry * 2)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, new_expiry * 2); ++ /* make sure new expiration times are properly saved */ ++ rnp_output_t keymem = NULL; ++ rnp_output_t seckeymem = NULL; ++ assert_rnp_success(rnp_output_to_memory(&keymem, 0)); ++ assert_rnp_success( ++ rnp_key_export(key, keymem, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS)); ++ assert_rnp_success(rnp_output_to_memory(&seckeymem, 0)); ++ assert_rnp_success( ++ rnp_key_export(key, seckeymem, RNP_KEY_EXPORT_SECRET | RNP_KEY_EXPORT_SUBKEYS)); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ uint8_t *keybuf = NULL; ++ size_t keylen = 0; ++ assert_rnp_success(rnp_output_memory_get_buf(keymem, &keybuf, &keylen, false)); ++ /* load public key */ ++ assert_true(import_pub_keys(ffi, keybuf, keylen)); ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, new_expiry); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, new_expiry * 2); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ /* now load exported secret key */ ++ assert_rnp_success(rnp_output_memory_get_buf(seckeymem, &keybuf, &keylen, false)); ++ assert_true(import_sec_keys(ffi, keybuf, keylen)); ++ assert_rnp_success(rnp_output_destroy(seckeymem)); ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, new_expiry); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, new_expiry * 2); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ /* now unset expiration time back, first loading the public key back */ ++ assert_rnp_success(rnp_output_memory_get_buf(keymem, &keybuf, &keylen, false)); ++ assert_true(import_pub_keys(ffi, keybuf, keylen)); ++ assert_rnp_success(rnp_output_destroy(keymem)); ++ /* set primary key expiration */ ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ assert_rnp_success(rnp_key_unlock(key, "password")); ++ assert_rnp_success(rnp_key_set_expiration(key, 0)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, 0); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub)); ++ assert_rnp_success(rnp_key_set_expiration(sub, 0)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, 0); ++ /* let's export them and reload */ ++ assert_rnp_success(rnp_output_to_memory(&keymem, 0)); ++ assert_rnp_success( ++ rnp_key_export(key, keymem, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS)); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_rnp_success(rnp_output_memory_get_buf(keymem, &keybuf, &keylen, false)); ++ assert_true(import_pub_keys(ffi, keybuf, keylen)); ++ assert_rnp_success(rnp_output_destroy(keymem)); ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, 0); ++ bool expired = true; ++ assert_rnp_success(rnp_key_is_expired(key, &expired)); ++ assert_false(expired); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, 0); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ ++ /* now try the sign-able subkey */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_true(import_pub_keys(ffi, "data/test_key_validity/alice-sign-sub-pub.pgp")); ++ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sign-sub-sec.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "22F3A217C0E439CB", &sub)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, 0); ++ assert_rnp_failure(rnp_key_set_expiration(sub, new_expiry)); ++ /* now unlock only primary key - should fail */ ++ assert_rnp_success(rnp_key_unlock(key, "password")); ++ assert_rnp_failure(rnp_key_set_expiration(sub, new_expiry)); ++ /* unlock subkey */ ++ assert_rnp_success(rnp_key_unlock(sub, "password")); ++ assert_rnp_success(rnp_key_set_expiration(sub, new_expiry)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, new_expiry); ++ assert_rnp_success(rnp_output_to_memory(&keymem, 0)); ++ assert_rnp_success( ++ rnp_key_export(key, keymem, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS)); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_rnp_success(rnp_output_memory_get_buf(keymem, &keybuf, &keylen, false)); ++ assert_true(import_pub_keys(ffi, keybuf, keylen)); ++ assert_rnp_success(rnp_output_destroy(keymem)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "22F3A217C0E439CB", &sub)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, new_expiry); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ ++ /* check whether we can change expiration for already expired key */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_true(import_pub_keys(ffi, "data/test_key_validity/alice-sign-sub-pub.pgp")); ++ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sign-sub-sec.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "22F3A217C0E439CB", &sub)); ++ assert_rnp_success(rnp_key_unlock(key, "password")); ++ assert_rnp_success(rnp_key_unlock(sub, "password")); ++ assert_rnp_success(rnp_key_set_expiration(key, 1)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, 1); ++ assert_rnp_success(rnp_key_is_expired(key, &expired)); ++ assert_true(expired); ++ ++ /* key is invalid since it is expired */ ++ assert_false(key->pub->valid()); ++ bool valid = true; ++ assert_rnp_success(rnp_key_is_valid(key, &valid)); ++ assert_false(valid); ++ uint32_t till = 0; ++ assert_rnp_success(rnp_key_valid_till(key, &till)); ++ assert_int_equal(till, 1577369391 + 1); ++ uint64_t till64 = 0; ++ assert_rnp_success(rnp_key_valid_till64(key, &till64)); ++ assert_int_equal(till64, 1577369391 + 1); ++ assert_rnp_success(rnp_key_set_expiration(sub, 1)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, 1); ++ assert_false(sub->pub->valid()); ++ valid = true; ++ assert_rnp_success(rnp_key_is_valid(sub, &valid)); ++ assert_false(valid); ++ till = 1; ++ assert_rnp_success(rnp_key_valid_till(sub, &till)); ++ assert_int_equal(till, 1577369391 + 1); ++ assert_rnp_success(rnp_key_valid_till64(sub, &till64)); ++ assert_int_equal(till64, 1577369391 + 1); ++ assert_rnp_success(rnp_key_set_expiration(key, 0)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, 0); ++ assert_true(key->pub->valid()); ++ assert_rnp_success(rnp_key_is_valid(key, &valid)); ++ assert_true(valid); ++ assert_rnp_success(rnp_key_valid_till(key, &till)); ++ assert_int_equal(till, 0xffffffff); ++ assert_rnp_success(rnp_key_valid_till64(key, &till64)); ++ assert_int_equal(till64, UINT64_MAX); ++ assert_rnp_success(rnp_key_set_expiration(sub, 0)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, 0); ++ assert_true(sub->pub->valid()); ++ valid = false; ++ assert_rnp_success(rnp_key_is_valid(sub, &valid)); ++ assert_true(valid); ++ till = 0; ++ assert_rnp_success(rnp_key_valid_till(sub, &till)); ++ assert_int_equal(till, 0xffffffff); ++ till64 = 0; ++ assert_rnp_success(rnp_key_valid_till64(sub, &till64)); ++ assert_int_equal(till64, UINT64_MAX); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ ++ /* check whether we can change expiration with password provider/locked key */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_true(import_pub_keys(ffi, "data/test_key_validity/alice-sign-sub-pub.pgp")); ++ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sign-sub-sec.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "22F3A217C0E439CB", &sub)); ++ ++ assert_rnp_success( ++ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "wrong")); ++ assert_rnp_failure(rnp_key_set_expiration(key, 1)); ++ expiry = 255; ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, 0); ++ assert_rnp_failure(rnp_key_set_expiration(sub, 1)); ++ expiry = 255; ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, 0); ++ ++ bool locked = true; ++ assert_rnp_success(rnp_key_is_locked(key, &locked)); ++ assert_true(locked); ++ locked = false; ++ assert_rnp_success(rnp_key_is_locked(sub, &locked)); ++ assert_true(locked); ++ assert_rnp_success( ++ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password")); ++ uint32_t creation = 0; ++ assert_rnp_success(rnp_key_get_creation(key, &creation)); ++ creation = time(NULL) - creation; ++ assert_rnp_success(rnp_key_set_expiration(key, creation + 8)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, creation + 8); ++ locked = false; ++ assert_rnp_success(rnp_key_is_locked(key, &locked)); ++ assert_true(locked); ++ assert_rnp_success(rnp_key_get_creation(sub, &creation)); ++ creation = time(NULL) - creation; ++ assert_rnp_success(rnp_key_set_expiration(sub, creation + 3)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, creation + 3); ++ locked = false; ++ assert_rnp_success(rnp_key_is_locked(sub, &locked)); ++ assert_true(locked); ++ locked = false; ++ assert_rnp_success(rnp_key_is_locked(key, &locked)); ++ assert_true(locked); ++ ++ /* now change just subkey's expiration - should also work */ ++ valid = false; ++ assert_rnp_success(rnp_key_is_valid(key, &valid)); ++ assert_true(valid); ++ assert_rnp_success(rnp_key_set_expiration(sub, 4)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, 4); ++ assert_rnp_success(rnp_key_is_expired(sub, &expired)); ++ assert_true(expired); ++ locked = false; ++ assert_rnp_success(rnp_key_is_locked(sub, &locked)); ++ assert_true(locked); ++ locked = false; ++ assert_rnp_success(rnp_key_is_locked(key, &locked)); ++ assert_true(locked); ++ ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ ++ /* now try to update already expired key and subkey */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_true(import_pub_keys(ffi, "data/test_key_validity/alice-sign-sub-exp-pub.asc")); ++ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sign-sub-exp-sec.asc")); ++ /* Alice key is searchable by userid since self-sig is not expired, and it just marks key ++ * as expired */ ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "22F3A217C0E439CB", &sub)); ++ assert_rnp_success(rnp_key_is_valid(key, &valid)); ++ /* key is not valid since expired */ ++ assert_false(valid); ++ assert_rnp_success(rnp_key_valid_till(key, &till)); ++ assert_int_equal(till, 1577369391 + 16324055); ++ assert_rnp_success(rnp_key_valid_till64(key, &till64)); ++ assert_int_equal(till64, 1577369391 + 16324055); ++ assert_false(key->pub->valid()); ++ /* secret key part is also not valid till new sig is added */ ++ assert_false(key->sec->valid()); ++ assert_rnp_success(rnp_key_is_valid(sub, &valid)); ++ assert_false(valid); ++ assert_rnp_success(rnp_key_valid_till(sub, &till)); ++ /* subkey valid no longer then the primary key */ ++ assert_int_equal(till, 1577369391 + 16324055); ++ assert_rnp_success(rnp_key_valid_till64(sub, &till64)); ++ assert_int_equal(till64, 1577369391 + 16324055); ++ assert_false(sub->pub->valid()); ++ assert_false(sub->sec->valid()); ++ creation = 0; ++ uint32_t validity = 2 * 30 * 24 * 60 * 60; // 2 monthes ++ assert_rnp_success(rnp_key_get_creation(key, &creation)); ++ uint32_t keytill = creation + validity; ++ creation = time(NULL) - creation; ++ keytill += creation; ++ assert_rnp_success(rnp_key_set_expiration(key, creation + validity)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, creation + validity); ++ assert_rnp_success(rnp_key_get_creation(sub, &creation)); ++ /* use smaller validity for the subkey */ ++ validity = validity / 2; ++ uint32_t subtill = creation + validity; ++ creation = time(NULL) - creation; ++ subtill += creation; ++ assert_rnp_success(rnp_key_set_expiration(sub, creation + validity)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, creation + validity); ++ assert_rnp_success(rnp_key_is_valid(key, &valid)); ++ assert_true(valid); ++ assert_rnp_success(rnp_key_valid_till(key, &till)); ++ assert_int_equal(till, keytill); ++ assert_rnp_success(rnp_key_valid_till64(key, &till64)); ++ assert_int_equal(till64, keytill); ++ assert_true(key->pub->valid()); ++ assert_true(key->sec->valid()); ++ assert_rnp_success(rnp_key_is_valid(sub, &valid)); ++ assert_true(valid); ++ assert_rnp_success(rnp_key_valid_till(sub, &till)); ++ assert_int_equal(till, subtill); ++ assert_rnp_success(rnp_key_valid_till64(sub, &till64)); ++ assert_int_equal(till64, subtill); ++ assert_true(sub->pub->valid()); ++ assert_true(sub->sec->valid()); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ ++ /* update expiration time when only secret key is available */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC)); ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "22F3A217C0E439CB", &sub)); ++ validity = 30 * 24 * 60 * 60; // 1 month ++ assert_rnp_success(rnp_key_get_creation(key, &creation)); ++ creation = time(NULL) - creation; ++ assert_rnp_success(rnp_key_set_expiration(key, creation + validity)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, creation + validity); ++ assert_rnp_success(rnp_key_get_creation(sub, &creation)); ++ creation = time(NULL) - creation; ++ assert_rnp_success(rnp_key_set_expiration(sub, creation + validity)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, creation + validity); ++ /* public key is not available - bad parameters */ ++ assert_int_equal(rnp_key_is_valid(key, &valid), RNP_ERROR_BAD_PARAMETERS); ++ assert_int_equal(rnp_key_valid_till(key, &till), RNP_ERROR_BAD_PARAMETERS); ++ assert_int_equal(rnp_key_valid_till64(key, &till64), RNP_ERROR_BAD_PARAMETERS); ++ assert_null(key->pub); ++ assert_true(key->sec->valid()); ++ assert_int_equal(rnp_key_is_valid(sub, &valid), RNP_ERROR_BAD_PARAMETERS); ++ assert_int_equal(rnp_key_valid_till(sub, &till), RNP_ERROR_BAD_PARAMETERS); ++ assert_int_equal(rnp_key_valid_till64(sub, &till64), RNP_ERROR_BAD_PARAMETERS); ++ assert_null(sub->pub); ++ assert_true(sub->sec->valid()); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ assert_rnp_success(rnp_ffi_destroy(ffi)); ++ ++ /* check whether things work for G10 keyring */ ++ assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10")); ++ assert_rnp_success( ++ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password")); ++ assert_true(load_keys_kbx_g10( ++ ffi, "data/keyrings/3/pubring.kbx", "data/keyrings/3/private-keys-v1.d")); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "4BE147BB22DF1E60", &key)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "A49BAE05C16E8BC8", &sub)); ++ assert_rnp_success(rnp_key_get_creation(key, &creation)); ++ keytill = creation + validity; ++ creation = time(NULL) - creation; ++ keytill += creation; ++ assert_rnp_success(rnp_key_set_expiration(key, creation + validity)); ++ expiry = 255; ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, creation + validity); ++ size_t key_expiry = expiry; ++ assert_rnp_success(rnp_key_get_creation(sub, &creation)); ++ creation = time(NULL) - creation; ++ assert_rnp_success(rnp_key_set_expiration(sub, creation + validity)); ++ expiry = 255; ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, creation + validity); ++ size_t sub_expiry = expiry; ++ assert_rnp_success(rnp_key_is_valid(key, &valid)); ++ assert_true(valid); ++ assert_rnp_success(rnp_key_valid_till(key, &till)); ++ assert_int_equal(till, keytill); ++ assert_rnp_success(rnp_key_valid_till64(key, &till64)); ++ assert_int_equal(till64, keytill); ++ assert_true(key->pub->valid()); ++ assert_true(key->sec->valid()); ++ assert_rnp_success(rnp_key_is_valid(sub, &valid)); ++ assert_true(valid); ++ assert_rnp_success(rnp_key_valid_till(sub, &till)); ++ assert_int_equal(till, keytill); ++ assert_rnp_success(rnp_key_valid_till64(sub, &till64)); ++ assert_int_equal(till64, keytill); ++ assert_true(sub->pub->valid()); ++ assert_true(sub->sec->valid()); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ ++ /* save keyring to KBX and reload it: fails now */ ++ rnp_output_t output = NULL; ++ assert_rnp_success(rnp_output_to_path(&output, "pubring.kbx")); ++ assert_rnp_success(rnp_save_keys(ffi, "KBX", output, RNP_LOAD_SAVE_PUBLIC_KEYS)); ++ assert_rnp_success(rnp_output_destroy(output)); ++ assert_rnp_success(rnp_ffi_destroy(ffi)); ++ assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10")); ++ assert_rnp_success(rnp_input_from_path(&input, "pubring.kbx")); ++ /* Saving to KBX doesn't work well, or was broken at some point. */ ++ assert_rnp_failure(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL)); ++ assert_rnp_success(rnp_input_destroy(input)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "4BE147BB22DF1E60", &key)); ++ assert_null(key); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "A49BAE05C16E8BC8", &sub)); ++ assert_null(sub); ++ expiry = 255; ++ assert_rnp_failure(rnp_key_get_expiration(key, &expiry)); ++ assert_int_not_equal(expiry, key_expiry); ++ expiry = 255; ++ assert_rnp_failure(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_not_equal(expiry, sub_expiry); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ assert_int_equal(rnp_unlink("pubring.kbx"), 0); ++ assert_rnp_success(rnp_ffi_destroy(ffi)); ++ ++ /* load G10/KBX and unload public keys - must succeed */ ++ assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10")); ++ assert_rnp_success( ++ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password")); ++ assert_true(load_keys_kbx_g10( ++ ffi, "data/keyrings/3/pubring.kbx", "data/keyrings/3/private-keys-v1.d")); ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "4BE147BB22DF1E60", &key)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "A49BAE05C16E8BC8", &sub)); ++ assert_rnp_success(rnp_key_get_creation(key, &creation)); ++ creation = time(NULL) - creation; ++ assert_rnp_success(rnp_key_set_expiration(key, creation + validity)); ++ assert_rnp_success(rnp_key_get_expiration(key, &expiry)); ++ assert_int_equal(expiry, creation + validity); ++ key_expiry = expiry; ++ assert_rnp_success(rnp_key_get_creation(sub, &creation)); ++ creation = time(NULL) - creation; ++ assert_rnp_success(rnp_key_set_expiration(sub, creation + validity)); ++ assert_rnp_success(rnp_key_get_expiration(sub, &expiry)); ++ assert_int_equal(expiry, creation + validity); ++ sub_expiry = expiry; ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(sub)); ++ ++ // TODO: check expiration date in direct-key signature, check without ++ // self-signature/binding signature. ++ ++ assert_rnp_success(rnp_ffi_destroy(ffi)); ++} ++ ++TEST_F(rnp_tests, test_ffi_key_get_protection_info) ++{ ++ rnp_ffi_t ffi = NULL; ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ ++ /* Edge cases - public key, NULL parameters, etc. */ ++ assert_true(import_pub_keys(ffi, "data/test_key_validity/alice-sub-pub.pgp")); ++ rnp_key_handle_t key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key)); ++ char *type = NULL; ++ assert_rnp_failure(rnp_key_get_protection_type(key, NULL)); ++ assert_rnp_failure(rnp_key_get_protection_type(NULL, &type)); ++ assert_rnp_failure(rnp_key_get_protection_type(key, &type)); ++ char *mode = NULL; ++ assert_rnp_failure(rnp_key_get_protection_mode(key, NULL)); ++ assert_rnp_failure(rnp_key_get_protection_mode(NULL, &mode)); ++ assert_rnp_failure(rnp_key_get_protection_mode(key, &mode)); ++ char *cipher = NULL; ++ assert_rnp_failure(rnp_key_get_protection_cipher(key, NULL)); ++ assert_rnp_failure(rnp_key_get_protection_cipher(NULL, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_cipher(key, &cipher)); ++ char *hash = NULL; ++ assert_rnp_failure(rnp_key_get_protection_hash(key, NULL)); ++ assert_rnp_failure(rnp_key_get_protection_hash(NULL, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_hash(key, &hash)); ++ size_t iterations = 0; ++ assert_rnp_failure(rnp_key_get_protection_iterations(key, NULL)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(NULL, &iterations)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(key, &iterations)); ++ rnp_key_handle_destroy(key); ++ ++ /* Encrypted secret key with subkeys */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC)); ++ assert_true(import_all_keys(ffi, "data/test_key_validity/alice-sub-sec.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key)); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "Encrypted-Hashed"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "CFB"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_success(rnp_key_get_protection_cipher(key, &cipher)); ++ assert_string_equal(cipher, "AES128"); ++ rnp_buffer_destroy(cipher); ++ assert_rnp_success(rnp_key_get_protection_hash(key, &hash)); ++ assert_string_equal(hash, "SHA1"); ++ rnp_buffer_destroy(hash); ++ assert_rnp_success(rnp_key_get_protection_iterations(key, &iterations)); ++ assert_int_equal(iterations, 22020096); ++ assert_rnp_success(rnp_key_unprotect(key, "password")); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "None"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "None"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(key, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(key, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(key, &iterations)); ++ rnp_key_handle_destroy(key); ++ ++ rnp_key_handle_t sub = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub)); ++ assert_rnp_success(rnp_key_get_protection_type(sub, &type)); ++ assert_string_equal(type, "Encrypted-Hashed"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(sub, &mode)); ++ assert_string_equal(mode, "CFB"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_success(rnp_key_get_protection_cipher(sub, &cipher)); ++ assert_string_equal(cipher, "AES128"); ++ rnp_buffer_destroy(cipher); ++ assert_rnp_success(rnp_key_get_protection_hash(sub, &hash)); ++ assert_string_equal(hash, "SHA1"); ++ rnp_buffer_destroy(hash); ++ assert_rnp_success(rnp_key_get_protection_iterations(sub, &iterations)); ++ assert_int_equal(iterations, 22020096); ++ assert_rnp_success(rnp_key_unprotect(sub, "password")); ++ assert_rnp_success(rnp_key_get_protection_type(sub, &type)); ++ assert_string_equal(type, "None"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(sub, &mode)); ++ assert_string_equal(mode, "None"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(sub, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(sub, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(sub, &iterations)); ++ rnp_key_handle_destroy(sub); ++ ++ /* v3 secret key */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_true(import_pub_keys(ffi, "data/keyrings/4/pubring.pgp")); ++ assert_true(import_sec_keys(ffi, "data/keyrings/4/secring.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7D0BC10E933404C9", &key)); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "Encrypted"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "CFB"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_success(rnp_key_get_protection_cipher(key, &cipher)); ++ assert_string_equal(cipher, "IDEA"); ++ rnp_buffer_destroy(cipher); ++ assert_rnp_success(rnp_key_get_protection_hash(key, &hash)); ++ assert_string_equal(hash, "MD5"); ++ rnp_buffer_destroy(hash); ++ assert_rnp_success(rnp_key_get_protection_iterations(key, &iterations)); ++ assert_int_equal(iterations, 1); ++ if (idea_enabled()) { ++ assert_rnp_success(rnp_key_unprotect(key, "password")); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "None"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "None"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(key, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(key, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(key, &iterations)); ++ } else { ++ assert_rnp_failure(rnp_key_unprotect(key, "password")); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "Encrypted"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "CFB"); ++ rnp_buffer_destroy(mode); ++ } ++ rnp_key_handle_destroy(key); ++ ++ /* G10 keys */ ++ rnp_ffi_destroy(ffi); ++ assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10")); ++ ++ assert_true(load_keys_kbx_g10( ++ ffi, "data/keyrings/3/pubring.kbx", "data/keyrings/3/private-keys-v1.d")); ++ ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "4BE147BB22DF1E60", &key)); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "Encrypted-Hashed"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "CBC"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_success(rnp_key_get_protection_cipher(key, &cipher)); ++ assert_string_equal(cipher, "AES128"); ++ rnp_buffer_destroy(cipher); ++ assert_rnp_success(rnp_key_get_protection_hash(key, &hash)); ++ assert_string_equal(hash, "SHA1"); ++ rnp_buffer_destroy(hash); ++ assert_rnp_success(rnp_key_get_protection_iterations(key, &iterations)); ++ assert_int_equal(iterations, 1024); ++ assert_rnp_success(rnp_key_unprotect(key, "password")); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "None"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "None"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(key, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(key, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(key, &iterations)); ++ rnp_key_handle_destroy(key); ++ ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "A49BAE05C16E8BC8", &sub)); ++ assert_rnp_success(rnp_key_get_protection_type(sub, &type)); ++ assert_string_equal(type, "Encrypted-Hashed"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(sub, &mode)); ++ assert_string_equal(mode, "CBC"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_success(rnp_key_get_protection_cipher(sub, &cipher)); ++ assert_string_equal(cipher, "AES128"); ++ rnp_buffer_destroy(cipher); ++ assert_rnp_success(rnp_key_get_protection_hash(sub, &hash)); ++ assert_string_equal(hash, "SHA1"); ++ rnp_buffer_destroy(hash); ++ assert_rnp_success(rnp_key_get_protection_iterations(sub, &iterations)); ++ assert_int_equal(iterations, 1024); ++ assert_rnp_success(rnp_key_unprotect(sub, "password")); ++ assert_rnp_success(rnp_key_get_protection_type(sub, &type)); ++ assert_string_equal(type, "None"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(sub, &mode)); ++ assert_string_equal(mode, "None"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(sub, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(sub, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(sub, &iterations)); ++ rnp_key_handle_destroy(sub); ++ ++ /* Secret subkeys, exported via gpg --export-secret-subkeys (no primary secret key data) */ ++ rnp_ffi_destroy(ffi); ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/alice-s2k-101-1-subs.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key)); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "GPG-None"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "Unknown"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(key, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(key, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(key, &iterations)); ++ rnp_key_handle_destroy(key); ++ ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub)); ++ assert_rnp_success(rnp_key_get_protection_type(sub, &type)); ++ assert_string_equal(type, "Encrypted-Hashed"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(sub, &mode)); ++ assert_string_equal(mode, "CFB"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_success(rnp_key_get_protection_cipher(sub, &cipher)); ++ assert_string_equal(cipher, "AES128"); ++ rnp_buffer_destroy(cipher); ++ assert_rnp_success(rnp_key_get_protection_hash(sub, &hash)); ++ assert_string_equal(hash, "SHA1"); ++ rnp_buffer_destroy(hash); ++ assert_rnp_success(rnp_key_get_protection_iterations(sub, &iterations)); ++ assert_int_equal(iterations, 30408704); ++ assert_rnp_success(rnp_key_unprotect(sub, "password")); ++ assert_rnp_success(rnp_key_get_protection_type(sub, &type)); ++ assert_string_equal(type, "None"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(sub, &mode)); ++ assert_string_equal(mode, "None"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(sub, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(sub, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(sub, &iterations)); ++ rnp_key_handle_destroy(sub); ++ ++ /* secret subkey is available, but primary key is stored on the smartcard by gpg */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/alice-s2k-101-2-card.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key)); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "GPG-Smartcard"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "Unknown"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(key, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(key, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(key, &iterations)); ++ rnp_key_handle_destroy(key); ++ ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub)); ++ assert_rnp_success(rnp_key_get_protection_type(sub, &type)); ++ assert_string_equal(type, "Encrypted-Hashed"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(sub, &mode)); ++ assert_string_equal(mode, "CFB"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_success(rnp_key_get_protection_cipher(sub, &cipher)); ++ assert_string_equal(cipher, "AES128"); ++ rnp_buffer_destroy(cipher); ++ assert_rnp_success(rnp_key_get_protection_hash(sub, &hash)); ++ assert_string_equal(hash, "SHA1"); ++ rnp_buffer_destroy(hash); ++ assert_rnp_success(rnp_key_get_protection_iterations(sub, &iterations)); ++ assert_int_equal(iterations, 30408704); ++ assert_rnp_success(rnp_key_unprotect(sub, "password")); ++ assert_rnp_success(rnp_key_get_protection_type(sub, &type)); ++ assert_string_equal(type, "None"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(sub, &mode)); ++ assert_string_equal(mode, "None"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(sub, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(sub, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(sub, &iterations)); ++ rnp_key_handle_destroy(sub); ++ ++ /* primary key is stored with unknown gpg s2k */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/alice-s2k-101-3.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key)); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "Unknown"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "Unknown"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(key, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(key, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(key, &iterations)); ++ rnp_key_handle_destroy(key); ++ ++ /* primary key is stored with unknown s2k */ ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/alice-s2k-101-unknown.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key)); ++ assert_rnp_success(rnp_key_get_protection_type(key, &type)); ++ assert_string_equal(type, "Unknown"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_key_get_protection_mode(key, &mode)); ++ assert_string_equal(mode, "Unknown"); ++ rnp_buffer_destroy(mode); ++ assert_rnp_failure(rnp_key_get_protection_cipher(key, &cipher)); ++ assert_rnp_failure(rnp_key_get_protection_hash(key, &hash)); ++ assert_rnp_failure(rnp_key_get_protection_iterations(key, &iterations)); ++ rnp_key_handle_destroy(key); ++ ++ rnp_ffi_destroy(ffi); ++} ++ ++TEST_F(rnp_tests, test_ffi_key_default_subkey) ++{ ++ rnp_ffi_t ffi = NULL; ++ rnp_key_handle_t primary = NULL; ++ rnp_key_handle_t def_key = NULL; ++ char * keyid = NULL; ++ ++ test_ffi_init(&ffi); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &primary)); ++ ++ /* bad parameters */ ++ assert_rnp_failure(rnp_key_get_default_key(NULL, NULL, 0, NULL)); ++ assert_rnp_failure(rnp_key_get_default_key(primary, NULL, 0, NULL)); ++ assert_rnp_failure(rnp_key_get_default_key(primary, "nonexistentusage", 0, &def_key)); ++ assert_rnp_failure(rnp_key_get_default_key(primary, "sign", UINT32_MAX, &def_key)); ++ assert_rnp_failure(rnp_key_get_default_key(primary, "sign", 0, NULL)); ++ ++ assert_rnp_success( ++ rnp_key_get_default_key(primary, "encrypt", RNP_KEY_SUBKEYS_ONLY, &def_key)); ++ assert_rnp_success(rnp_key_get_keyid(def_key, &keyid)); ++ assert_string_equal(keyid, "8A05B89FAD5ADED1"); ++ rnp_buffer_destroy(keyid); ++ rnp_key_handle_destroy(def_key); ++ ++ /* no signing subkey */ ++ assert_int_equal(RNP_ERROR_NO_SUITABLE_KEY, ++ rnp_key_get_default_key(primary, "sign", RNP_KEY_SUBKEYS_ONLY, &def_key)); ++ assert_null(def_key); ++ ++ /* primary key returned as a default one */ ++ assert_rnp_success(rnp_key_get_default_key(primary, "sign", 0, &def_key)); ++ assert_rnp_success(rnp_key_get_keyid(def_key, &keyid)); ++ assert_string_equal(keyid, "7BC6709B15C23A4A"); ++ rnp_buffer_destroy(keyid); ++ rnp_key_handle_destroy(def_key); ++ ++ assert_rnp_success(rnp_key_get_default_key(primary, "certify", 0, &def_key)); ++ assert_rnp_success(rnp_key_get_keyid(def_key, &keyid)); ++ assert_string_equal(keyid, "7BC6709B15C23A4A"); ++ rnp_buffer_destroy(keyid); ++ rnp_key_handle_destroy(def_key); ++ ++ rnp_key_handle_destroy(primary); ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ ++ /* primary key with encrypting capability */ ++ assert_true(import_pub_keys(ffi, "data/test_key_validity/encrypting-primary.pgp")); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "92091b7b76c50017", &primary)); ++ ++ assert_rnp_success(rnp_key_get_default_key(primary, "encrypt", 0, &def_key)); ++ assert_rnp_success(rnp_key_get_keyid(def_key, &keyid)); ++ assert_string_equal(keyid, "92091B7B76C50017"); ++ rnp_buffer_destroy(keyid); ++ rnp_key_handle_destroy(def_key); ++ ++ assert_rnp_success( ++ rnp_key_get_default_key(primary, "encrypt", RNP_KEY_SUBKEYS_ONLY, &def_key)); ++ assert_rnp_success(rnp_key_get_keyid(def_key, &keyid)); ++ assert_string_equal(keyid, "C2E243E872C1FE50"); ++ rnp_buffer_destroy(keyid); ++ rnp_key_handle_destroy(def_key); ++ ++ rnp_key_handle_destroy(primary); ++ rnp_ffi_destroy(ffi); ++} ++ ++TEST_F(rnp_tests, test_ffi_rnp_key_get_primary_grip) ++{ ++ rnp_ffi_t ffi = NULL; ++ rnp_key_handle_t key = NULL; ++ char * grip = NULL; ++ ++ // setup FFI ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ ++ // load our keyrings ++ assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg")); ++ ++ // locate primary key ++ key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7BC6709B15C23A4A", &key)); ++ assert_non_null(key); ++ ++ // some edge cases ++ assert_rnp_failure(rnp_key_get_primary_grip(NULL, NULL)); ++ assert_rnp_failure(rnp_key_get_primary_grip(NULL, &grip)); ++ assert_rnp_failure(rnp_key_get_primary_grip(key, NULL)); ++ assert_rnp_failure(rnp_key_get_primary_grip(key, &grip)); ++ assert_null(grip); ++ rnp_key_handle_destroy(key); ++ ++ // locate subkey 1 ++ key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ED63EE56FADC34D", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_get_primary_grip(key, &grip)); ++ assert_non_null(grip); ++ assert_string_equal(grip, "66D6A0800A3FACDE0C0EB60B16B3669ED380FDFA"); ++ rnp_buffer_destroy(grip); ++ grip = NULL; ++ rnp_key_handle_destroy(key); ++ ++ // locate subkey 2 ++ key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1D7E8A5393C997A8", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_get_primary_grip(key, &grip)); ++ assert_non_null(grip); ++ assert_string_equal(grip, "66D6A0800A3FACDE0C0EB60B16B3669ED380FDFA"); ++ rnp_buffer_destroy(grip); ++ grip = NULL; ++ rnp_key_handle_destroy(key); ++ ++ // locate subkey 3 ++ key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "8A05B89FAD5ADED1", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_get_primary_grip(key, &grip)); ++ assert_non_null(grip); ++ assert_string_equal(grip, "66D6A0800A3FACDE0C0EB60B16B3669ED380FDFA"); ++ rnp_buffer_destroy(grip); ++ grip = NULL; ++ rnp_key_handle_destroy(key); ++ ++ // cleanup ++ rnp_ffi_destroy(ffi); ++} ++ ++TEST_F(rnp_tests, test_ffi_rnp_key_get_primary_fprint) ++{ ++ rnp_ffi_t ffi = NULL; ++ ++ // setup FFI ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ ++ // load our keyrings ++ assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg")); ++ ++ // locate primary key ++ rnp_key_handle_t key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7BC6709B15C23A4A", &key)); ++ assert_non_null(key); ++ ++ // some edge cases ++ char *fp = NULL; ++ assert_rnp_failure(rnp_key_get_primary_fprint(NULL, NULL)); ++ assert_rnp_failure(rnp_key_get_primary_fprint(NULL, &fp)); ++ assert_rnp_failure(rnp_key_get_primary_fprint(key, NULL)); ++ assert_rnp_failure(rnp_key_get_primary_fprint(key, &fp)); ++ assert_null(fp); ++ rnp_key_handle_destroy(key); ++ ++ // locate subkey 1 ++ key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ED63EE56FADC34D", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_get_primary_fprint(key, &fp)); ++ assert_non_null(fp); ++ assert_string_equal(fp, "E95A3CBF583AA80A2CCC53AA7BC6709B15C23A4A"); ++ rnp_buffer_destroy(fp); ++ fp = NULL; ++ rnp_key_handle_destroy(key); ++ ++ // locate subkey 2 ++ key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1D7E8A5393C997A8", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_get_primary_fprint(key, &fp)); ++ assert_non_null(fp); ++ assert_string_equal(fp, "E95A3CBF583AA80A2CCC53AA7BC6709B15C23A4A"); ++ rnp_buffer_destroy(fp); ++ fp = NULL; ++ rnp_key_handle_destroy(key); ++ ++ // locate subkey 3 ++ key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "8A05B89FAD5ADED1", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_get_primary_fprint(key, &fp)); ++ assert_non_null(fp); ++ assert_string_equal(fp, "E95A3CBF583AA80A2CCC53AA7BC6709B15C23A4A"); ++ rnp_buffer_destroy(fp); ++ fp = NULL; ++ rnp_key_handle_destroy(key); ++ ++ // locate key 1 - subkey 0 ++ key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "54505A936A4A970E", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_get_primary_fprint(key, &fp)); ++ assert_non_null(fp); ++ assert_string_equal(fp, "BE1C4AB951F4C2F6B604C7F82FCADF05FFA501BB"); ++ rnp_buffer_destroy(fp); ++ fp = NULL; ++ rnp_key_handle_destroy(key); ++ ++ // locate key 2 - subkey 1 ++ key = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "326EF111425D14A5", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_get_primary_fprint(key, &fp)); ++ assert_non_null(fp); ++ assert_string_equal(fp, "BE1C4AB951F4C2F6B604C7F82FCADF05FFA501BB"); ++ rnp_buffer_destroy(fp); ++ fp = NULL; ++ rnp_key_handle_destroy(key); ++ ++ // cleanup ++ rnp_ffi_destroy(ffi); ++} +diff --git a/comm/third_party/rnp/src/tests/ffi-key-sig.cpp b/comm/third_party/rnp/src/tests/ffi-key-sig.cpp +--- a/comm/third_party/rnp/src/tests/ffi-key-sig.cpp ++++ b/comm/third_party/rnp/src/tests/ffi-key-sig.cpp +@@ -82,10 +82,17 @@ TEST_F(rnp_tests, test_ffi_key_signature + char *keyid = NULL; + assert_rnp_success(rnp_signature_get_keyid(sig, &keyid)); + assert_non_null(keyid); + assert_string_equal(keyid, "242A3AA5EA85F44A"); + rnp_buffer_destroy(keyid); ++ char *keyfp = NULL; ++ assert_rnp_failure(rnp_signature_get_key_fprint(sig, NULL)); ++ assert_rnp_failure(rnp_signature_get_key_fprint(NULL, &keyfp)); ++ assert_null(keyfp); ++ assert_rnp_success(rnp_signature_get_key_fprint(sig, &keyfp)); ++ assert_string_equal(keyfp, "AB25CBA042DD924C3ACC3ED3242A3AA5EA85F44A"); ++ rnp_buffer_destroy(keyfp); + rnp_key_handle_t signer = NULL; + assert_rnp_success(rnp_signature_get_signer(sig, &signer)); + assert_non_null(signer); + assert_rnp_success(rnp_key_get_keyid(signer, &keyid)); + assert_non_null(keyid); +@@ -127,10 +134,24 @@ TEST_F(rnp_tests, test_ffi_key_signature + rnp_key_handle_destroy(subkey); + assert_rnp_success(rnp_signature_handle_destroy(sig)); + assert_rnp_success(rnp_uid_handle_destroy(uid)); + assert_rnp_success(rnp_key_handle_destroy(key)); + ++ // check subkey which signature doesn't have issue fingerprint subpacket ++ assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg")); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", "326EF111425D14A5", &subkey)); ++ assert_rnp_success(rnp_key_get_signature_count(subkey, &sigs)); ++ assert_int_equal(sigs, 1); ++ assert_rnp_success(rnp_key_get_signature_at(subkey, 0, &sig)); ++ assert_rnp_success(rnp_signature_get_type(sig, &type)); ++ assert_string_equal(type, "subkey binding"); ++ rnp_buffer_destroy(type); ++ assert_rnp_success(rnp_signature_get_key_fprint(sig, &keyfp)); ++ assert_null(keyfp); ++ rnp_signature_handle_destroy(sig); ++ rnp_key_handle_destroy(subkey); ++ + // cleanup + rnp_ffi_destroy(ffi); + } + + static bool +@@ -446,10 +467,17 @@ TEST_F(rnp_tests, test_ffi_export_revoca + /* wrong password - must fail */ + assert_rnp_success( + rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "wrong")); + assert_rnp_failure(rnp_key_export_revocation( + key_handle, output, 0, "SHA256", "superseded", "test key revocation")); ++ assert_rnp_failure(rnp_key_export_revocation(key_handle, ++ output, ++ RNP_KEY_EXPORT_ARMORED, ++ "SHA256", ++ "superseded", ++ "test key revocation")); ++ + /* unlocked key - must succeed */ + assert_rnp_success(rnp_key_unlock(key_handle, "password")); + assert_rnp_success(rnp_key_export_revocation(key_handle, output, 0, "SHA256", NULL, NULL)); + assert_rnp_success(rnp_output_destroy(output)); + assert_rnp_success(rnp_output_to_path(&output, "alice-revocation.pgp")); +@@ -457,15 +485,21 @@ TEST_F(rnp_tests, test_ffi_export_revoca + assert_rnp_success(rnp_key_lock(key_handle)); + assert_rnp_success( + rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password")); + assert_rnp_success(rnp_key_export_revocation( + key_handle, output, 0, "SHA256", "superseded", "test key revocation")); ++ assert_rnp_success(rnp_output_destroy(output)); ++ ++ /* check that the output is binary or armored as requested */ ++ std::string data = file_to_str("alice-revocation.pgp"); ++ assert_false(starts_with(data, "-----BEGIN PGP PUBLIC KEY BLOCK-----")); ++ assert_false(ends_with(strip_eol(data), "-----END PGP PUBLIC KEY BLOCK-----")); ++ + /* make sure FFI locks key back */ + bool locked = false; + assert_rnp_success(rnp_key_is_locked(key_handle, &locked)); + assert_true(locked); +- assert_rnp_success(rnp_output_destroy(output)); + assert_rnp_success(rnp_key_handle_destroy(key_handle)); + /* make sure we can successfully import exported revocation */ + json_object *jso = NULL; + json_object *jsosigs = NULL; + assert_true(check_import_sigs(ffi, &jso, &jsosigs, "alice-revocation.pgp")); +@@ -494,12 +528,55 @@ TEST_F(rnp_tests, test_ffi_export_revoca + assert_int_equal(sig.type(), PGP_SIG_REV_KEY); + assert_true(sig.has_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON)); + assert_true(sig.has_keyfp()); + assert_int_equal(sig.revocation_code(), PGP_REVOCATION_SUPERSEDED); + assert_string_equal(sig.revocation_reason().c_str(), "test key revocation"); +- assert_int_equal(unlink("alice-revocation.pgp"), 0); ++ ++ assert_int_equal(rnp_unlink("alice-revocation.pgp"), 0); ++ assert_rnp_success(rnp_ffi_destroy(ffi)); ++ ++ /* testing armored revocation generation */ ++ ++ // load initial keyring ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sec.asc")); ++ ++ key_handle = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key_handle)); + ++ // export revocation ++ assert_rnp_success(rnp_output_to_path(&output, "alice-revocation.asc")); ++ assert_rnp_success(rnp_key_unlock(key_handle, "password")); ++ assert_rnp_success(rnp_key_export_revocation(key_handle, ++ output, ++ RNP_KEY_EXPORT_ARMORED, ++ "SHA256", ++ "superseded", ++ "test key revocation")); ++ assert_rnp_success(rnp_output_destroy(output)); ++ assert_rnp_success(rnp_key_handle_destroy(key_handle)); ++ ++ data = file_to_str("alice-revocation.asc"); ++ assert_true(starts_with(data, "-----BEGIN PGP PUBLIC KEY BLOCK-----")); ++ assert_true(ends_with(strip_eol(data), "-----END PGP PUBLIC KEY BLOCK-----")); ++ ++ // import it back ++ assert_true(check_import_sigs(ffi, &jso, &jsosigs, "alice-revocation.asc")); ++ assert_int_equal(json_object_array_length(jsosigs), 1); ++ jsosig = json_object_array_get_idx(jsosigs, 0); ++ assert_true( ++ check_sig_status(jsosig, "new", "new", "73edcc9119afc8e2dbbdcde50451409669ffde3c")); ++ json_object_put(jso); ++ ++ // make sure that key becomes revoked ++ key_handle = NULL; ++ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice ", &key_handle)); ++ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked)); ++ assert_true(revoked); ++ assert_rnp_success(rnp_key_handle_destroy(key_handle)); ++ ++ assert_int_equal(rnp_unlink("alice-revocation.asc"), 0); + assert_rnp_success(rnp_ffi_destroy(ffi)); + } + + #define KEYSIG_PATH "data/test_key_validity/" + +diff --git a/comm/third_party/rnp/src/tests/ffi-key.cpp b/comm/third_party/rnp/src/tests/ffi-key.cpp +new file mode 100644 +--- /dev/null ++++ b/comm/third_party/rnp/src/tests/ffi-key.cpp +@@ -0,0 +1,4358 @@ ++/* ++ * Copyright (c) 2022 [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 ++#include "rnp_tests.h" ++#include "support.h" ++#include ++#include "pgp-key.h" ++#include "ffi-priv-types.h" ++#include "str-utils.h" ++#ifndef RNP_USE_STD_REGEX ++#include ++#else ++#include ++#endif ++ ++static void ++check_key_properties(rnp_key_handle_t key, ++ bool primary_exptected, ++ bool have_public_expected, ++ bool have_secret_expected) ++{ ++ bool isprimary = !primary_exptected; ++ assert_rnp_success(rnp_key_is_primary(key, &isprimary)); ++ assert_true(isprimary == primary_exptected); ++ bool issub = primary_exptected; ++ assert_rnp_success(rnp_key_is_sub(key, &issub)); ++ assert_true(issub == !primary_exptected); ++ bool have_public = !have_public_expected; ++ assert_rnp_success(rnp_key_have_public(key, &have_public)); ++ assert_true(have_public == have_public_expected); ++ bool have_secret = !have_secret_expected; ++ assert_rnp_success(rnp_key_have_secret(key, &have_secret)); ++ assert_true(have_secret == have_secret_expected); ++} ++ ++TEST_F(rnp_tests, test_ffi_keygen_json_pair) ++{ ++ rnp_ffi_t ffi = NULL; ++ char * results = NULL; ++ size_t count = 0; ++ ++ // setup FFI ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL)); ++ assert_rnp_success( ++ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "abc")); ++ ++ // load our JSON ++ auto json = file_to_str("data/test_ffi_json/generate-pair.json"); ++ ++ // generate the keys ++ assert_rnp_success(rnp_generate_key_json(ffi, json.c_str(), &results)); ++ assert_non_null(results); ++ ++ // parse the results JSON ++ json_object *parsed_results = json_tokener_parse(results); ++ assert_non_null(parsed_results); ++ rnp_buffer_destroy(results); ++ results = NULL; ++ // get a handle for the primary ++ rnp_key_handle_t primary = NULL; ++ { ++ json_object *jsokey = NULL; ++ assert_int_equal(true, json_object_object_get_ex(parsed_results, "primary", &jsokey)); ++ assert_non_null(jsokey); ++ json_object *jsogrip = NULL; ++ assert_int_equal(true, json_object_object_get_ex(jsokey, "grip", &jsogrip)); ++ assert_non_null(jsogrip); ++ const char *grip = json_object_get_string(jsogrip); ++ assert_non_null(grip); ++ assert_rnp_success(rnp_locate_key(ffi, "grip", grip, &primary)); ++ assert_non_null(primary); ++ } ++ // get a handle for the sub ++ rnp_key_handle_t sub = NULL; ++ { ++ json_object *jsokey = NULL; ++ assert_int_equal(true, json_object_object_get_ex(parsed_results, "sub", &jsokey)); ++ assert_non_null(jsokey); ++ json_object *jsogrip = NULL; ++ assert_int_equal(true, json_object_object_get_ex(jsokey, "grip", &jsogrip)); ++ assert_non_null(jsogrip); ++ const char *grip = json_object_get_string(jsogrip); ++ assert_non_null(grip); ++ assert_rnp_success(rnp_locate_key(ffi, "grip", grip, &sub)); ++ assert_non_null(sub); ++ } ++ // cleanup ++ json_object_put(parsed_results); ++ ++ // check the key counts ++ assert_rnp_success(rnp_get_public_key_count(ffi, &count)); ++ assert_int_equal(2, count); ++ assert_rnp_success(rnp_get_secret_key_count(ffi, &count)); ++ assert_int_equal(2, count); ++ ++ // check some key properties ++ check_key_properties(primary, true, true, true); ++ check_key_properties(sub, false, true, true); ++ ++ // check sub bit length ++ uint32_t length = 0; ++ assert_rnp_success(rnp_key_get_bits(sub, &length)); ++ assert_int_equal(1024, length); ++ ++ // cleanup ++ rnp_key_handle_destroy(primary); ++ rnp_key_handle_destroy(sub); ++ rnp_ffi_destroy(ffi); ++} ++ ++TEST_F(rnp_tests, test_ffi_keygen_json_pair_dsa_elg) ++{ ++ rnp_ffi_t ffi = NULL; ++ char * results = NULL; ++ size_t count = 0; ++ ++ // setup FFI ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL)); ++ assert_rnp_success( ++ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "abc")); ++ ++ // load our JSON ++ auto json = file_to_str("data/test_ffi_json/generate-pair-dsa-elg.json"); ++ ++ // generate the keys ++ assert_rnp_success(rnp_generate_key_json(ffi, json.c_str(), &results)); ++ assert_non_null(results); ++ ++ // parse the results JSON ++ json_object *parsed_results = json_tokener_parse(results); ++ assert_non_null(parsed_results); ++ rnp_buffer_destroy(results); ++ results = NULL; ++ // get a handle for the primary ++ rnp_key_handle_t primary = NULL; ++ { ++ json_object *jsokey = NULL; ++ assert_int_equal(true, json_object_object_get_ex(parsed_results, "primary", &jsokey)); ++ assert_non_null(jsokey); ++ json_object *jsogrip = NULL; ++ assert_int_equal(true, json_object_object_get_ex(jsokey, "grip", &jsogrip)); ++ assert_non_null(jsogrip); ++ const char *grip = json_object_get_string(jsogrip); ++ assert_non_null(grip); ++ assert_rnp_success(rnp_locate_key(ffi, "grip", grip, &primary)); ++ assert_non_null(primary); ++ } ++ // get a handle for the sub ++ rnp_key_handle_t sub = NULL; ++ { ++ json_object *jsokey = NULL; ++ assert_int_equal(true, json_object_object_get_ex(parsed_results, "sub", &jsokey)); ++ assert_non_null(jsokey); ++ json_object *jsogrip = NULL; ++ assert_int_equal(true, json_object_object_get_ex(jsokey, "grip", &jsogrip)); ++ assert_non_null(jsogrip); ++ const char *grip = json_object_get_string(jsogrip); ++ assert_non_null(grip); ++ assert_rnp_success(rnp_locate_key(ffi, "grip", grip, &sub)); ++ assert_non_null(sub); ++ } ++ // cleanup ++ json_object_put(parsed_results); ++ ++ // check the key counts ++ assert_rnp_success(rnp_get_public_key_count(ffi, &count)); ++ assert_int_equal(2, count); ++ assert_rnp_success(rnp_get_secret_key_count(ffi, &count)); ++ assert_int_equal(2, count); ++ ++ // check some key properties ++ check_key_properties(primary, true, true, true); ++ check_key_properties(sub, false, true, true); ++ ++ // check bit lengths ++ uint32_t length = 0; ++ assert_rnp_success(rnp_key_get_bits(primary, &length)); ++ assert_int_equal(length, 1024); ++ assert_rnp_success(rnp_key_get_bits(sub, &length)); ++ assert_int_equal(length, 1536); ++ ++ // cleanup ++ rnp_key_handle_destroy(primary); ++ rnp_key_handle_destroy(sub); ++ rnp_ffi_destroy(ffi); ++} ++ ++TEST_F(rnp_tests, test_ffi_keygen_json_primary) ++{ ++ rnp_ffi_t ffi = NULL; ++ char * results = NULL; ++ size_t count = 0; ++ ++ // setup FFI ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL)); ++ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, unused_getpasscb, NULL)); ++ ++ // load our JSON ++ auto json = file_to_str("data/test_ffi_json/generate-primary.json"); ++ ++ // generate the keys ++ assert_rnp_success(rnp_generate_key_json(ffi, json.c_str(), &results)); ++ assert_non_null(results); ++ ++ // parse the results JSON ++ json_object *parsed_results = json_tokener_parse(results); ++ assert_non_null(parsed_results); ++ rnp_buffer_destroy(results); ++ results = NULL; ++ // get a handle for the primary ++ rnp_key_handle_t primary = NULL; ++ { ++ json_object *jsokey = NULL; ++ assert_int_equal(true, json_object_object_get_ex(parsed_results, "primary", &jsokey)); ++ assert_non_null(jsokey); ++ json_object *jsogrip = NULL; ++ assert_int_equal(true, json_object_object_get_ex(jsokey, "grip", &jsogrip)); ++ assert_non_null(jsogrip); ++ const char *grip = json_object_get_string(jsogrip); ++ assert_non_null(grip); ++ assert_rnp_success(rnp_locate_key(ffi, "grip", grip, &primary)); ++ assert_non_null(primary); ++ } ++ // cleanup ++ json_object_put(parsed_results); ++ parsed_results = NULL; ++ ++ // check the key counts ++ assert_rnp_success(rnp_get_public_key_count(ffi, &count)); ++ assert_int_equal(1, count); ++ assert_rnp_success(rnp_get_secret_key_count(ffi, &count)); ++ assert_int_equal(1, count); ++ ++ // check some key properties ++ check_key_properties(primary, true, true, true); ++ ++ // cleanup ++ rnp_key_handle_destroy(primary); ++ rnp_ffi_destroy(ffi); ++} ++ ++/* This test generates a primary key, and then a subkey (separately). ++ */ ++TEST_F(rnp_tests, test_ffi_keygen_json_sub) ++{ ++ char * results = NULL; ++ size_t count = 0; ++ rnp_ffi_t ffi = NULL; ++ ++ // setup FFI ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL)); ++ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, unused_getpasscb, NULL)); ++ ++ // generate our primary key ++ auto json = file_to_str("data/test_ffi_json/generate-primary.json"); ++ assert_rnp_success(rnp_generate_key_json(ffi, json.c_str(), &results)); ++ // check key counts ++ assert_rnp_success(rnp_get_public_key_count(ffi, &count)); ++ assert_int_equal(1, count); ++ assert_rnp_success(rnp_get_secret_key_count(ffi, &count)); ++ assert_int_equal(1, count); ++ ++ // parse the results JSON ++ json_object *parsed_results = json_tokener_parse(results); ++ assert_non_null(parsed_results); ++ rnp_buffer_destroy(results); ++ results = NULL; ++ // get a handle+grip for the primary ++ rnp_key_handle_t primary = NULL; ++ char * primary_grip = NULL; ++ { ++ json_object *jsokey = NULL; ++ assert_int_equal(true, json_object_object_get_ex(parsed_results, "primary", &jsokey)); ++ assert_non_null(jsokey); ++ json_object *jsogrip = NULL; ++ assert_int_equal(true, json_object_object_get_ex(jsokey, "grip", &jsogrip)); ++ assert_non_null(jsogrip); ++ primary_grip = strdup(json_object_get_string(jsogrip)); ++ assert_non_null(primary_grip); ++ assert_rnp_success(rnp_locate_key(ffi, "grip", primary_grip, &primary)); ++ assert_non_null(primary); ++ } ++ // cleanup ++ json_object_put(parsed_results); ++ parsed_results = NULL; ++ ++ // load our JSON template ++ json = file_to_str("data/test_ffi_json/generate-sub.json"); ++ // modify our JSON ++ { ++ // parse ++ json_object *jso = json_tokener_parse(json.c_str()); ++ assert_non_null(jso); ++ // find the relevant fields ++ json_object *jsosub = NULL; ++ json_object *jsoprimary = NULL; ++ assert_true(json_object_object_get_ex(jso, "sub", &jsosub)); ++ assert_non_null(jsosub); ++ assert_true(json_object_object_get_ex(jsosub, "primary", &jsoprimary)); ++ assert_non_null(jsoprimary); ++ // replace the placeholder grip with the correct one ++ json_object_object_del(jsoprimary, "grip"); ++ json_object_object_add(jsoprimary, "grip", json_object_new_string(primary_grip)); ++ assert_int_equal(1, json_object_object_length(jsoprimary)); ++ json = json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY); ++ assert_false(json.empty()); ++ json_object_put(jso); ++ } ++ // cleanup ++ rnp_buffer_destroy(primary_grip); ++ primary_grip = NULL; ++ ++ // generate the subkey ++ assert_rnp_success(rnp_generate_key_json(ffi, json.c_str(), &results)); ++ assert_non_null(results); ++ ++ // parse the results JSON ++ parsed_results = json_tokener_parse(results); ++ assert_non_null(parsed_results); ++ rnp_buffer_destroy(results); ++ results = NULL; ++ // get a handle for the sub ++ rnp_key_handle_t sub = NULL; ++ { ++ json_object *jsokey = NULL; ++ assert_int_equal(true, json_object_object_get_ex(parsed_results, "sub", &jsokey)); ++ assert_non_null(jsokey); ++ json_object *jsogrip = NULL; ++ assert_int_equal(true, json_object_object_get_ex(jsokey, "grip", &jsogrip)); ++ assert_non_null(jsogrip); ++ const char *grip = json_object_get_string(jsogrip); ++ assert_non_null(grip); ++ assert_rnp_success(rnp_locate_key(ffi, "grip", grip, &sub)); ++ assert_non_null(sub); ++ } ++ // cleanup ++ json_object_put(parsed_results); ++ parsed_results = NULL; ++ ++ // check the key counts ++ assert_rnp_success(rnp_get_public_key_count(ffi, &count)); ++ assert_int_equal(2, count); ++ assert_rnp_success(rnp_get_secret_key_count(ffi, &count)); ++ assert_int_equal(2, count); ++ ++ // check some key properties ++ check_key_properties(primary, true, true, true); ++ check_key_properties(sub, false, true, true); ++ ++ // check sub bit length ++ uint32_t length = 0; ++ assert_rnp_success(rnp_key_get_bits(sub, &length)); ++ assert_int_equal(length, 1024); ++ ++ // cleanup ++ rnp_key_handle_destroy(primary); ++ rnp_key_handle_destroy(sub); ++ rnp_ffi_destroy(ffi); ++} ++ ++TEST_F(rnp_tests, test_ffi_keygen_json_edge_cases) ++{ ++ rnp_ffi_t ffi = NULL; ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ ++ /* Attempt to generate with invalid parameters */ ++ std::string json = ""; ++ char * results = NULL; ++ assert_rnp_failure(rnp_generate_key_json(NULL, json.c_str(), &results)); ++ assert_rnp_failure(rnp_generate_key_json(ffi, NULL, &results)); ++ assert_rnp_failure(rnp_generate_key_json(ffi, "{ something, wrong }", &results)); ++ assert_rnp_failure(rnp_generate_key_json(ffi, "{ }", &results)); ++ assert_rnp_failure( ++ rnp_generate_key_json(ffi, "{ \"primary\": { }, \"wrong\": {} }", &results)); ++ assert_rnp_failure( ++ rnp_generate_key_json(ffi, "{ \"primary\": { }, \"PRIMARY\": { } }", &results)); ++ /* Json-C puts stuff under the same key into the single object */ ++ assert_rnp_success( ++ rnp_generate_key_json(ffi, "{ \"primary\": { }, \"primary\": { } }", &results)); ++ rnp_buffer_destroy(results); ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ /* Generate key with an empty description */ ++ assert_rnp_success(rnp_generate_key_json(ffi, "{ \"priMary\": {} }", &results)); ++ assert_non_null(results); ++ rnp_buffer_destroy(results); ++ size_t count = 0; ++ assert_rnp_success(rnp_get_secret_key_count(ffi, &count)); ++ assert_int_equal(count, 1); ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ /* Generate with wrong preferences */ ++ json = file_to_str("data/test_ffi_json/generate-eddsa-wrong-prefs.json"); ++ assert_rnp_failure(rnp_generate_key_json(ffi, json.c_str(), &results)); ++ /* Generate with wrong PK algorithm */ ++ json = file_to_str("data/test_ffi_json/generate-bad-pk-alg.json"); ++ results = NULL; ++ assert_rnp_failure(rnp_generate_key_json(ffi, json.c_str(), &results)); ++ assert_null(results); ++ assert_rnp_success(rnp_get_secret_key_count(ffi, &count)); ++ assert_int_equal(count, 0); ++ assert_rnp_success(rnp_get_public_key_count(ffi, &count)); ++ assert_int_equal(count, 0); ++ ++ rnp_ffi_destroy(ffi); ++} ++ ++TEST_F(rnp_tests, test_ffi_key_generate_misc) ++{ ++ rnp_ffi_t ffi = NULL; ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL)); ++ ++ /* make sure we do not leak key handle and do not access NULL */ ++ assert_rnp_success(rnp_generate_key_rsa(ffi, 1024, 1024, "rsa", NULL, NULL)); ++ ++ /* make sure we do not leak password on failed key generation */ ++ rnp_key_handle_t key = NULL; ++ assert_rnp_failure(rnp_generate_key_rsa(ffi, 768, 2048, "rsa_768", "password", &key)); ++ assert_rnp_failure(rnp_generate_key_rsa(ffi, 1024, 768, "rsa_768", "password", &key)); ++ ++ /* make sure we behave correctly and do not leak data on wrong parameters to _generate_ex ++ * function */ ++ assert_rnp_failure(rnp_generate_key_ex( ++ ffi, "RSA", "RSA", 1024, 1024, "Curve", NULL, "userid", "password", &key)); ++ assert_rnp_failure(rnp_generate_key_ex( ++ ffi, "RSA", "RSA", 1024, 1024, "Curve", NULL, NULL, "password", &key)); ++ assert_rnp_failure(rnp_generate_key_ex( ++ ffi, "RSA", "RSA", 1024, 768, NULL, "Curve", NULL, "password", &key)); ++ assert_rnp_failure(rnp_generate_key_ex( ++ ffi, "ECDSA", "ECDH", 1024, 0, "Unknown", "Curve", NULL, NULL, &key)); ++ assert_rnp_failure(rnp_generate_key_ex( ++ ffi, "ECDSA", "ECDH", 0, 1024, "Unknown", "Curve", NULL, "password", &key)); ++ ++ /* generate RSA-RSA key without password */ ++ assert_rnp_success( ++ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "abc")); ++ assert_rnp_success(rnp_generate_key_rsa(ffi, 1024, 1024, "rsa_1024", NULL, &key)); ++ assert_non_null(key); ++ bool locked = false; ++ assert_rnp_success(rnp_key_is_locked(key, &locked)); ++ assert_false(locked); ++ /* check key and subkey flags */ ++ bool flag = false; ++ assert_rnp_success(rnp_key_allows_usage(key, "sign", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "certify", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "encrypt", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "authenticate", &flag)); ++ assert_false(flag); ++ uint32_t expiration = 0; ++ assert_rnp_success(rnp_key_get_expiration(key, &expiration)); ++ assert_int_equal(expiration, 2 * 365 * 24 * 60 * 60); ++ uint32_t creation = 0; ++ assert_rnp_success(rnp_key_get_creation(key, &creation)); ++ uint32_t till = 0; ++ assert_rnp_success(rnp_key_valid_till(key, &till)); ++ assert_int_equal(till, creation + expiration); ++ rnp_key_handle_t subkey = NULL; ++ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &subkey)); ++ assert_non_null(subkey); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "sign", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "certify", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "encrypt", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "authenticate", &flag)); ++ assert_false(flag); ++ expiration = 0; ++ assert_rnp_success(rnp_key_get_expiration(subkey, &expiration)); ++ assert_int_equal(expiration, 2 * 365 * 24 * 60 * 60); ++ creation = 0; ++ assert_rnp_success(rnp_key_get_creation(key, &creation)); ++ till = 0; ++ assert_rnp_success(rnp_key_valid_till(key, &till)); ++ assert_int_equal(till, creation + expiration); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(subkey)); ++ /* generate encrypted RSA-RSA key */ ++ assert_rnp_success(rnp_generate_key_rsa(ffi, 1024, 1024, "rsa_1024", "123", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_is_locked(key, &locked)); ++ assert_true(locked); ++ /* make sure it can be unlocked with correct password */ ++ assert_rnp_success(rnp_key_unlock(key, "123")); ++ /* do the same for subkey */ ++ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &subkey)); ++ assert_non_null(subkey); ++ assert_rnp_success(rnp_key_is_locked(subkey, &locked)); ++ assert_true(locked); ++ assert_rnp_success(rnp_key_unlock(subkey, "123")); ++ /* cleanup */ ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_key_handle_destroy(subkey)); ++ /* generate encrypted RSA key (primary only) */ ++ key = NULL; ++ assert_rnp_success( ++ rnp_generate_key_ex(ffi, "RSA", NULL, 1024, 0, NULL, NULL, "rsa_1024", "123", &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_is_locked(key, &locked)); ++ assert_true(locked); ++ bool prot = false; ++ assert_rnp_success(rnp_key_is_protected(key, &prot)); ++ assert_true(prot); ++ /* cleanup */ ++ rnp_key_handle_destroy(key); ++ ++ /* generate key with signing subkey */ ++ rnp_op_generate_t op = NULL; ++ assert_rnp_success(rnp_op_generate_create(&op, ffi, "ECDSA")); ++ assert_rnp_success(rnp_op_generate_set_curve(op, "secp256k1")); ++ assert_rnp_success(rnp_op_generate_set_userid(op, "ecdsa_ecdsa")); ++ assert_rnp_success(rnp_op_generate_add_usage(op, "sign")); ++ assert_rnp_success(rnp_op_generate_add_usage(op, "certify")); ++ assert_rnp_success(rnp_op_generate_set_expiration(op, 0)); ++ assert_rnp_success(rnp_op_generate_execute(op)); ++ rnp_key_handle_t primary = NULL; ++ assert_rnp_success(rnp_op_generate_get_key(op, &primary)); ++ rnp_op_generate_destroy(op); ++ char *keyid = NULL; ++ assert_rnp_success(rnp_key_get_keyid(primary, &keyid)); ++ ++ rnp_op_generate_t subop = NULL; ++ assert_rnp_success(rnp_op_generate_subkey_create(&subop, ffi, primary, "ECDSA")); ++ assert_rnp_success(rnp_op_generate_set_curve(subop, "NIST P-256")); ++ assert_rnp_success(rnp_op_generate_add_usage(subop, "sign")); ++ assert_rnp_success(rnp_op_generate_add_usage(subop, "certify")); ++ assert_rnp_success(rnp_op_generate_set_expiration(subop, 0)); ++ assert_rnp_success(rnp_op_generate_execute(subop)); ++ assert_rnp_success(rnp_op_generate_get_key(subop, &subkey)); ++ rnp_op_generate_destroy(subop); ++ char *subid = NULL; ++ assert_rnp_success(rnp_key_get_keyid(subkey, &subid)); ++ ++ rnp_output_t output = NULL; ++ rnp_output_to_memory(&output, 0); ++ assert_rnp_success( ++ rnp_key_export(primary, ++ output, ++ RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS)); ++ rnp_key_handle_destroy(primary); ++ rnp_key_handle_destroy(subkey); ++ uint8_t *buf = NULL; ++ size_t len = 0; ++ rnp_output_memory_get_buf(output, &buf, &len, false); ++ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)); ++ assert_true(import_pub_keys(ffi, buf, len)); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", keyid, &primary)); ++ assert_non_null(primary); ++ assert_true(primary->pub->valid()); ++ bool valid = false; ++ assert_rnp_failure(rnp_key_is_valid(primary, NULL)); ++ assert_rnp_failure(rnp_key_is_valid(NULL, &valid)); ++ assert_rnp_success(rnp_key_is_valid(primary, &valid)); ++ assert_true(valid); ++ till = 0; ++ assert_rnp_failure(rnp_key_valid_till(primary, NULL)); ++ assert_rnp_failure(rnp_key_valid_till(NULL, &till)); ++ assert_rnp_success(rnp_key_valid_till(primary, &till)); ++ assert_int_equal(till, 0xffffffff); ++ uint64_t till64 = 0; ++ assert_rnp_failure(rnp_key_valid_till64(primary, NULL)); ++ assert_rnp_failure(rnp_key_valid_till64(NULL, &till64)); ++ assert_rnp_success(rnp_key_valid_till64(primary, &till64)); ++ assert_int_equal(till64, UINT64_MAX); ++ rnp_key_handle_destroy(primary); ++ assert_rnp_success(rnp_locate_key(ffi, "keyid", subid, &subkey)); ++ assert_non_null(subkey); ++ assert_true(subkey->pub->valid()); ++ valid = false; ++ assert_rnp_success(rnp_key_is_valid(subkey, &valid)); ++ assert_true(valid); ++ till = 0; ++ assert_rnp_success(rnp_key_valid_till(subkey, &till)); ++ assert_int_equal(till, 0xffffffff); ++ assert_rnp_success(rnp_key_valid_till64(subkey, &till64)); ++ assert_int_equal(till64, UINT64_MAX); ++ rnp_key_handle_destroy(subkey); ++ rnp_buffer_destroy(keyid); ++ rnp_buffer_destroy(subid); ++ rnp_output_destroy(output); ++ ++ /* cleanup */ ++ assert_rnp_success(rnp_ffi_destroy(ffi)); ++} ++ ++TEST_F(rnp_tests, test_ffi_key_generate_rsa) ++{ ++ rnp_ffi_t ffi = NULL; ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL)); ++ /* make sure we fail to generate too small and too large keys/subkeys */ ++ rnp_key_handle_t key = NULL; ++ assert_rnp_failure(rnp_generate_key_rsa(ffi, 768, 2048, "rsa_768", NULL, &key)); ++ assert_rnp_failure(rnp_generate_key_rsa(ffi, 1024, 768, "rsa_768", NULL, &key)); ++ assert_rnp_failure(rnp_generate_key_rsa(ffi, 20480, 1024, "rsa_20480", NULL, &key)); ++ assert_rnp_failure(rnp_generate_key_rsa(ffi, 1024, 20480, "rsa_20480", NULL, &key)); ++ /* generate RSA-RSA key */ ++ assert_rnp_success(rnp_generate_key_rsa(ffi, 1024, 2048, "rsa_1024", NULL, &key)); ++ assert_non_null(key); ++ /* check properties of the generated key */ ++ bool boolres = false; ++ assert_rnp_success(rnp_key_is_primary(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_public(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_secret(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_is_protected(key, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_is_locked(key, &boolres)); ++ assert_false(boolres); ++ /* algorithm */ ++ char *alg = NULL; ++ assert_rnp_success(rnp_key_get_alg(key, &alg)); ++ assert_int_equal(strcasecmp(alg, "RSA"), 0); ++ rnp_buffer_destroy(alg); ++ /* key bits */ ++ uint32_t bits = 0; ++ assert_rnp_failure(rnp_key_get_bits(key, NULL)); ++ assert_rnp_success(rnp_key_get_bits(key, &bits)); ++ assert_int_equal(bits, 1024); ++ assert_rnp_failure(rnp_key_get_dsa_qbits(key, &bits)); ++ /* key flags */ ++ bool flag = false; ++ assert_rnp_success(rnp_key_allows_usage(key, "sign", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "certify", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "encrypt", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "authenticate", &flag)); ++ assert_false(flag); ++ /* curve - must fail */ ++ char *curve = NULL; ++ assert_rnp_failure(rnp_key_get_curve(key, NULL)); ++ assert_rnp_failure(rnp_key_get_curve(key, &curve)); ++ assert_null(curve); ++ /* user ids */ ++ size_t uids = 0; ++ char * uid = NULL; ++ assert_rnp_success(rnp_key_get_uid_count(key, &uids)); ++ assert_int_equal(uids, 1); ++ assert_rnp_failure(rnp_key_get_uid_at(key, 1, &uid)); ++ assert_null(uid); ++ assert_rnp_success(rnp_key_get_uid_at(key, 0, &uid)); ++ assert_string_equal(uid, "rsa_1024"); ++ rnp_buffer_destroy(uid); ++ /* subkey */ ++ size_t subkeys = 0; ++ assert_rnp_failure(rnp_key_get_subkey_count(key, NULL)); ++ assert_rnp_success(rnp_key_get_subkey_count(key, &subkeys)); ++ assert_int_equal(subkeys, 1); ++ rnp_key_handle_t subkey = NULL; ++ assert_rnp_failure(rnp_key_get_subkey_at(key, 1, &subkey)); ++ assert_rnp_failure(rnp_key_get_subkey_at(key, 0, NULL)); ++ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &subkey)); ++ /* check properties of the generated subkey */ ++ assert_rnp_success(rnp_key_is_primary(subkey, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_have_public(subkey, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_secret(subkey, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_is_protected(subkey, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_is_locked(subkey, &boolres)); ++ assert_false(boolres); ++ /* algorithm */ ++ assert_rnp_success(rnp_key_get_alg(subkey, &alg)); ++ assert_int_equal(strcasecmp(alg, "RSA"), 0); ++ rnp_buffer_destroy(alg); ++ /* key bits */ ++ assert_rnp_success(rnp_key_get_bits(subkey, &bits)); ++ assert_int_equal(bits, 2048); ++ /* subkey flags */ ++ assert_rnp_success(rnp_key_allows_usage(subkey, "sign", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "certify", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "encrypt", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "authenticate", &flag)); ++ assert_false(flag); ++ /* cleanup */ ++ assert_rnp_success(rnp_key_handle_destroy(subkey)); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ ++ /* generate RSA key without the subkey */ ++ assert_rnp_success(rnp_generate_key_rsa(ffi, 1024, 0, "rsa_1024", NULL, &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_get_subkey_count(key, &subkeys)); ++ assert_int_equal(subkeys, 0); ++ /* cleanup */ ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_ffi_destroy(ffi)); ++} ++ ++TEST_F(rnp_tests, test_ffi_key_generate_dsa) ++{ ++ rnp_ffi_t ffi = NULL; ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL)); ++ /* try to generate keys with invalid sizes */ ++ rnp_key_handle_t key = NULL; ++ assert_rnp_failure(rnp_generate_key_dsa_eg(ffi, 768, 2048, "dsa_768", NULL, &key)); ++ assert_rnp_failure(rnp_generate_key_dsa_eg(ffi, 1024, 768, "dsa_768", NULL, &key)); ++ assert_rnp_failure(rnp_generate_key_dsa_eg(ffi, 4096, 1024, "dsa_20480", NULL, &key)); ++ assert_rnp_failure(rnp_generate_key_dsa_eg(ffi, 1024, 20480, "dsa_20480", NULL, &key)); ++ /* generate DSA-ElGamal keypair */ ++ assert_rnp_success(rnp_generate_key_dsa_eg(ffi, 1024, 1024, "dsa_1024", NULL, &key)); ++ assert_non_null(key); ++ /* check properties of the generated key */ ++ bool boolres = false; ++ assert_rnp_success(rnp_key_is_primary(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_public(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_secret(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_is_protected(key, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_is_locked(key, &boolres)); ++ assert_false(boolres); ++ /* algorithm */ ++ char *alg = NULL; ++ assert_rnp_success(rnp_key_get_alg(key, &alg)); ++ assert_int_equal(strcasecmp(alg, "DSA"), 0); ++ rnp_buffer_destroy(alg); ++ /* key bits */ ++ uint32_t bits = 0; ++ assert_rnp_success(rnp_key_get_bits(key, &bits)); ++ assert_int_equal(bits, 1024); ++ assert_rnp_success(rnp_key_get_dsa_qbits(key, &bits)); ++ assert_int_equal(bits, 160); ++ /* key flags */ ++ bool flag = false; ++ assert_rnp_success(rnp_key_allows_usage(key, "sign", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "certify", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "encrypt", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "authenticate", &flag)); ++ assert_false(flag); ++ /* user ids */ ++ size_t uids = 0; ++ char * uid = NULL; ++ assert_rnp_success(rnp_key_get_uid_count(key, &uids)); ++ assert_int_equal(uids, 1); ++ assert_rnp_success(rnp_key_get_uid_at(key, 0, &uid)); ++ assert_string_equal(uid, "dsa_1024"); ++ rnp_buffer_destroy(uid); ++ /* subkey */ ++ size_t subkeys = 0; ++ assert_rnp_success(rnp_key_get_subkey_count(key, &subkeys)); ++ assert_int_equal(subkeys, 1); ++ rnp_key_handle_t subkey = NULL; ++ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &subkey)); ++ /* check properties of the generated subkey */ ++ assert_rnp_success(rnp_key_is_primary(subkey, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_have_public(subkey, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_secret(subkey, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_is_protected(subkey, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_is_locked(subkey, &boolres)); ++ assert_false(boolres); ++ /* algorithm */ ++ assert_rnp_success(rnp_key_get_alg(subkey, &alg)); ++ assert_int_equal(strcasecmp(alg, "ELGAMAL"), 0); ++ rnp_buffer_destroy(alg); ++ /* key bits */ ++ assert_rnp_success(rnp_key_get_bits(subkey, &bits)); ++ assert_int_equal(bits, 1024); ++ /* subkey flags */ ++ assert_rnp_success(rnp_key_allows_usage(subkey, "sign", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "certify", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "encrypt", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "authenticate", &flag)); ++ assert_false(flag); ++ /* cleanup */ ++ assert_rnp_success(rnp_key_handle_destroy(subkey)); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ ++ /* generate DSA key without the subkey */ ++ assert_rnp_success(rnp_generate_key_dsa_eg(ffi, 1024, 0, "dsa_1024", NULL, &key)); ++ assert_non_null(key); ++ assert_rnp_success(rnp_key_get_subkey_count(key, &subkeys)); ++ assert_int_equal(subkeys, 0); ++ /* cleanup */ ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_ffi_destroy(ffi)); ++} ++ ++TEST_F(rnp_tests, test_ffi_key_generate_ecdsa) ++{ ++ rnp_ffi_t ffi = NULL; ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL)); ++ /* try to generate key with invalid curve */ ++ rnp_key_handle_t key = NULL; ++ assert_rnp_failure(rnp_generate_key_ec(ffi, "curve_wrong", "wrong", NULL, &key)); ++ assert_null(key); ++ /* generate secp256k1 key */ ++ assert_rnp_success(rnp_generate_key_ec(ffi, "secp256k1", "ec_256k1", NULL, &key)); ++ assert_non_null(key); ++ /* check properties of the generated key */ ++ bool boolres = false; ++ assert_rnp_success(rnp_key_is_primary(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_public(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_secret(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_is_protected(key, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_is_locked(key, &boolres)); ++ assert_false(boolres); ++ /* algorithm */ ++ char *alg = NULL; ++ assert_rnp_success(rnp_key_get_alg(key, &alg)); ++ assert_int_equal(strcasecmp(alg, "ECDSA"), 0); ++ rnp_buffer_destroy(alg); ++ /* key bits */ ++ uint32_t bits = 0; ++ assert_rnp_success(rnp_key_get_bits(key, &bits)); ++ assert_int_equal(bits, 256); ++ assert_rnp_failure(rnp_key_get_dsa_qbits(key, &bits)); ++ /* curve */ ++ char *curve = NULL; ++ assert_rnp_failure(rnp_key_get_curve(key, NULL)); ++ assert_rnp_success(rnp_key_get_curve(key, &curve)); ++ assert_int_equal(strcasecmp(curve, "secp256k1"), 0); ++ rnp_buffer_destroy(curve); ++ /* key flags */ ++ bool flag = false; ++ assert_rnp_success(rnp_key_allows_usage(key, "sign", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "certify", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "encrypt", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "authenticate", &flag)); ++ assert_false(flag); ++ /* user ids */ ++ size_t uids = 0; ++ char * uid = NULL; ++ assert_rnp_success(rnp_key_get_uid_count(key, &uids)); ++ assert_int_equal(uids, 1); ++ assert_rnp_success(rnp_key_get_uid_at(key, 0, &uid)); ++ assert_string_equal(uid, "ec_256k1"); ++ rnp_buffer_destroy(uid); ++ /* subkey */ ++ size_t subkeys = 0; ++ assert_rnp_success(rnp_key_get_subkey_count(key, &subkeys)); ++ assert_int_equal(subkeys, 1); ++ rnp_key_handle_t subkey = NULL; ++ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &subkey)); ++ /* check properties of the generated subkey */ ++ assert_rnp_success(rnp_key_is_primary(subkey, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_have_public(subkey, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_secret(subkey, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_is_protected(subkey, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_is_locked(subkey, &boolres)); ++ assert_false(boolres); ++ /* algorithm */ ++ assert_rnp_success(rnp_key_get_alg(subkey, &alg)); ++ assert_int_equal(strcasecmp(alg, "ECDH"), 0); ++ rnp_buffer_destroy(alg); ++ /* bits */ ++ assert_rnp_success(rnp_key_get_bits(subkey, &bits)); ++ assert_int_equal(bits, 256); ++ /* curve */ ++ curve = NULL; ++ assert_rnp_success(rnp_key_get_curve(subkey, &curve)); ++ assert_int_equal(strcasecmp(curve, "secp256k1"), 0); ++ rnp_buffer_destroy(curve); ++ /* subkey flags */ ++ assert_rnp_success(rnp_key_allows_usage(subkey, "sign", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "certify", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "encrypt", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(subkey, "authenticate", &flag)); ++ assert_false(flag); ++ ++ assert_rnp_success(rnp_key_handle_destroy(subkey)); ++ assert_rnp_success(rnp_key_handle_destroy(key)); ++ assert_rnp_success(rnp_ffi_destroy(ffi)); ++} ++ ++TEST_F(rnp_tests, test_ffi_key_generate_eddsa) ++{ ++ rnp_ffi_t ffi = NULL; ++ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); ++ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL)); ++ /* generate key with subkey */ ++ rnp_key_handle_t key = NULL; ++ assert_rnp_success(rnp_generate_key_25519(ffi, "eddsa_25519", NULL, &key)); ++ assert_non_null(key); ++ /* check properties of the generated key */ ++ bool boolres = false; ++ assert_rnp_success(rnp_key_is_primary(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_public(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_have_secret(key, &boolres)); ++ assert_true(boolres); ++ assert_rnp_success(rnp_key_is_protected(key, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_is_locked(key, &boolres)); ++ assert_false(boolres); ++ /* algorithm */ ++ char *alg = NULL; ++ assert_rnp_success(rnp_key_get_alg(key, &alg)); ++ assert_int_equal(strcasecmp(alg, "EDDSA"), 0); ++ rnp_buffer_destroy(alg); ++ /* key bits */ ++ uint32_t bits = 0; ++ assert_rnp_success(rnp_key_get_bits(key, &bits)); ++ assert_int_equal(bits, 255); ++ /* curve */ ++ char *curve = NULL; ++ assert_rnp_success(rnp_key_get_curve(key, &curve)); ++ assert_int_equal(strcasecmp(curve, "ed25519"), 0); ++ rnp_buffer_destroy(curve); ++ /* key flags */ ++ bool flag = false; ++ assert_rnp_success(rnp_key_allows_usage(key, "sign", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "certify", &flag)); ++ assert_true(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "encrypt", &flag)); ++ assert_false(flag); ++ assert_rnp_success(rnp_key_allows_usage(key, "authenticate", &flag)); ++ assert_false(flag); ++ /* user ids */ ++ size_t uids = 0; ++ char * uid = NULL; ++ assert_rnp_success(rnp_key_get_uid_count(key, &uids)); ++ assert_int_equal(uids, 1); ++ assert_rnp_success(rnp_key_get_uid_at(key, 0, &uid)); ++ assert_string_equal(uid, "eddsa_25519"); ++ rnp_buffer_destroy(uid); ++ /* subkey */ ++ size_t subkeys = 0; ++ assert_rnp_success(rnp_key_get_subkey_count(key, &subkeys)); ++ assert_int_equal(subkeys, 1); ++ rnp_key_handle_t subkey = NULL; ++ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &subkey)); ++ /* check properties of the generated subkey */ ++ assert_rnp_success(rnp_key_is_primary(subkey, &boolres)); ++ assert_false(boolres); ++ assert_rnp_success(rnp_key_have_public(subkey, &boolres)); ++ assert_true(boolres